1f695d3932
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.3 KiB
4.3 KiB
DOCKER_SPEC.md
Container strategy. Descriptive spec — actual Dockerfiles/compose files are Phase 3 artifacts.
1. Images
site (production)
- Multi-stage build: stage 1
node:22-alpine+ pnpm → install (frozen lockfile) →astro build→dist/; stage 2nginx:alpine(digest-pinned) copyingdist/+ a site-local nginx config. - Site-local nginx config responsibilities: gzip/brotli precompressed asset serving, cache headers (immutable hashed assets / no-cache HTML per ARCHITECTURE §5), security headers + CSP (ARCHITECTURE §6), trailing-slash 301s,
/Linkedin301, legacy-WP 410 map, custom 404 page wiring. - Runs as non-root (nginx unprivileged image), read-only filesystem, no writable volumes, port 8080 internal.
- Image contains zero secrets and zero build tooling — final image is nginx + static files, ~25 MB.
contact-relay (production)
- Multi-stage: .NET 9 SDK build →
mcraspnet runtime-deps/alpine final; non-root, read-only FS, listens 8081 internal. - Config via environment only:
Smtp__Host/Port/User/Password (secret),Relay__ToAddress,Relay__RateLimit*,Relay__AllowedOrigin(site origin for CORS/Origincheck). - Healthcheck endpoint
/healthz(no auth, no info leakage) wired to compose healthcheck.
site-dev (development)
node:22dev container runningastro devwith HMR;site/bind-mounted,node_modulesin a named volume (Windows-host performance), port 4321 published.- Relay runs alongside via
dotnet watchin an SDK-image dev container (or directly on host — both supported); Astro dev proxy forwards/api/contact→ relay so the form works identically in dev. - Purpose: parity with prod behaviour (headers/proxying approximated) while keeping the fast inner loop; contributors need only Docker, not local Node/.NET installs.
2. Compose topology
deploy/
docker-compose.yml # prod: site + contact-relay, internal network,
# exposed only to the host reverse-proxy network
docker-compose.dev.yml # dev: site-dev + relay-dev
.env.example # documented variables, no real values
- Prod compose joins the server's existing reverse-proxy network; the host nginx (existing, TLS-terminating) routes
cesnimda.co.uk→site:8080andcesnimda.co.uk/api/contact→contact-relay:8081. The stack publishes no host ports itself. restart: unless-stopped, log rotation via compose logging options, images referenced by registry tag + digest.- Optional third service slot (
analytics, self-hosted Umami/Plausible) reserved in the compose file as a commented profile — decision deferred.
3. Environment variables
| Var | Service | Notes |
|---|---|---|
SMTP_HOST/PORT/USER |
relay | His mail provider's submission endpoint |
SMTP_PASSWORD |
relay | Secret — provided via env file with 600 perms on server (or Docker secret); never in git, never in image |
RELAY_TO |
relay | Destination inbox |
RELAY_ALLOWED_ORIGIN |
relay | https://cesnimda.co.uk |
RELAY_RATE_* |
relay | Requests/window per IP |
PUBLIC_SITE_URL |
site (build-time) | Canonical origin for sitemap/OG absolute URLs |
Build-time vs runtime separation is explicit: the static site consumes variables only at build; the relay only at runtime. .env.example documents every variable (pattern Connor already uses in both projects).
4. Build pipeline approach (interface to DEPLOYMENT_SPEC)
- CI builds both images on Gitea Actions runners, tags
:<git-sha>+:latest, pushes to Gitea's built-in container registry (git.cesnimda.uk — keeps the whole chain self-hosted). - The
siteimage build runs the full quality gate first (typecheck, unit, Playwright against a preview build, Lighthouse budgets) — an image only exists if gates passed. - Reproducibility: pnpm frozen lockfile, pinned base images, no network fetches during
astro buildbeyond npm (fonts vendored in repo).
5. Local ops conventions
make-style task runner or npm scripts wrapping compose invocations (dev,build,test,deploy) so every workflow is one documented command — README-driven, same convention as JobTrack/InboxIntel repos.- Backups: nothing stateful to back up (site is git + registry; relay is stateless) — the deliberate architectural win; documented so nobody adds state casually.