Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.frankenpress.com/llms.txt

Use this file to discover all available pages before exploring further.

charts ships site — a Helm chart that deploys a single FrankenPress WordPress site to Kubernetes. Bitnami chart style throughout: every value annotated with ## @param, naming + labels via the bitnami/common library, optional subchart deps via the Bitnami OCI registry.

Install

The chart is published as an OCI artifact on GHCR:
helm install mysite oci://ghcr.io/frankenpress/charts/site \
  --version 0.5.0 \
  --namespace mysite --create-namespace
For the kind-cluster quickstart, see Quickstart. For the production topology (DragonflyDB Operator, MariaDB Operator, AWS S3, External Secrets), see Operations → Production topology.

What gets deployed

The install Job is new in chart v0.2.0 — fresh deploys produce a usable site without manual kubectl exec ... wp core install. The sync-admin-credentials initContainer (chart v0.4.0+) reconciles wp_users from the install Secret on every Pod start. See First install for credentials and Operations → Admin credential rotation for the rotation flow.

Subchart dependencies

SubchartDefaultPurpose
bitnami/commonalways loadedLibrary helpers (fullname, labels, image templating)
bitnami/mariadbenabledIn-cluster DB for instant deploy. Not for production.
bitnami/redisenabledSouin HTTP cache backend. Production: swap to DragonflyDB Operator.
bitnami/minioenabledS3-compatible object storage. Production: swap to AWS S3 / R2 / GCS.
Bitnami withdrew their bitnami/<name> images from free public Docker Hub in 2025 (“Bitnami Secure Images” commercial pivot). The chart’s default values point at the bitnamilegacy/<name> mirror Bitnami publishes for community use. We track this as a Renovate concern; see the values.yaml comments.

Values reference

The full annotated values reference is in charts/site/values.yaml; the most-used keys:
KeyDefaultPurpose
image.repositoryfrankenpress/site-templateYour site image
image.tag"" (chart appVersion)Pin per release
site.urlhttp://site.localhostWP_HOME — must match your access URL
site.envproductionSelects config/environments/<env>.php
keysSalts.autoGeneratetrueOff → set keysSalts.existingSecret
siteInstall.enabledtrueOff → skip auto wp core install (use for DB-restore deploys)
siteInstall.adminUseradminAuto-generated path
siteInstall.existingSecret""BYO Secret (key names configurable)
syncAdminCredentialstrueInitContainer that reconciles wp_users from the install Secret on every Pod start (idempotent, multi-replica safe). Pair with Reloader for self-driving rotation. Set false to skip — see Admin credential rotation.
replicaCount1Deployment replicas (HPA optional)
revisionHistoryLimit3Old ReplicaSets retained for kubectl rollout undo. Lower than the k8s default of 10 since helm rollback and image-tag-pinned re-deploys are the canonical rollback paths in this stack. Chart v0.4.1+.
ingress.enabled / httpRoute.enabledfalse / falsePick one for external routing
mariadb.enabled / redis.enabled / minio.enabledtrue / true / trueBundled subcharts (kind dev)
externalDatabase.host""Used when mariadb.enabled=false
externalCache.host""Used when redis.enabled=false
externalS3.bucketsite-mediaUsed when minio.enabled=false
wpCron.enabledtrueRun a CronJob that fires wp cron event run --due-now. When true, the chart also sets DISABLE_WP_CRON=true on the site so WP’s in-process pseudo-cron doesn’t fire alongside it (the two settings are coupled). Disable to fall back to WP’s request-triggered cron.
wpCron.schedule*/5 * * * *wp-cron CronJob frequency
serviceMonitor.enabledfalseRender a prometheus-operator ServiceMonitor for the :9145/metrics endpoint. Self-gates on the monitoring.coreos.com/v1 CRD — leaving on for clusters without it is safe (renders nothing). Set serviceMonitor.labels to whatever your Prometheus’s serviceMonitorSelector expects.
smtp.enabledfalseWire wp_mail() through SMTP. Inject 5 FP_SMTP_* env vars + 2 secretKeyRef env entries into the Deployment, wpcron CronJob, and install Job. Pairs with the SMTPMailer mu-plugin component. Transport-agnostic — Postmark, SendGrid, Mailgun, AWS SES, in-cluster relay. See Operations → Email for provider recipes. Requires site-template v0.2.4+.
smtp.host / smtp.port / smtp.encryption"" / 587 / tlsSMTP server, TCP port, transport encryption (tls for STARTTLS, ssl for implicit TLS, "" for plaintext local-dev only).
smtp.auth.existingSecret""Required when smtp.enabled=true and the server requires auth. Secret keys default to username / password; override via smtp.auth.usernameKey / passwordKey. Leave empty for unauthenticated in-cluster relays.
tmpfsSize.tmp / data / config256Mi / 64Mi / 16Mitmpfs sizes for the read-only-root pod
A condensed reference is at Operations → Configuration. The chart deliberately doesn’t ship a log shipper — pod streams are backend-neutral and any cluster-side agent picks them up. Setup for Vector / Grafana Alloy / Datadog Agent is in Operations → Logging.

First install

The chart runs wp core install and wp core update-db from a post-install/post-upgrade Helm hook Job — a fresh helm install produces a usable WordPress site without any manual kubectl exec. The install step is idempotent (wp core is-installed short-circuits re-runs), so the Job is safe across helm upgrades.

Default behavior

By default, the chart creates a <release>-site-install Secret with a random 32-char admin password. Retrieve it after install:
kubectl --namespace mysite get secret mysite-site-install \
  -o jsonpath='{.data.admin_password}' | base64 -d
Override the username, email, or password at install:
helm install mysite oci://ghcr.io/frankenpress/charts/site \
  --set siteInstall.adminUser=alice \
  --set siteInstall.adminEmail=alice@example.com \
  --set siteInstall.adminPassword=please-change-me

Bring-your-own Secret

Point at a pre-existing Secret with the admin credentials:
siteInstall:
  existingSecret: mysite-admin
  existingSecretAdminUserKey: admin_user      # default
  existingSecretAdminEmailKey: admin_email    # default
  existingSecretAdminPasswordKey: admin_password  # default
The chart doesn’t care how the Secret got there — kubectl create secret, External Secrets Operator (any provider: AWS Secrets Manager, GCP Secret Manager, Vault, 1Password), Sealed Secrets, SOPS-decrypted, anything works. The configurable key names let the Secret use whatever schema the source system produces.
Already running with auto-generated credentials and now want to move them into your secret manager? See Production → Migrating auto-generated Secrets to a secret manager for the kubectl get secret -o json | jq extract commands (also covers WP keys+salts and the DB / S3 Secrets).

Password rotation

WordPress stores a hash of the admin password in wp_users, so simply updating the Secret value won’t change the live login. The chart’s sync-admin-credentials initContainer (default on) handles this on every Pod start — pair it with Stakater Reloader so the Pod actually rolls when the Secret changes:
# values.yaml
syncAdminCredentials: true   # default — initContainer on every site Pod

commonAnnotations:
  # Reloader watches the install Secret and rolls the Deployment when
  # admin_password / admin_email change. commonAnnotations lands on
  # the Deployment's metadata.annotations (where Reloader looks);
  # podAnnotations lands on the pod-template (which Reloader ignores).
  secret.reloader.stakater.com/reload: "<release>-site-install"
Once that’s wired, the full flow is automatic:
secret manager → ESO → install Secret bumps →
  Reloader rolls Deployment → initContainer detects drift →
    wp user update → wp_users caught up to Secret
Idempotent and multi-replica safe — only the first Pod to roll runs wp user update; subsequent Pods see the DB in sync and short-circuit. One DB write, one WP “Password Changed” notification per rotation, regardless of replica count. Full setup, verification, failure modes, and the syncAdminCredentials: false opt-out are in Operations → Admin credential rotation. The database password rotates differently — the Deployment, wpcron CronJob, and install Job all read DB_PASSWORD from secretKeyRef, so when the Secret value changes, restarting pods picks up the new value automatically. No chart-side reconciliation needed (and Reloader handles the restart trigger).

Skipping install

For sites being restored from an existing database dump:
helm install mysite oci://ghcr.io/frankenpress/charts/site \
  --set siteInstall.enabled=false
wp core update-db is skipped too, so make sure your dump is schema-compatible with the WP core version baked into the site image. If it isn’t, re-enable the Job after restore — wp core is-installed will short-circuit and only update-db will run.

Production overrides example

# values-prod.yaml
image:
  repository: ghcr.io/your-org/your-site
  tag: v1.0.0

site:
  url: https://mysite.example.com
  env: production

replicaCount: 3
autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10

ingress:
  enabled: true
  className: nginx
  hostname: mysite.example.com
  tls: true

# Disable in-cluster subcharts.
mariadb:
  enabled: false
redis:
  enabled: false
minio:
  enabled: false

# Point at production services.
externalDatabase:
  host: mysite-mariadb-primary.databases.svc.cluster.local
  database: mysite
  user: mysite
  existingSecret: mysite-db-credentials

externalCache:
  host: mysite-dragonfly.cache.svc.cluster.local
  port: 6379

externalS3:
  bucket: mysite-media-prod
  region: eu-west-1
  bucketUrl: https://cdn.mysite.example.com
  existingSecret: mysite-s3-credentials   # keys: access-key, secret-key

# External Secrets Operator (recommended) for keys+salts.
keysSalts:
  autoGenerate: false
  existingSecret: mysite-wp-keys

CI / publishing

  • lint.ymlhelm lint + helm template + chart-testing ct lint on every push and PR.
  • release.yml — on v*.*.* tag push, helm package + push to oci://ghcr.io/frankenpress/charts/site:<version>. Tag-driven, never push-to-main-driven, so Chart.yaml version and the git tag move together. OCI is the canonical channel — no gh-pages fallback.