ClassicPress Common — Shared Application Configuration
ClassicPress_Common is the shared application layer for ClassicPress. It is
not deployed on its own; instead it supplies the ClassicPress-specific configuration
that both ClassicPress_GKE and
ClassicPress_CloudRun build on, so the two platform
variants behave identically where it matters. End users never configure this layer
directly — it has no deployment UI inputs of its own — but understanding what it
provides explains the defaults you see in the platform docs.
For the infrastructure that actually provisions and runs ClassicPress, see the platform guides (ClassicPress_GKE, ClassicPress_CloudRun) and the foundation guides (App_GKE, App_CloudRun, App_Common).
1. What this layer provides
| Area | Provided by ClassicPress_Common | Where it surfaces |
|---|---|---|
| Cryptographic secret | Generates CLASSICPRESS_SALT_SEED (64-char random seed) and stores it in Secret Manager | Injected automatically; the entrypoint derives all 8 WordPress-style auth keys/salts from it |
| Container image | Wraps the official classicpress/classicpress image with a thin custom entrypoint script; builds via Cloud Build | container_image output of the platform deployment |
| Database engine | Fixes Cloud SQL for MySQL 8.0 as the only supported engine (database_type = "MYSQL_8_0" hardcoded in config) | §Database in the platform guides |
| Database bootstrap | Defines the first-deploy job (db-init) that creates the database and user and grants privileges | initialization_jobs output |
| Object storage | Declares the Cloud Storage classicpress-uploads bucket | storage_buckets output |
| Core settings | Sets the baseline ClassicPress environment: DB port, table prefix, charset, conditional Redis env vars | Application behaviour in the platform guides |
| Health checks | Supplies the default startup/liveness probe defaults (TCP port 80 / HTTP /) | §Observability in the platform guides |
2. Cryptographic secrets in Secret Manager
One secret is generated automatically and stored in Secret Manager — it is never set in plain text and must never be changed after the first deployment:
CLASSICPRESS_SALT_SEED— a 64-character random string (no special characters) generated byrandom_password.classicpress_salt_seed. ClassicPress (a WordPress 4.9.x fork) needs 8 secretAUTH_KEY/SALTphrases (AUTH_KEY,SECURE_AUTH_KEY,LOGGED_IN_KEY,NONCE_KEY, and their matching_SALTvalues). Rather than store 8 separate secrets,ClassicPress_Commonstores this single seed and the entrypoint derives all 8 values deterministically (sha256(seed-<key-name>)) on every container start. Because/var/www/html(and thuswp-config.php) is not guaranteed to persist across container/pod recreation on every platform, deriving from one stable, secret-backed seed keeps sessions and signed cookies consistent across restarts and across a multi-instance fleet without relying on the image's own random-salt generation. Rotating this seed after first boot invalidates every signed cookie and logged-in session for all users immediately.
The secret's Secret Manager ID uses a single underscore, not a double underscore
(CLASSICPRESS_SALT_SEED, exposed as the output key of the same name) — GKE's
SecretSync CRD rejects targetKey values containing __, so this name is safe on
both platforms.
The database password is generated and managed separately by the foundation; its
secret name is reported in the platform deployment outputs (database_password_secret).
See App_Common for the shared secret and Workload Identity model.
Retrieve the secret after deployment:
# List secrets for this deployment (names include the resource prefix):
gcloud secrets list --project "$PROJECT" --filter="name~salt-seed"
# Read the secret version:
gcloud secrets versions access latest --secret=<secret-name> --project "$PROJECT"
An orphan-cleanup submodule (cleanup_orphaned_secrets) runs ahead of secret
creation to remove any stale secret-<prefix>-<app>-salt-seed left behind by a
previous partial deployment before the new one is created.
3. Database engine and bootstrap
ClassicPress requires MySQL 8.0; ClassicPress_Common's config hardcodes
database_type = "MYSQL_8_0" directly — the engine is fixed regardless of what an
Application module's own database_type variable is set to. On the first
deployment a one-shot job (db-init, script scripts/db-init.sh, image
mysql:8.0-debian) runs and idempotently:
- Waits for the Cloud SQL Auth Proxy Unix socket under
/cloudsql(up to 30 seconds) ifenable_cloudsql_volume = true; otherwise falls back to TCP viaDB_IP(preferred) or a non-socketDB_HOST, - Confirms
DB_PASSWORDandROOT_PASSWORDare present (injected by the foundation) and errors out clearly if either is missing, - Waits for TCP connectivity to port 3306 when not using a socket,
- Detects whether the local
mysqlclient supports--get-server-public-key(needed for MySQL 8'scaching_sha2_passworddefault auth plugin over a plain, proxy-terminated TCP connection) and adds the flag when available, - Creates (or updates the password of) the application user with
CREATE USER IF NOT EXISTS/ALTER USER ... IDENTIFIED BY, - Creates the application database with
CREATE DATABASE IF NOT EXISTS, - Grants
ALL PRIVILEGESon the database to the application user and flushes privileges, - Verifies the application user can actually connect and run
SELECT 1— this both catches password/grant mismatches early and warms MySQL'scaching_sha2_passwordserver-side auth cache for subsequent PHP connections, - Signals the Cloud SQL Auth Proxy sidecar to shut down gracefully (
POST /quitquitquit, falling back toSIGKILLafter a 30-second wait) so the job exits0rather than being retried underrestartPolicy: OnFailure.
There is no separate migration/schema job. Once db-init has provisioned the
empty database and user, ClassicPress creates its own schema and admin account
through its own first-run web installer (/wp-admin/install.php) — the same
pattern WordPress itself uses. The job is safe to re-run
(execute_on_apply = true, max_retries = 3, timeout_seconds = 600).
Inspect the database directly with:
gcloud sql connect <instance-name> --user=<db-user> --database=<db-name> --project "$PROJECT"
The instance, database, and user names are in the platform deployment outputs.
4. Container image and entrypoint
The custom image is a thin build FROM classicpress/classicpress:${CLASSICPRESS_VERSION}
(the base tag defaults to php8.3-apache; upstream tags are PHP-qualified, not
version-qualified, and the Application modules resolve application_version = "latest" to this pinned default via an app-specific CLASSICPRESS_VERSION build
arg — the Foundation's generic APP_VERSION build arg would otherwise silently
overwrite a same-named arg with the literal, non-existent tag "latest"). The
Dockerfile grafts a shell entrypoint (entrypoint.sh, copied to
/usr/local/bin/cp-entrypoint.sh) ahead of the upstream docker-entrypoint.sh:
- Translates
DB_*/DB_IPtoCLASSICPRESS_DB_*— ClassicPress'swp-config-docker.phpreads discreteCLASSICPRESS_DB_HOST/CLASSICPRESS_DB_NAME/CLASSICPRESS_DB_USER/CLASSICPRESS_DB_PASSWORDvariables, not the Laravel-styleDB_USERNAMEconvention some other PHP apps in this repo use. The Foundation injects the genericDB_HOST,DB_IP,DB_NAME,DB_USER,DB_PASSWORD; the entrypoint buildsCLASSICPRESS_DB_HOSTfrom${DB_IP:-DB_HOST}: a leading/is treated as a Cloud SQL socket directory (the entrypoint searches it for a socket file and emits the mysqlilocalhost:<socket>form), an empty value is left for the image's own default to surface a clear error, and anything else is treated as a TCP host and suffixed with:${DB_PORT:-3306}. MySQL over the Cloud SQL private-IP range needs no SSL (unlike Postgres), so this single code path works unmodified on both Cloud Run (DB_IP= the instance private IP) and GKE (DB_HOSToverridden to127.0.0.1, the cloud-sql-proxy sidecar). - Derives the 8 auth keys/salts — when
CLASSICPRESS_SALT_SEEDis present, computesCLASSICPRESS_AUTH_KEY,CLASSICPRESS_SECURE_AUTH_KEY,CLASSICPRESS_LOGGED_IN_KEY,CLASSICPRESS_NONCE_KEY, and their_SALTcounterparts assha256(seed-<key-name>), so every restart and every instance in a fleet agrees on the same values (see §2). - Hands off to the upstream entrypoint —
exec docker-entrypoint.sh "$@", which materialiseswp-config.phpfrom theCLASSICPRESS_*env vars, copies the ClassicPress application into/var/www/htmlif that directory is empty, and startsapache2-foregroundas PID 1 (the Dockerfile'sCMD).
How persistence actually works — the NFS mount path IS the entrypoint's data
directory. ClassicPress_Common's Dockerfile and entrypoint.sh themselves
contain no explicit reference to a Filestore/NFS path — the only two things the
entrypoint does directly are the DB_* aliasing above and salt derivation.
Persistence instead comes from where each Application module's nfs_mount_path
points: both variants default it to /var/www/html/wp-content, the exact
subdirectory the upstream docker-entrypoint.sh reads and writes uploaded media,
installed plugins, and themes to. Upstream's first-boot copy logic explicitly skips
an existing wp-content directory rather than overwriting it, so mounting NFS
directly onto that path is what makes the mount effective, without this Common
layer needing any code of its own to wire it up:
- On Cloud Run, no PVC-equivalent exists, so the default
enable_nfs = truemounting Filestore at/var/www/html/wp-contentis the primary persistence mechanism: uploaded media and any plugins/themes installed via wp-admin survive scale-to-zero cold starts, redeploys, and instance replacement. Only the ClassicPress core files outsidewp-contentare freshly copied into/var/www/htmlon each cold start, which is expected since they ship with the image. See ClassicPress_CloudRun §3 and §6. - On GKE, the Application module (not this Common layer) additionally enables
stateful_pvc_enabled = true, which auto-selects aStatefulSetand mounts a per-pod block PVC atstateful_pvc_mount_path = /var/www/html— the whole directory the upstream entrypoint populates, giving the entire install (core files included) per-pod persistence. GKE'senable_nfs = truedefault still mounts Filestore at/var/www/html/wp-content, a subdirectory of that PVC; on a single-replica StatefulSet the PVC alone already coverswp-content, but the NFS mount is what would keepwp-contentdurable and shared ifstateful_pvc_enabledwere ever disabled or the workload scaled to multiple replicas — see ClassicPress_GKE §3.
In short: this layer's Dockerfile/entrypoint don't need to know about NFS directly
— persistence works because each Application module's nfs_mount_path default
lines up with the exact subdirectory (wp-content) that upstream's own copy-skip
logic treats specially. That alignment, not any code in ClassicPress_Common, is
what makes the mount effective on both platforms.
5. Core application settings
ClassicPress_Common establishes the baseline ClassicPress environment so the
application comes up correctly on first boot:
- Database port —
DB_PORT = "3306", read byentrypoint.shto buildCLASSICPRESS_DB_HOSTinhost:portform. - Table prefix —
CLASSICPRESS_TABLE_PREFIX = "cp_"(the image default). - Character set —
CLASSICPRESS_DB_CHARSET = "utf8mb4". - Redis (conditional) — when
var.enable_redis = true, merges inREDIS_HOST(either the explicitvar.redis_hostor a literal$(REDIS_HOST)reference for the platform to resolve) andREDIS_PORT = var.redis_port.enable_redisis forwarded unchanged from the Application module — it is never gated on whetherredis_hostis set, so the Foundation's own NFS-VM Redis injection still fires whenredis_hostis left empty. - MySQL plugins —
enable_mysql_plugins = false,mysql_plugins = []; no MySQL server-side plugins are installed by default.
Platform-specific adjustments made in the Application modules (not this Common
layer, but built on top of its config output):
- Cloud Run forces
enable_cloudsql_volume = falsein itsmain.tfmerge (private-IP TCP by default, overriding whateverClassicPress_Commonmight otherwise pass through), soentrypoint.sh's TCP branch (DB_IP:DB_PORT) is what normally executes. - GKE merges in
DB_HOST = "127.0.0.1"on top ofClassicPress_Common'sconfig.environment_variables, and forcesenable_cloudsql_volume = true— the cloud-sql-proxy sidecar listens on loopback, soentrypoint.sh's TCP branch runs against127.0.0.1:3306instead.
php_memory_limit, upload_max_filesize, and post_max_size are declared as
variables and forwarded into the module call, but ClassicPress_Common's
config.environment_variables sets no corresponding PHP_* / upload-limit env var
for the upstream image to read — they currently have no observed effect on the
deployed container.
6. Health probe behaviour
ClassicPress_Common's variables.tf supplies the probe defaults that both
Application modules forward straight through (neither variant overrides them):
- Startup probe — TCP on the container port (80),
initial_delay_seconds = 30,period_seconds = 15,timeout_seconds = 10, and a generousfailure_threshold = 20(roughly 5 minutes of retries after the initial delay) — enough time for the upstreamdocker-entrypoint.shto populate an empty/var/www/htmlon first boot of a fresh instance or pod. - Liveness probe — HTTP
GET /,initial_delay_seconds = 300,period_seconds = 60,timeout_seconds = 60,failure_threshold = 3. Both a200(an already-installed site) and a302redirect to the first-run installer (a fresh, uninstalled site) count as healthy.
There is no CloudRun-vs-GKE difference at this layer — both platform variants use
identical probe defaults from ClassicPress_Common. (The GKE variant's own docs
note the same TCP/HTTP defaults; no per-platform override exists in either
main.tf.)
7. Object storage
A dedicated Cloud Storage bucket with name suffix classicpress-uploads is
declared in outputs.tf (location = var.region, force_destroy = true) and
provisioned by the foundation, which also grants the workload service account
access. It is not wired in as a gcs_volumes mount by either Application
module by default — add an entry to gcs_volumes (e.g. mounted at
/var/www/html/wp-content/uploads) if you want it to actually back media uploads.
gcloud storage buckets list --project "$PROJECT" --filter="name~classicpress-uploads"
For the ClassicPress-specific, user-facing configuration (variables by group, outputs, and how to explore each service from the Console and CLI), see the platform guides: ClassicPress_GKE and ClassicPress_CloudRun.
Related guides
- ClassicPress on Google Cloud Run — this configuration deployed on Cloud Run.
- ClassicPress on GKE Autopilot — this configuration deployed on GKE.