# 05 — Compose stack & Caddy ## Goal Produce the production deploy artifacts: Dockerfile, `deploy/compose.yaml`, Caddy snippet, `.env.production.example`. ## Depends on - 02 (so `output: "standalone"` exists) - 03 (so DB service is defined) ## Scope ### Dockerfile (multi-stage) - Stage 1: `node:22-alpine` + pnpm, install deps, build (`pnpm build`). - Stage 2: `node:22-alpine` runtime, copy `.next/standalone`, `.next/static`, `public/`. Run as non-root. `CMD ["node", "server.js"]`. ### `deploy/compose.yaml` Services: - `famapp` (built from Dockerfile) - `famapp-db` (postgres:16, volume `famapp_db_data`) - `authentik-server`, `authentik-worker`, `authentik-db` (postgres:16, volume `authentik_db_data`), `authentik-redis` — leave fully configured but task 06 will tune env All services on a `famapp_net` network. famapp depends on famapp-db. No host port exposure for the DBs/redis. ### Caddy snippet (`deploy/Caddyfile.snippet`) ``` fam.ginnoir.com { reverse_proxy famapp:3000 } auth.ginnoir.com { reverse_proxy authentik-server:9000 } ``` Comment at the top: "Include from main Caddyfile or paste into the existing one." ### `.env.production.example` All vars needed by the compose stack, with comments explaining each. ## Out of scope - Actually running the stack against ginnoir.com (Matt does that). - Backups, log shipping (later tasks). ## Acceptance criteria - [ ] `docker build -t famapp .` succeeds. - [ ] `docker compose -f deploy/compose.yaml config` validates without errors. - [ ] famapp container starts against famapp-db when given a populated `.env`. - [ ] Image size under ~300 MB. - [ ] No secrets committed; only `.env.production.example`. ## Notes - Use `pnpm fetch` + `pnpm install --offline` in the build stage for cache locality. - `output: "standalone"` (set in task 02) means we copy `.next/standalone/server.js`, not run `next start`.