# ============================================================================= # certctl DEMO overlay — Bundle 2 (2026-05-12) # ============================================================================= # # Layered on top of the production-shaped base (docker-compose.yml) to give # operators a one-command, zero-config demo path: # # deploy/demo-up.sh -d --build # # (which forwards args to `docker compose up` after exporting the fresh # CERTCTL_DEMO_MODE_ACK_TS that Phase 2 SEC-H3 requires). Equivalent # manual invocation: # # CERTCTL_DEMO_MODE_ACK_TS=$(date +%s) docker compose \ # -f deploy/docker-compose.yml \ # -f deploy/docker-compose.demo.yml up -d --build # # What this overlay does: # # 1. Flips CERTCTL_AUTH_TYPE=none + CERTCTL_DEMO_MODE_ACK=true. Every # request is served as the synthetic admin actor `actor-demo-anon`; # the server emits a prominent ⚠ DEMO MODE WARN banner at boot with # a production-promotion checklist (cmd/server/main.go::emitDemoBanner). # Phase 2 SEC-H3 (2026-05-13) pairs DEMO_MODE_ACK with a required # DEMO_MODE_ACK_TS within the last 24h. The overlay reads # ${CERTCTL_DEMO_MODE_ACK_TS:-} from the shell — use deploy/demo-up.sh # (which exports a fresh TS) instead of bare `docker compose up`. # # 2. Flips CERTCTL_KEYGEN_MODE=server (the demo issues + holds the key on # the server to keep the dashboard populated; production deploys must # use the default `agent` mode where keys never leave the agent box). # # 3. Flips CERTCTL_DEMO_SEED=true. The server applies migrations/seed_demo.sql # at boot via postgres.RunDemoSeed AFTER baseline migrations + seed.sql, # pre-seeding 180 days of simulated history across 13 issuers + 8 agents. # # 4. Supplies the change-me-... placeholder values for POSTGRES_PASSWORD, # CERTCTL_API_KEY, CERTCTL_CONFIG_ENCRYPTION_KEY, and CERTCTL_AGENT_ID # so the demo runs without a deploy/.env file. The Bundle 2 fail-closed # Validate() rejects these placeholders outside demo mode, so this only # works alongside DEMO_MODE_ACK=true. # # U-3 history: pre-U-3 this overlay mounted seed_demo.sql into postgres # `/docker-entrypoint-initdb.d/`. That worked only because the production # stack also mounted the migrations there. Once U-3 dropped the production # initdb mounts (single source of truth: server runs RunMigrations + RunSeed # at boot), the demo seed could no longer be applied at initdb time — the # tables it references wouldn't exist yet. Post-U-3 the overlay just sets # CERTCTL_DEMO_SEED=true; the server applies seed_demo.sql at boot via # postgres.RunDemoSeed AFTER baseline migrations + seed.sql. # # Bundle 2 history: pre-Bundle-2 the base compose IS this demo path; this # overlay was a single-flag thin shim. Bundle 2 split the demo env vars # out of the base so `docker compose -f deploy/docker-compose.yml up` # (no overlay) boots production-shaped — which is what every operator # reading the README quickstart line "drop the demo overlay for a clean # install" expected. The overlay carries the full demo posture now. # # To start fresh (wipe previous data): # docker compose -f deploy/docker-compose.yml \ # -f deploy/docker-compose.demo.yml down -v # deploy/demo-up.sh -d --build services: postgres: # Fixed weak password is intentional for the no-setup demo path. # See docker-compose.yml for the production override pattern. environment: POSTGRES_PASSWORD: certctl certctl-server: environment: # Demo-mode auth: every request served as the synthetic # `actor-demo-anon` admin. The server's HIGH-12 startup guard # requires DEMO_MODE_ACK=true to allow this combination on a # non-loopback bind; the boot-time WARN banner (cmd/server/main.go) # reminds the operator on every start. CERTCTL_AUTH_TYPE: none CERTCTL_DEMO_MODE_ACK: "true" # Phase 2 SEC-H3 (2026-05-13): DEMO_MODE_ACK=true requires a fresh # DEMO_MODE_ACK_TS within the last 24h. The overlay can't hardcode # a timestamp (it would rot the next day), so we passthrough from # the shell. Operators set this via: # CERTCTL_DEMO_MODE_ACK_TS=$(date +%s) docker compose \ # -f docker-compose.yml -f docker-compose.demo.yml up -d # The cold-DB smoke + any helper script (deploy/demo-up.sh, when # it lands) export this before invoking compose. Empty value # fails the SEC-H3 guard with a clear operator-facing error # message pointing at this line. CERTCTL_DEMO_MODE_ACK_TS: "${CERTCTL_DEMO_MODE_ACK_TS:-}" # Server-side keygen so the demo can populate the dashboard with # full lifecycle history. Production deploys leave this at the # code default `agent` (CertctlAgent generates ECDSA P-256 keys # locally and submits CSRs only). CERTCTL_KEYGEN_MODE: server # Demo creds — the Bundle 2 fail-closed Validate() rejects these # sentinels outside demo mode, but DEMO_MODE_ACK=true unlocks them. CERTCTL_CONFIG_ENCRYPTION_KEY: change-me-32-char-encryption-key CERTCTL_AUTH_SECRET: change-me-in-production # Cold-DB smoke fix (2026-05-13): the base compose builds the # database URL via compose-level `${POSTGRES_PASSWORD}` interpolation # (deploy/docker-compose.yml line ~177), which reads the SHELL env — # NOT the postgres service's `environment:` block above (that one # feeds the postgres container's initdb only). In a zero-env-var # CI run the shell var is blank, producing # `postgres://certctl:@postgres:5432/...` and a SCRAM rejection # against a database that initdb seeded with password `certctl`. # Pinning the full URL here closes the gap: the demo overlay is # now fully self-sufficient (matches the file's docstring claim) # and the cold-DB smoke passes against a fresh GitHub-runner clone # with no .env file or exported shell vars. Production deploys # override CERTCTL_DATABASE_URL via the base compose's # `${CERTCTL_DATABASE_URL:-...}` default, so this literal is # overlay-scoped and never leaks into a production posture. CERTCTL_DATABASE_URL: postgres://certctl:certctl@postgres:5432/certctl?sslmode=disable # 180-day simulated history seed applied at boot. CERTCTL_DEMO_SEED: "true" certctl-agent: environment: # Pre-seeded by migrations/seed_demo.sql; the bundled agent # connects with these creds and the demo-mode synthetic admin # accepts every request regardless of API key. CERTCTL_API_KEY: change-me-in-production CERTCTL_AGENT_ID: agent-demo-1