Local CMS Development Stack
The supported local runtime is a Docker Compose stack with the CMS web app, PostgreSQL with pgvector, pgAdmin, and the RAG and presentation workers. External S3, AI, email, billing, Python, and Unity services remain outside this stack.
Prerequisites
- Docker Desktop with Docker Compose v2
- enough Docker resources for the Node development server and PostgreSQL
- repository provider configuration in the ignored
.envfile when exercising flows that call external services
Configure
Create the ignored Compose environment file:
Replace every CHANGE_ME value with a strong machine-local value. Do not commit
.env.compose. The file supplies Postgres and pgAdmin credentials plus local
runtime overrides. The optional ignored .env continues to supply provider,
storage, email, and billing configuration.
Inside Compose, web connects to postgres:5432. Host-run tools use
127.0.0.1:5432. A service running on the Docker Desktop host must be addressed
from the web container as host.docker.internal, not localhost.
Start and inspect
pnpm stack:up runs docker compose up --build --wait; it builds the web image,
starts all five services, and waits for the health-gated services. The RAG and
presentation workers start only after web is healthy, use the internal
postgres:5432 connection, and keep Payload schema push disabled.
The web launcher waits for PostgreSQL and inspects the machine-local Payload
schema before starting Next.js. With PAYLOAD_DB_PUSH=1, it keeps schema push
enabled for an empty first boot and disables it when the Payload tables already
exist, making routine restarts safe without changing hosted-database policy.
Endpoints:
- CMS and Payload admin:
http://127.0.0.1:3000andhttp://127.0.0.1:3000/admin - PostgreSQL:
127.0.0.1:5432 - pgAdmin:
http://127.0.0.1:5050
pgAdmin has a preconfigured postgres:5432 server entry. It does not persist a
database password in the tracked server definition.
The queue workers run as rag-worker and presentation-worker. Inspect or
follow their logs independently:
docker compose --env-file .env.compose logs --tail 200 rag-worker presentation-worker
docker compose --env-file .env.compose logs --follow rag-worker presentation-worker
Lifecycle and persistence
Routine restart and stack:down preserve the Postgres, pgAdmin, dependency, and
Next.js cache named volumes. Do not add --volumes to normal stop commands.
The destructive reset is a separate guarded command. Its default mode prints the exact project and volume targets without changing them:
After reviewing that plan and obtaining approval, execution requires the exact project confirmation:
This deletes only the named volumes in the printed local Compose project. It is not a database rollback and must never be used against an unverified project.
Pinned baselines
The tracked definitions use immutable multi-architecture digests resolved from the official Docker Hub tag API on 2026-07-21.
| Runtime | Pinned reference | Multi-architecture digest | Linux amd64 child digest |
|---|---|---|---|
| Web base | node:22.5.1-bookworm-slim |
sha256:2fb92fe9d7350866a73c5cc311c1a19919ffd47e8592d4233374ee330e3bdb1e |
sha256:ec92a9eaa01e8e098dc7da1c884a3d2a0f10c66b6dac6b1ca054cc5908442ab3 |
| Postgres and pgvector | pgvector/pgvector:0.8.2-pg17-bookworm |
sha256:feb68f4f15446397d8cac7f4fe48fe4586de83160d1fc48b46283312d1a33966 |
sha256:e04af45eb526378554a24ed05b37d9ea56fd623feca9adf264d4f47d875c9a93 |
| pgAdmin | dpage/pgadmin4:9.16 |
sha256:40fa840c5bb7c8463957f1255b01283732c2d8c9396a956d180f8e6c296753b3 |
sha256:66a300a7ecdcc1f325af0c430315329bca46cd4a7067227d6899802238167c6e |
The required running versions are PostgreSQL 17.10, pgvector 0.8.2, pgAdmin 9.16, and Node 22.5.1. Verify them from the running containers rather than inferring them only from tags:
docker compose --env-file .env.compose exec -T postgres postgres --version
docker compose --env-file .env.compose exec -T postgres sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "SELECT extversion FROM pg_extension WHERE extname = ''vector'';"'
docker compose --env-file .env.compose exec -T pgadmin /venv/bin/python -c "import config; print(config.APP_VERSION)"
docker compose --env-file .env.compose exec -T web node --version
Application smoke
The smoke initializes Payload and Mastra storage, performs a deterministic three-dimensional pgvector insert/query/delete cycle, and calls the authenticated worker jobs endpoint. It disables schema push in its separate process, deletes its temporary vector index, closes owned storage pools, and exits on completion.
Database migration and cutover runbook
ANU-7 completed the first accepted Supabase-to-local cutover and hardened the
migration tooling for repeatable recovery or refreshes. The Supabase CLI is
pinned as a project dependency. Commands default to non-connecting plan mode,
read connection strings only from named environment variables, and allow
artifacts only below ignored .local-data/database/<run-id> storage.
Use pnpm local-stack:db:env for source-only operations loaded from the ignored
.env. Use pnpm local-stack:db:migrate when a command needs both the source
and a loopback target derived from .env.compose. Set ANUVA_LOCAL_DATABASE
to select a fresh target database without changing tracked configuration. The
create-target guard derives its administrative connection by replacing that
not-yet-created database name with the always-present local postgres database;
host, port, user, password, and query parameters remain unchanged.
pnpm local-stack:db:env -- inventory --database-env DATABASE_URI --artifact-dir .local-data/database/<inventory-run> --label source
pnpm local-stack:db:env -- export --source-env DATABASE_URI --artifact-dir .local-data/database/<export-run> --schemas public
pnpm local-stack:db -- verify --artifact-dir .local-data/database/<export-run>
pnpm local-stack:db -- prepare --artifact-dir .local-data/database/<export-run>
$env:ANUVA_LOCAL_DATABASE = '<fresh-database>'
pnpm local-stack:db:migrate -- create-target --maintenance-env LOCAL_DATABASE_URI --database <fresh-database> --confirm-create <fresh-database>
pnpm local-stack:db:migrate -- restore --target-env LOCAL_DATABASE_URI --artifact-dir .local-data/database/<export-run> --confirm-target 127.0.0.1:5432/<fresh-database>
pnpm local-stack:db:migrate -- parity --source-env DATABASE_URI --target-env LOCAL_DATABASE_URI --artifact-dir .local-data/database/<parity-run>
pnpm local-stack:db:migrate -- probe --target-env LOCAL_DATABASE_URI --artifact-dir .local-data/database/<probe-run>
Review each plan before repeating it with --dry-run or --execute as the
operation requires. The accepted sequence is:
- Run source inventory inside a read-only transaction. Confirm that only reviewed application schemas will be exported.
- Run the Supabase CLI dump dry run, then export schema and data. Do not export hosted roles unless a new review explicitly requires them.
- Verify the SHA-256 manifest and prove every artifact is ignored and absent from Git.
- Run
prepareto createschema.local.sql. It removes only hosted ownership, grants, revokes, default privileges, and session authorization; it refuses managed Supabase schema data or residual hosted-role references. - Create a fresh loopback target. Never restore over the accepted local database by default. Restore re-verifies checksums, passes dump paths as positional arguments rather than shell source, and uses one transaction.
- Require semantic parity and the targeted workspace, Payload, Mastra, RAG,
queue, and vector probe before changing
.env.composePOSTGRES_DB. The parity command writes its ignored report and exits nonzero on any mismatch. - Recreate the Compose services with
pnpm stack:up, then run migration status,pnpm stack:verify, HTTP/admin/login probes, worker startup probes, restart, and routine down/up persistence checks.
The align-target operation exists only for an empty target created with
pgvector in a non-public schema. It refuses non-empty databases. New targets
install pgvector under public, matching the portable Supabase CLI dump.
Recovery
- Inventory, parity, and probe SQL run read-only. Export reads the source; it never writes to it.
- A failed restore is rolled back as one transaction. Inspect the ignored restore log, correct only the reviewed compatibility difference, and retry against the still-empty target.
pnpm stack:downpreserves local state for diagnosis.- Inspect
pnpm stack:logsand the ignored database run directory before any clean retry. - Keep the original Supabase URI unchanged in ignored
.env. To roll back, stop local application writes, run the CMS from.envwithPAYLOAD_DB_PUSH=0, verifypnpm payload migrate:status, and leave the local Compose volume intact. Return forward by starting Compose with the accepted localPOSTGRES_DB. - Never use broad Docker cleanup commands as recovery for this stack.