SoConnective

Operations

Runbook 04 — Platform CMS deployment (Payload)

How apps/cms (Payload CMS) is deployed, how the platform database is wired, and the real gotchas hit along the way. Update this whenever the pipeline changes.

Architecture

push to Forgejo (main)  ──►  Coolify clones (HTTPS + token)  ──►  Nixpacks build (pnpm)


   Docker container  ◄── Traefik (HTTPS, Let's Encrypt) ── cms.soconnective.com


   Platform PostgreSQL (Coolify-managed, private)
      ├─ schema `payload`  → Payload's tables (auth, admin, collections)
      └─ schema `public`   → domain tables (organizations/leads/audit_log) with RLS

Components

ItemValue
Appapps/cms — Payload 3.85 + Next.js 15.4
Coolify appbase_directory /apps/cms, build pack nixpacks, port 3000
Domainhttps://cms.soconnective.com (Let's Encrypt)
Start commandpnpm payload migrate && pnpm start (migrations run on every deploy)
DatabaseCoolify-managed PostgreSQL, private (not publicly exposed)
Env (runtime)DATABASE_URI (internal URL), PAYLOAD_SECRET
Schema isolationPayload uses schemaName: 'payload'; domain stays in public

Secrets (platform DB credentials, PAYLOAD_SECRET, app uuid) live in the password manager / fs-secrets, never in git.

First-time provisioning

  1. Create the platform Postgres (Coolify) — private.
  2. Create the payload schema and the domain schema:
    • CREATE SCHEMA IF NOT EXISTS payload;
    • Apply packages/db SQL migrations + provision the app_user role (see ADR 0003).
  3. Generate and run Payload migrations: pnpm payload migrate:create init then pnpm payload migrate (inside the container, against the platform DB).
  4. Seed the first admin (Payload local API) and change the password immediately.

Gotchas (real problems and fixes)

  1. Coolify mangled the HTTPS git URL to SSH. Creating the app via the API stored git_repository as feedback/soconnective.git and the build failed with git ls-remote ... over ssh. Fix: PATCH git_repository to the full https://feedback:<TOKEN>@git.soconnective.com/feedback/soconnective.git.
  2. push: true does not run in production. Payload only pushes the schema in dev. Fix: use migrations in production (payload migrate in the start command).
  3. TLS / ERR_CERT_AUTHORITY_INVALID + HSTS. An old Plesk server (216.250.126.64) still answered for cms with an expired self-signed certificate. Authoritative DNS (IONOS) already points cms → VPS (216.250.119.216); the error was stale DNS cache reaching the old server. Fix: flush DNS / lower TTL / retire the old Plesk server. After DNS pointed correctly, restarting the app made Traefik issue the Let's Encrypt certificate.
Previous
Runbook 03 — Deployment (Coolify) and the web pipeline