Skip to main content
The default site Helm install bundles in-cluster MariaDB + Redis + MinIO subcharts so a helm install works on kind with zero prerequisites. These defaults are not for production. This page documents the recommended swaps.

Production swap matrix

Why swap?

The bundled bitnami/mariadb is a single-replica StatefulSet with no automatic backups, no failover, no point-in-time recovery, and no schema migration tooling. Fine for kind, dangerous for production.MariaDB Operator provides a declarative MariaDB custom resource with replication, backups (Galera, mariabackup, mysqldump), User / Database / Grant CRDs, and Galera cluster mode for HA.
Souin uses the RESP protocol to talk to its cache backend. DragonflyDB is a drop-in Redis-protocol-compatible in-memory store with dramatically better single-node throughput (Dragonfly’s docs cite ~25× over Redis on multi-core hardware because of its lock-free shared-nothing architecture).The Dragonfly Operator deploys it declaratively. From FrankenPress’s perspective it’s just a Redis-protocol endpoint — set externalCache.host and we don’t care what’s actually behind the address.
Self-hosted object storage in your cluster is operationally costly (replication, backups, the data plane is on the same critical path as everything else). A managed S3-compatible service (AWS S3, Cloudflare R2, Google Cloud Storage XML, Backblaze B2) takes that operational burden off you and is usually cheaper at WordPress-site scale.humanmade/s3-uploads (which mu-plugin configures) talks the S3 API; it doesn’t care which provider answers.
The chart’s default keysSalts.autoGenerate: true runs a one-shot Job that creates a Secret containing the eight WP auth keys + salts on first install. Fine for instant deploy, but for production you want secrets to live in your cloud secret manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, 1Password Connect, etc.).Set keysSalts.autoGenerate: false and keysSalts.existingSecret: <name>, then have ESO sync the values into that Secret.
When you swap the bundled MinIO subchart for a real S3 (or R2 / GCS XML) bucket, configure it as below. These defaults match what mu-plugin v0.2.0+ expects.

Bucket settings

Block Public Access — pick one

Block-public-access has four sub-toggles. Two scenarios:
  • CDN-fronted (recommended): all four toggles on. CloudFront / Cloudflare reaches the bucket via an Origin Access Control (CloudFront) or a bucket policy that whitelists the CDN’s IP ranges / service-account.
  • Bucket as public origin: “Block public ACLs” can stay on (we don’t set ACLs anyway in v0.2.0+); “Block public bucket policies” must be off, and you attach a s3:GetObject policy for * / Principal: "*".

IAM policy (minimum required)

The IAM principal humanmade/s3-uploads authenticates as needs:
Notable omissions:
  • s3:PutObjectAcl is not needed — the v0.2.0+ default sends no ACL header. Granting it doesn’t hurt but it’s pointless on a bucket-owner-enforced bucket (the API call would fail with AccessControlListNotSupported).
  • s3:GetBucketLocation is not needed — humanmade/s3-uploads doesn’t issue getBucketLocation; we set the region via the FP_S3_REGION env var.

CORS (only if you serve media on a different origin)

If bucketUrl differs from WP_HOME (CDN domain, separate static host) and the WP admin’s media library uses a XHR.responseType = "blob" flow against the bucket URL, browsers enforce CORS on the fetch:
Most WP installs don’t need CORS — the front-end fetches images via <img src> (no CORS preflight) and the admin renders thumbnails server-side. Add the policy only when a console error surfaces.

Quick verification after creating the bucket

Migrating auto-generated Secrets to a secret manager

If you ran helm install with the chart’s defaults you already have a working site backed by auto-generated WP keys+salts and admin credentials, stored in two Secrets:
  • <release>-site-keys — the eight WP auth keys and salts
  • <release>-site-install — the wp-admin credentials (when siteInstall.autoGenerate: true)
To move to a secret-manager-backed deploy without losing your existing keys (rotating WP keys+salts logs every user out of every session and invalidates every nonce), extract the current values and seed them into your secret manager.

Extract WP keys+salts

JSON object ready to paste into a single AWS Secrets Manager / GCP Secret Manager / Vault secret:
Output is a flat object with the eight keys (AUTH_KEY, SECURE_AUTH_KEY, …, NONCE_SALT):
KEY=value-per-line if you’d rather pipe into something else:
Push to AWS Secrets Manager in one shot:
Then point the chart at the ESO-synced Secret:

Extract admin install credentials

Same shape:
Yields admin_user / admin_email / admin_password. Push those into your secret manager, then:

Other Secrets

Same kubectl get secret -o json | jq pattern works for every Secret the chart consumes: The MariaDB and MinIO subchart Secrets only exist while their respective subchart is enabled: true — for production you’ll provision the DB / object store separately and supply credentials via your secret manager from day one.

End-to-end production values

The chart’s defaults already satisfy PodSecurity Standard restricted:latestrunAsNonRoot: true, runAsUser: 33, fsGroup: 33, seccompProfile.type: RuntimeDefault at the pod level; allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: [ALL] per container — so production overrides above don’t need to repeat them. If you build a custom runtime that re-adds the cap_net_bind_service file capability, override containerSecurityContext.allowPrivilegeEscalation: true and containerSecurityContext.capabilities.add: [NET_BIND_SERVICE] (the kernel’s no_new_privs flag would otherwise refuse to exec the binary). The published runtime strips the cap, so this only matters for forked images. Install:

Image promotion across environments

The same site image (e.g. ghcr.io/your-org/your-site:v1.0.0) is promoted from staging → production. Only values-prod.yaml differs — same code, different config. Use whatever GitOps tooling you prefer (Argo CD, Flux, Kargo for tag promotion + verification).FrankenPress doesn’t ship its own GitOps glue — the chart renders k8s primitives that any orchestrator can consume.

Multi-site

For multiple sites in one cluster, each gets its own:
  • Namespace
  • Helm release (helm install <release-name> ...)
  • DB / cache / S3 endpoints (or shared infrastructure with per-site DBs / buckets)
  • Secret with WP keys+salts
The chart is single-tenant by design. Multi-tenant features (per-site URL routing in one Deployment, etc.) are explicitly out of scope.