Skip to main content

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

AreaProvided by ClassicPress_CommonWhere it surfaces
Cryptographic secretGenerates CLASSICPRESS_SALT_SEED (64-char random seed) and stores it in Secret ManagerInjected automatically; the entrypoint derives all 8 WordPress-style auth keys/salts from it
Container imageWraps the official classicpress/classicpress image with a thin custom entrypoint script; builds via Cloud Buildcontainer_image output of the platform deployment
Database engineFixes 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 bootstrapDefines the first-deploy job (db-init) that creates the database and user and grants privilegesinitialization_jobs output
Object storageDeclares the Cloud Storage classicpress-uploads bucketstorage_buckets output
Core settingsSets the baseline ClassicPress environment: DB port, table prefix, charset, conditional Redis env varsApplication behaviour in the platform guides
Health checksSupplies 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 by random_password.classicpress_salt_seed. ClassicPress (a WordPress 4.9.x fork) needs 8 secret AUTH_KEY/SALT phrases (AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY, and their matching _SALT values). Rather than store 8 separate secrets, ClassicPress_Common stores this single seed and the entrypoint derives all 8 values deterministically (sha256(seed-<key-name>)) on every container start. Because /var/www/html (and thus wp-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:

  1. Waits for the Cloud SQL Auth Proxy Unix socket under /cloudsql (up to 30 seconds) if enable_cloudsql_volume = true; otherwise falls back to TCP via DB_IP (preferred) or a non-socket DB_HOST,
  2. Confirms DB_PASSWORD and ROOT_PASSWORD are present (injected by the foundation) and errors out clearly if either is missing,
  3. Waits for TCP connectivity to port 3306 when not using a socket,
  4. Detects whether the local mysql client supports --get-server-public-key (needed for MySQL 8's caching_sha2_password default auth plugin over a plain, proxy-terminated TCP connection) and adds the flag when available,
  5. Creates (or updates the password of) the application user with CREATE USER IF NOT EXISTS / ALTER USER ... IDENTIFIED BY,
  6. Creates the application database with CREATE DATABASE IF NOT EXISTS,
  7. Grants ALL PRIVILEGES on the database to the application user and flushes privileges,
  8. Verifies the application user can actually connect and run SELECT 1 — this both catches password/grant mismatches early and warms MySQL's caching_sha2_password server-side auth cache for subsequent PHP connections,
  9. Signals the Cloud SQL Auth Proxy sidecar to shut down gracefully (POST /quitquitquit, falling back to SIGKILL after a 30-second wait) so the job exits 0 rather than being retried under restartPolicy: 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_IP to CLASSICPRESS_DB_* — ClassicPress's wp-config-docker.php reads discrete CLASSICPRESS_DB_HOST / CLASSICPRESS_DB_NAME / CLASSICPRESS_DB_USER / CLASSICPRESS_DB_PASSWORD variables, not the Laravel-style DB_USERNAME convention some other PHP apps in this repo use. The Foundation injects the generic DB_HOST, DB_IP, DB_NAME, DB_USER, DB_PASSWORD; the entrypoint builds CLASSICPRESS_DB_HOST from ${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 mysqli localhost:<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_HOST overridden to 127.0.0.1, the cloud-sql-proxy sidecar).
  • Derives the 8 auth keys/salts — when CLASSICPRESS_SALT_SEED is present, computes CLASSICPRESS_AUTH_KEY, CLASSICPRESS_SECURE_AUTH_KEY, CLASSICPRESS_LOGGED_IN_KEY, CLASSICPRESS_NONCE_KEY, and their _SALT counterparts as sha256(seed-<key-name>), so every restart and every instance in a fleet agrees on the same values (see §2).
  • Hands off to the upstream entrypointexec docker-entrypoint.sh "$@", which materialises wp-config.php from the CLASSICPRESS_* env vars, copies the ClassicPress application into /var/www/html if that directory is empty, and starts apache2-foreground as PID 1 (the Dockerfile's CMD).

Known limitation — the entrypoint never touches persistent storage. ClassicPress_Common's Dockerfile and entrypoint.sh contain no reference to /var/lib/classicpress, Filestore/NFS, or any Foundation-level NFS mount path — confirmed by direct inspection of both files. The only two things the entrypoint does are the DB_* aliasing above and salt derivation; it neither reads from nor writes to any mount the Foundation might attach. Whether that matters depends entirely on what the Foundation mounts over /var/www/html (the directory the upstream docker-entrypoint.sh actually populates with the app, wp-content, and uploads):

  • On Cloud Run, no PVC-equivalent exists and the default enable_nfs = true mounts Filestore at /var/lib/classicpress — a path this layer never reads or writes. /var/www/html is therefore left on the container's own ephemeral filesystem, and with min_instance_count = 0 (the Cloud Run variant's default), every cold-started instance starts from an empty /var/www/html: uploaded media and any plugins/themes installed via wp-admin on a prior instance are lost, even though the Cloud SQL database (posts, pages, settings, and media metadata rows) is durable. This is confirmed, not theoretical — see ClassicPress_CloudRun §3 and §6.
  • On GKE, the Application module (not this Common layer) separately enables stateful_pvc_enabled = true, which auto-selects a StatefulSet and mounts a block PVC at stateful_pvc_mount_path = /var/www/html — the exact directory the upstream entrypoint populates. That PVC is what actually persists the install across pod restarts on GKE; it is unrelated to, and not provided by, this Common module. GKE's own enable_nfs = true default still mounts Filestore at /var/lib/classicpress, which remains unused by the entrypoint/Dockerfile for the same reason as on Cloud Run — see ClassicPress_GKE §3.

In short: this layer's NFS-unawareness is real and identical on both platforms: it never wires /var/lib/classicpress (or any NFS mount) into where ClassicPress actually stores its install. It is masked on GKE only because that variant's Foundation-level StatefulSet PVC happens to be mounted at the correct path (/var/www/html), not because ClassicPress_Common fixed anything.


5. Core application settings

ClassicPress_Common establishes the baseline ClassicPress environment so the application comes up correctly on first boot:

  • Database portDB_PORT = "3306", read by entrypoint.sh to build CLASSICPRESS_DB_HOST in host:port form.
  • Table prefixCLASSICPRESS_TABLE_PREFIX = "cp_" (the image default).
  • Character setCLASSICPRESS_DB_CHARSET = "utf8mb4".
  • Redis (conditional) — when var.enable_redis = true, merges in REDIS_HOST (either the explicit var.redis_host or a literal $(REDIS_HOST) reference for the platform to resolve) and REDIS_PORT = var.redis_port. enable_redis is forwarded unchanged from the Application module — it is never gated on whether redis_host is set, so the Foundation's own NFS-VM Redis injection still fires when redis_host is left empty.
  • MySQL pluginsenable_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 = false in its main.tf merge (private-IP TCP by default, overriding whatever ClassicPress_Common might otherwise pass through), so entrypoint.sh's TCP branch (DB_IP:DB_PORT) is what normally executes.
  • GKE merges in DB_HOST = "127.0.0.1" on top of ClassicPress_Common's config.environment_variables, and forces enable_cloudsql_volume = true — the cloud-sql-proxy sidecar listens on loopback, so entrypoint.sh's TCP branch runs against 127.0.0.1:3306 instead.

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 probeTCP on the container port (80), initial_delay_seconds = 30, period_seconds = 15, timeout_seconds = 10, and a generous failure_threshold = 20 (roughly 5 minutes of retries after the initial delay) — enough time for the upstream docker-entrypoint.sh to populate an empty /var/www/html on first boot of a fresh instance or pod.
  • Liveness probeHTTP GET /, initial_delay_seconds = 300, period_seconds = 60, timeout_seconds = 60, failure_threshold = 3. Both a 200 (an already-installed site) and a 302 redirect 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.