WordPress stores a hash of the admin password inDocumentation Index
Fetch the complete documentation index at: https://docs.frankenpress.com/llms.txt
Use this file to discover all available pages before exploring further.
wp_users, so updating
a Secret alone doesn’t change the live login. The fp-site chart ships
with a credential-sync initContainer on every site Pod that reads the
install Secret and reconciles wp_users on Pod start. Pair it with
Stakater Reloader
and rotation is self-driving end-to-end.
End-to-end flow
helm upgrade, no argocd app sync, no kubectl exec. Available in
chart v0.4.0+.
How the initContainer works
Every site Pod runs async-admin-credentials initContainer before the
main site container. The initContainer:
Wait for the database
Direct
mysqli probe (doesn’t load WordPress) up to 60×2s. The
same probe the install Job uses — works against fresh DBs that
haven’t seen wp core install yet.Skip on fresh installs
If
wp core is-installed returns false, exit 0. The post-install
Helm hook Job is responsible for first-time bootstrap; the
initContainer would have nothing to reconcile.Idempotency check
Resolve the target user (prefer
admin_email, fall back to
admin_user). Compare the desired credentials against the current
DB state via wp eval:wp_check_password(getenv("ADMIN_PASSWORD"), $u->user_pass, $u->ID)— hash compare$u->user_email === getenv("ADMIN_EMAIL")— string compare$u->user_login === getenv("ADMIN_USER")— string compare
[sync-creds] DB already in sync with Secret; skipping update.Multi-replica idempotency
WithreplicaCount > 1, every Pod runs the initContainer on start. The
default rolling-update strategy (maxSurge: 1, maxUnavailable: 0) brings
new Pods up sequentially, so:
- The first Pod to roll detects drift, runs
wp user update. WP fires its “Password Changed” notification (one email). - Every subsequent Pod sees the DB already-in-sync and short-circuits
before calling
wp user update. No DB write. No notification.
wp user update on every roll, generating noise and
duplicate emails.
Reloader setup
The initContainer only runs when the Pod restarts. To turn an updated Secret into a Pod restart automatically, install Stakater Reloader cluster-wide and annotate the Deployment. Full setup is in Operations → Email → Auto-rolling on Secret change — Reloader is platform-wide infrastructure, not feature-specific. Once installed, the same controller solves rotation for the SMTP token, DB password, WP keys+salts, and the admin Secret. For admin-credential rotation specifically, watch the install Secret:For the auto-generated install Secret the name is
<release>-fp-site-install. With siteInstall.existingSecret set,
use whatever name you supplied.Verifying a rotation took effect
After the Pod has rolled, check the live DB user from any running Pod:wp_check_password against the
expected value in-pod:
ok confirms the live DB hash matches the Secret value. mismatch
means rotation hasn’t propagated yet — check the Pod has actually
rolled (kubectl rollout status deployment/<release>-fp-site) and
that Reloader sees the Secret (kubectl logs -n reloader deploy/reloader-reloader).
For a full e2e (rotate + assert hash matches across all replicas), see
tests/credential-sync-test.sh
in fp-charts.
Failure modes
| State | Behaviour |
|---|---|
| Reloader not installed | Secret update doesn’t roll the Pod. The initContainer never re-runs, so wp_users drifts from the Secret until something else triggers a roll (kubectl rollout restart, helm upgrade, autoscaler scale-up, etc.). Install Reloader. |
| Reloader watching the wrong key | Annotation set on podAnnotations instead of commonAnnotations. Reloader watches the Deployment object’s metadata.annotations, not the pod-template’s. Move the annotation. |
Pod stuck Init:Error after rotation | The initContainer ran but failed (typically a wp-cli error visible in kubectl logs <pod> -c sync-admin-credentials). The Deployment never becomes Ready, the rollout times out, old replicas keep serving with the old password. Investigate the log; the most likely cause is the admin user not being resolvable by either email or username (e.g. someone manually renamed it via wp-admin). |
smtp.enabled=false (default) | Each rotation suppresses WP’s password-change notification via a WP_CLI::add_hook( 'after_wp_load', ... ) filter file. The site container’s view of wp_mail() is unchanged. |
smtp.enabled=true | WP fires its built-in “Password Changed” / “Email Changed” notification when wp user update writes. Expected behaviour — site authors and ops both like seeing rotation in their inbox. To suppress globally, install a wp_mail filter mu-plugin (out-of-scope for this chart; see What’s not in the platform). |
| Drifted manual edit via wp-admin | If an operator changed the admin password in wp-admin without updating the Secret, the next Pod roll detects drift and overwrites their manual change with the Secret’s value. The Secret is the source of truth — set syncAdminCredentials: false if you want the in-DB value to win. |
Opting out
SetsyncAdminCredentials: false to skip the initContainer entirely:
helm template confirms no initContainer block renders. The admin
user becomes whatever wp core install set on first install — never
touched again by the chart.
When to opt out:
- External IdP owns the WP admin user. SSO plugin (e.g. SAML, LDAP, OIDC) provisions and updates wp_users from the identity provider. The chart’s reconciler would fight with the SSO plugin’s writes.
- You don’t trust the Secret as source of truth. Some teams prefer wp-admin as the canonical credential store. Opting out means manual edits via wp-admin are not overwritten by Pod rolls.
- Static demo / single-shot install. No rotation expected, saves the per-Pod-start initContainer cost (~1-3s).
What’s not in the platform
- Auto-suppression of WP “Password Changed” emails when SMTP is on.
When
smtp.enabled=trueandwp user updatewrites, WP fires its built-in notification email. That’s correct WP behaviour and most operators want to see rotation in their inbox. Sites that want this suppressed globally need a one-linewp_mailfilter mu-plugin (add_filter('send_password_change_email', '__return_false')); not shipped infp-mu-pluginbecause of conflicting opinions. - Sync of arbitrary user fields. The initContainer reconciles
user_pass,user_email, anduser_login. Display name, roles, and metadata are not synced — those live in WP and stay there. - Multi-user reconciliation. One admin user per site, resolved by
email-then-username. Multi-admin orgs run their own provisioning
(SSO plugin or
wp user createin build-time scripts).
Related
syncAdminCredentialsvalue — chart values reference- Reloader install + annotations — platform-wide setup
- Migrating auto-generated Secrets to a secret manager — extract the install Secret into ASM/Vault/etc.
- Plan: install-job-credential-sync-gaps.md — the design rationale behind moving sync from the install Job to a Pod-lifecycle initContainer