> ## 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.

# Configuration reference

> Every FrankenPress env var with default and purpose

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)

| Var                        | Default                | Purpose                                                                                                                                                                        |
| -------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `REDIS_URL`                | `redis:6379`           | Redis backing Souin's HTTP cache                                                                                                                                               |
| `FP_CACHE_TTL`             | `5m`                   | Default cache entry TTL                                                                                                                                                        |
| `FP_CACHE_STALE`           | `1h`                   | Stale-while-revalidate window                                                                                                                                                  |
| `FP_CACHE_DEFAULT_CONTROL` | `public, s-maxage=300` | `Cache-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](/components/runtime#cacheability-model). |
| `FP_DOCROOT`               | `/app/web`             | WordPress webroot inside the container                                                                                                                                         |
| `FP_PORT`                  | `8080`                 | Public HTTP listen port                                                                                                                                                        |
| `FP_METRICS_PORT`          | `9145`                 | Prometheus 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](/operations/logging).

## mu-plugin: S3UploadsBootstrap

| Var                | Default                                 | Required                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ------------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `FP_S3_BUCKET`     | —                                       | ✓                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `FP_S3_KEY`        | —                                       | ✓                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `FP_S3_SECRET`     | —                                       | ✓                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `FP_S3_REGION`     | `us-east-1`                             |                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `FP_S3_BUCKET_URL` | —                                       | Public 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_ENDPOINT`   | —                                       | optional, 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_DISABLED`   | auto: on in-cluster, off out-of-cluster | tri-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

| Var                       | Default | Purpose                                                         |
| ------------------------- | ------- | --------------------------------------------------------------- |
| `FP_SOUIN_REDIS_HOST`     | `redis` | Redis hostname                                                  |
| `FP_SOUIN_REDIS_PORT`     | `6379`  | Redis port                                                      |
| `FP_SOUIN_REDIS_PASSWORD` | (empty) | Redis AUTH password                                             |
| `FP_SOUIN_REDIS_DB`       | `0`     | Logical database                                                |
| `FP_SOUIN_REDIS_TIMEOUT`  | `1.0`   | Connect timeout (seconds)                                       |
| `FP_SOUIN_DISABLED`       | `false` | Truthy → 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.

| Var                                 | Default      | Purpose                                                                                                                                                                                                                             |
| ----------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WP_ENV`                            | `production` | Selects 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_HOST`                           | `localhost`  | Database host                                                                                                                                                                                                                       |
| `DB_NAME`, `DB_USER`, `DB_PASSWORD` | (required)   | Database connection                                                                                                                                                                                                                 |
| `DB_PREFIX`                         | `wp_`        | 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_ADMIN`                   | `true`       | Force SSL for admin (the chart sets X-Forwarded-Proto pass-through)                                                                                                                                                                 |
| `DISABLE_WP_CRON`                   | `false`      | Disable 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

| Var                  | Default            | Required             | Notes                                                                                        |
| -------------------- | ------------------ | -------------------- | -------------------------------------------------------------------------------------------- |
| `FP_SMTP_HOST`       | (unset)            | yes (when opting in) | SMTP server hostname. Component is a no-op when unset.                                       |
| `FP_SMTP_PORT`       | `587`              |                      | TCP port                                                                                     |
| `FP_SMTP_ENCRYPTION` | `tls`              |                      | `tls` (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_DISABLED`   | `false`            |                      | Truthy → 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](/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`](/designer-flow-fp-pull).
Dormant when `FP_SNAPSHOT_BUCKET` is unset — zero overhead on
non-opted-in tenants.

| Var                      | Default     | Required             | Notes                                                                                                                                           |
| ------------------------ | ----------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `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_REGION`     | `eu-west-2` |                      | AWS 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`](/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`:

| Arg                         | Default   | Purpose                                    |
| --------------------------- | --------- | ------------------------------------------ |
| `PHP_VERSION`               | `8.3`     | PHP series                                 |
| `FRANKENPHP_VERSION`        | `1.12.2`  | Pinned FrankenPHP base tag                 |
| `CACHE_HANDLER_VERSION`     | `v0.16.0` | Souin cache-handler module                 |
| `STORAGES_GO_REDIS_VERSION` | `v0.0.19` | Souin Redis storage                        |
| `CADDY_CBROTLI_VERSION`     | `v1.0.1`  | Brotli encoding                            |
| `WP_CLI_VERSION`            | `2.12.0`  | WP-CLI release                             |
| `FP_MU_PLUGIN_VERSION`      | `v0.1.1`  | mu-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`](https://github.com/frankenpress/charts/blob/main/charts/site/values.yaml)
for every annotated parameter.

```yaml theme={null}
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.

```bash theme={null}
kubectl rollout restart deployment/mysite
```

Or wire up [Stakater Reloader](/operations/email#auto-rolling-on-secret-change-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](/operations/email#auto-rolling-on-secret-change-reloader)
and rotation is self-driving:

```bash theme={null}
# 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](/operations/admin-credential-rotation).
