Payload Common — Shared Application Configuration
Payload_Common is the shared application layer for Payload CMS. It is
not deployed on its own; instead it supplies the Payload-specific configuration that both
Payload_GKE and
Payload_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 Payload, see the platform guides (Payload_GKE, Payload_CloudRun) and the foundation guides (App_GKE, App_CloudRun, App_Common).
1. What this layer provides
| Area | Provided by Payload_Common | Where it surfaces |
|---|---|---|
| Secret | Generates PAYLOAD_SECRET (32-character random string) and stores it in Secret Manager | Injected automatically; retrieve via Secret Manager (see below) |
| Container image | Builds a real Payload starter app from source via Cloud Build — there is no upstream image to wrap | container_image output of the platform deployment |
| Database engine | Fixes Cloud SQL for PostgreSQL 15 as the only supported engine | §Database in the platform guides |
| Database bootstrap | Defines two sequential first-deploy jobs: db-init (role/database creation) and payload-migrate (schema migration via the payload CLI) | initialization_jobs output |
| Object storage | None — storage_buckets output is a static empty list | N/A |
| Core settings | Sets container port (3000), database name/user, resource limits | Application behaviour in the platform guides |
| Health checks | Supplies the default startup/liveness probe targeting /admin | §Observability in the platform guides |
2. Container image: building from source, not wrapping a vendor image
Payload has no official Docker image upstream — confirmed against Payload's own
documentation, which ships only example Dockerfiles meant to be adapted into a real project.
Every other application layer in this catalogue wraps a pullable vendor image; Payload_Common
instead builds a real, locally-verified application from source
(image_source = "custom", container_image = ""):
scripts/is a genuine, buildable Next.js/Payload application — a blank template scaffolded with the officialcreate-payload-appCLI, configured with the PostgreSQL adapter (@payloadcms/db-postgres) instead of Payload's default MongoDB adapter, to match this catalogue's standard Cloud SQL Postgres provisioning. It ships Next.js 16, Payload 3.86.0, React 19, on Node 22.scripts/Dockerfileis a multi-stage build (deps→builder→runner) that fixes two real bugs found in the official template/Dockerfile while building this module:- The generated
next.config.tsdoes not setoutput: 'standalone'by default, despite the official Dockerfile assuming the standalone build output exists. npm cifails with lockfile-consistency errors when the lockfile was generated on macOS but the build runs on Linux/alpine — the Dockerfile usesnpm installinstead. A third fix: the blank template ships nopublic/directory, but the runner stage'sCOPY --from=builder /app/publicunconditionally expects one — the builder stage runsmkdir -p publicfirst.
- The generated
- The runner stage keeps two copies of the application: the trimmed Next.js standalone
output (enough to serve HTTP traffic) and a full second copy of
node_modules+src/under/app/cli, used exclusively by thepayload-migrateinit job, which needs the Payload CLI and its TypeScript loader (tsx) — neither of which the standalone trace includes.
Inspect recent builds:
gcloud builds list --project "$PROJECT" --limit=10
gcloud builds log <build-id> --project "$PROJECT"
3. Secret in Secret Manager
One secret is generated automatically and stored in Secret Manager — it is never set in plain text and should not be rotated casually:
PAYLOAD_SECRET— a 32-character random alphanumeric string. Payload reads it asprocess.env.PAYLOAD_SECRETinpayload.config.tsto sign its own session/auth tokens. Rotating it after first boot invalidates every active session, forcing all users to log back in.
Retrieve the secret after deployment:
# List the secret for this deployment (name includes the resource prefix):
gcloud secrets list --project "$PROJECT" --filter="name~-secret"
# Read the secret version:
gcloud secrets versions access latest --secret=<secret-name> --project "$PROJECT"
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.
A 30-second time_sleep resource ensures Secret Manager replication completes before the service
or pods attempt to read PAYLOAD_SECRET.
4. Database engine and bootstrap
Payload requires PostgreSQL; the engine is fixed to POSTGRES_15. Unlike many apps in this
catalogue, Payload's Postgres adapter does not create its schema on boot in production —
confirmed locally: booting the built server against a fresh, empty database created zero tables.
Schema only appears via payload migrate, which needs an already-generated migration file baked
into the image (src/migrations/*.ts, generated once via payload migrate:create and
committed).
Two sequential jobs run by default:
db-init(postgres:15-alpine) — connects through the Cloud SQL Auth Proxy Unix socket and idempotently creates the application role and database.payload-migrate(depends_on_jobs = ["db-init"], uses the deployed application image itself) — runs./node_modules/.bin/payload migratefrom the/app/clicopy of the full dependency tree and source. After migrations complete, it signals the Cloud SQL Auth Proxy sidecar to shut down (quitquitquiton Cloud Run,http://localhost:9091/quitquitquiton GKE's native sidecar).
The job is safe to re-run. 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.
5. Health probe behaviour
The default probes target /admin — Payload's own admin UI route, which returns an
unauthenticated 200 (it serves the login/first-user-creation form) once the Node.js server and
database connection are ready. Payload's actual REST/GraphQL API routes require auth and would
401/403 a probe; root / also returns 200 but is app-content-dependent, making /admin the
more stable choice.
- Cloud Run startup probe: HTTP
/admin, 20s initial delay, 10s period, 10 retries (defaultPayload_CloudRunvalues — see its Configuration Guide for the exact numbers in force). - GKE startup probe: HTTP
/admin, 30s initial delay (defaultPayload_GKEvalues).
Allow several minutes on first boot for the payload-migrate job to complete schema setup before
the service is expected to serve real content.
6. Object storage
Payload_Common provisions no storage bucket. The storage_buckets output is a static empty
list regardless of any Application Module input, so uploaded media in the bundled starter app
goes to local container disk and is not persisted across a pod restart or redeploy:
gcloud storage buckets list --project "$PROJECT" # no Payload-specific bucket will be listed
To make media uploads durable, add a storage adapter (e.g. an S3-compatible adapter pointed at a
GCS bucket) to the application in scripts/.
For the Payload-specific, user-facing configuration (variables by group, outputs, and how to explore each service from the Console and CLI), see the platform guides: Payload_GKE and Payload_CloudRun.