Skip to main content
Every env var the FrankenPress platform reads, grouped by component. None are required at the runtime layer — defaults produce a working config. Site-level vars (WP_*, DB_*, the eight *_KEY / *_SALT) are the consuming site’s responsibility.

runtime (Caddy + FrankenPHP + Souin)

VarDefaultPurpose
REDIS_URLredis:6379Redis backing Souin’s HTTP cache
FP_CACHE_TTL5mDefault cache entry TTL
FP_CACHE_STALE1hStale-while-revalidate window
FP_CACHE_DEFAULT_CONTROLpublic, s-maxage=300Cache-Control fallback
FP_CACHE_BYPASS_EXTRA(empty)Extra alternation fragment appended to the path-bypass regex (e.g. |^/api/private/). See components/runtime → Cacheability model.
FP_DOCROOT/app/webWordPress webroot inside the container
FP_PORT8080Public HTTP listen port
FP_METRICS_PORT9145Prometheus metrics listen port
The runtime emits Caddy access logs as JSON on stdout (public server only) and PHP errors on stderr. Shipping setup is in Logging.

mu-plugin: S3UploadsBootstrap

VarDefaultRequired
FP_S3_BUCKET
FP_S3_KEY
FP_S3_SECRET
FP_S3_REGIONus-east-1
FP_S3_BUCKET_URLPublic URL clients use to fetch media. CDN URL or raw bucket URL for AWS S3 / R2 / GCS XML; port-forward or HTTPRoute URL for the bundled MinIO subchart. When unset, humanmade/s3-uploads bakes canonical https://<bucket>.s3.amazonaws.com URLs into attachment GUIDs and post content — required for any non-AWS bucket. Changing this on an existing site does not rewrite already-stored URLs; use wp search-replace.
FP_S3_ENDPOINToptional, for non-AWS S3-compatible (MinIO, R2, GCS XML)
FP_S3_OBJECT_ACL(empty — no ACL)optional S3 object ACL. Leave unset for buckets with Object Ownership = “Bucket owner enforced” (the AWS new-bucket default since April 2023). Set to public-read / private / authenticated-read only for ACL-enabled buckets.
FP_S3_DISABLEDauto: on in-cluster, off out-of-clustertri-state. Truthy (1/true/yes/on) → off. Falsy (0/false/no/off) → force on. Unset → default gates on KUBERNETES_SERVICE_HOST (kubelet-injected on every pod): production stays on, local dev skips the s3:// stream wrapper so admin install flows hit local disk. Force on locally with FP_S3_DISABLED=0.
When required vars are missing, the bootstrap registers wp_handle_upload_prefilter to refuse uploads with a clear error message rather than silently fall back to local disk.

mu-plugin: SouinInvalidator

VarDefaultPurpose
FP_SOUIN_REDIS_HOSTredisRedis hostname
FP_SOUIN_REDIS_PORT6379Redis port
FP_SOUIN_REDIS_PASSWORD(empty)Redis AUTH password
FP_SOUIN_REDIS_DB0Logical database
FP_SOUIN_REDIS_TIMEOUT1.0Connect timeout (seconds)
FP_SOUIN_DISABLEDfalseTruthy → no-op the invalidator (cache then expires only by TTL)
If ext-redis isn’t loaded or the connection fails, the invalidator becomes a silent no-op. Errors are logged via error_log.

WordPress site (site-template)

These are read by config/application.php in the site template. None are FrankenPress-specific — they’re standard WordPress config.
VarDefaultPurpose
WP_ENVproductionSelects which config/environments/*.php overrides load
WP_HOME(required)Site URL (no trailing slash)
WP_SITEURL(required)WordPress core URL (typically ${WP_HOME}/wp)
DB_HOSTlocalhostDatabase host
DB_NAME, DB_USER, DB_PASSWORD(required)Database connection
DB_PREFIXwp_Table prefix
AUTH_KEY etc. (8 keys+salts)(required)WP auth secrets — auto-generated by the Helm chart’s bootstrap Job by default
FORCE_SSL_ADMINtrueForce SSL for admin (the chart sets X-Forwarded-Proto pass-through)
DISABLE_WP_CRONfalseDisable WP’s in-process pseudo-cron. The Helm chart sets this to true automatically when wpCron.enabled is on, so the CronJob is the only driver. Sites built from site-template v0.2.3+ honour this; older images ignore it.

mu-plugin: SMTPMailer

VarDefaultRequiredNotes
FP_SMTP_HOST(unset)yes (when opting in)SMTP server hostname. Component is a no-op when unset.
FP_SMTP_PORT587TCP port
FP_SMTP_ENCRYPTIONtlstls (STARTTLS), ssl (implicit TLS), none (local dev only)
FP_SMTP_USERNAME(unset)yes (when opting in)SMTP auth username
FP_SMTP_PASSWORD(unset)yes (when opting in)SMTP auth password
FP_SMTP_FROM_EMAIL(WP admin_email)wp_mail_from filter target
FP_SMTP_FROM_NAME(WP blogname)wp_mail_from_name filter target
FP_SMTP_DISABLEDfalseTruthy → mu-plugin bootstrap is a no-op. Local-dev only; the Helm chart never sets this.
When FP_SMTP_HOST is unset, wp_mail() falls through to PHP’s mail() and fails silently because runtime ships no MTA. The chart sets smtp.enabled=true to wire the env above; see Operations → Email for provider recipes.

mu-plugin: SnapshotExporter

Daily wp-cron + admin-button capture-and-upload to a per-tenant S3 snapshot bucket. Drives the prod-side half of fp pull. Dormant when FP_SNAPSHOT_BUCKET is unset — zero overhead on non-opted-in tenants.
VarDefaultRequiredNotes
FP_SNAPSHOT_BUCKET(unset)yes (when opting in)Snapshot bucket name (e.g. sts-production-snapshots-eu-west-2-533158516642). Component is dormant when unset.
FP_SNAPSHOT_KEY(unset)yes (when opting in)AWS access key id for the snapshot-bucket service user
FP_SNAPSHOT_SECRET(unset)yes (when opting in)AWS secret access key for the same user
FP_SNAPSHOT_REGIONeu-west-2AWS region of the snapshot bucket
FP_APPLY_REMAP_AUTHORS(unset)Apply-time only — set transiently by Restorer before wp import to rewrite imported post_author to the local admin. Never set this manually.
The chart’s snapshotExport.* values surface map to these env vars (see components/charts). The K8s Secret is typically projected from AWS Secrets Manager via ExternalSecret.

Build args (runtime)

Set at image build time with --build-arg:
ArgDefaultPurpose
PHP_VERSION8.3PHP series
FRANKENPHP_VERSION1.12.2Pinned FrankenPHP base tag
CACHE_HANDLER_VERSIONv0.16.0Souin cache-handler module
STORAGES_GO_REDIS_VERSIONv0.0.19Souin Redis storage
CADDY_CBROTLI_VERSIONv1.0.1Brotli encoding
WP_CLI_VERSION2.12.0WP-CLI release
FP_MU_PLUGIN_VERSIONv0.1.1mu-plugin tag to bake; set to "" to skip

Helm values (site chart)

A condensed reference of the most-used values — see charts/charts/site/values.yaml for every annotated parameter.
image:
  registry: ghcr.io
  repository: frankenpress/site-template
  tag: ""                       # defaults to chart appVersion
  pullPolicy: IfNotPresent
  pullSecrets: []

site:
  url: "http://site.localhost"
  env: production
  wpPath: /wp

keysSalts:
  autoGenerate: true            # one-shot Job, or:
  existingSecret: ""            # External Secrets Operator's Secret name

siteInstall:                    # post-install/post-upgrade `wp core install` + `update-db` Job
  enabled: true                 # set false to skip (e.g. restoring from a DB dump)
  title: "FrankenPress site"
  adminUser: "admin"
  adminEmail: "admin@example.com"
  adminPassword: ""             # empty → generated; stored in <release>-site-install Secret
  autoGenerate: true            # render the install Secret; off if existingSecret is set
  existingSecret: ""            # bring-your-own Secret (any source: ESO, Sealed, manual)
  existingSecretAdminUserKey: "admin_user"
  existingSecretAdminEmailKey: "admin_email"
  existingSecretAdminPasswordKey: "admin_password"
  skipEmail: true               # pass --skip-email to wp core install

syncAdminCredentials: true      # initContainer on every Pod reconciles wp_users from the install Secret
                                # — false to skip (e.g. external IdP owns the WP admin user)

replicaCount: 1
revisionHistoryLimit: 3         # old ReplicaSets retained for kubectl rollout undo (default 3, was k8s-default 10 pre-0.4.1)
autoscaling:
  enabled: false                # set true + minReplicas/maxReplicas in prod

serviceMonitor:                 # prometheus-operator integration (optional)
  enabled: false                # self-gates on the monitoring.coreos.com/v1 CRD
  namespace: ""                 # defaults to release namespace
  interval: 30s
  scrapeTimeout: 10s
  labels: {}                    # match your Prometheus's serviceMonitorSelector

smtp:                           # email delivery via SMTP (optional)
  enabled: false                # opt-in
  host: ""                      # e.g. smtp.postmarkapp.com
  port: 587                     # 587 STARTTLS, 465 implicit TLS
  encryption: tls               # tls | ssl | "" (none)
  fromEmail: ""                 # default: WP admin_email
  fromName: ""                  # default: WP blogname
  auth:
    existingSecret: ""          # required when SMTP server needs auth
    usernameKey: username
    passwordKey: password

ingress:
  enabled: false                # pick this OR httpRoute
httpRoute:
  enabled: false                # pick this OR ingress

mariadb: { enabled: true }      # off → externalDatabase.host = ...
redis:   { enabled: true }      # off → externalCache.host = ...
minio:   { enabled: true }      # off → externalS3.endpoint = ...

externalDatabase:               # used when mariadb.enabled = false
  host: ""
  database: wordpress
  user: wordpress
  existingSecret: ""            # password lives here

externalCache:                  # used when redis.enabled = false
  host: ""
  port: 6379

externalS3:                     # bucket/region/endpoint/secrets used when minio.enabled = false
  bucket: site-media
  region: us-east-1
  endpoint: ""                  # leave empty for AWS S3
  bucketUrl: ""                 # required for any non-AWS bucket (CDN, R2, MinIO). Read in both modes.
  existingSecret: ""            # keys: access-key, secret-key

Password rotation

Two passwords participate in rotation: the WordPress admin password and the database password. They behave differently because of where WordPress reads each one.

Database password

DB_PASSWORD is read from a secretKeyRef by the Deployment, the wpcron CronJob, and the install Job. When the Secret value changes (manual edit, External Secrets Operator sync, sealed-secret rotation, etc.), restart the Deployment and pods pick up the new value. No chart-side reconciliation is required.
kubectl rollout restart deployment/mysite
Or wire up Stakater Reloader once cluster-wide and skip the manual restart for every Secret rotation on the platform — the database password, WP keys+salts, S3 credentials, and the SMTP token all benefit. Rotating the password on the DB server itself is the DB’s lifecycle (mariadb-operator, RDS rotation, etc.) — the chart consumes whatever’s in the Secret.

WordPress admin password

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, chart v0.4.0+) reconciles wp_users against the install Secret on every Pod start. Pair it with Reloader and rotation is self-driving:
# Whatever rotates your Secret updates the value:
kubectl patch secret mysite-install \
  -p '{"stringData":{"admin_password":"new-value"}}'

# Reloader rolls the Deployment → initContainer detects drift →
# wp user update → DB caught up to Secret. No helm upgrade needed.
Set syncAdminCredentials: false to skip the initContainer (e.g. when an external IdP owns the WP admin user, or when you treat wp-admin as the canonical credential store). Full flow, verification, and failure modes are in Admin credential rotation.