bfa8cfd357
- rewrote homelab (content + topology diagram + stack) from a live inspection of the host: Ubuntu 24.04, ~30 Docker services behind Traefik (Cloudflare-fronted, TLS, HTTP/3), Authentik SSO forward-auth, CrowdSec, Pi-hole, self-hosted Gitea + CI runner, socket-proxy, per-app network isolation; WordPress framed as being decommissioned (not future arch) - deploy: docker-compose now uses Traefik labels + traefik_proxy network (was assumed nginx edge); .env.example adds SITE_HOST/TRAEFIK_ENTRYPOINT; colophon + ARCHITECTURE/DOCKER/ DEPLOYMENT specs corrected nginx-edge -> Traefik (site container still serves via nginx) - PROJECT_STATUS: pre-launch checklist updated; infra section added Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
61 lines
4.4 KiB
Markdown
61 lines
4.4 KiB
Markdown
# 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 2 `nginx:alpine` (digest-pinned) copying `dist/` + 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, `/Linkedin` 301, 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 → `mcr` aspnet 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/`Origin` check).
|
|
- Healthcheck endpoint `/healthz` (no auth, no info leakage) wired to compose healthcheck.
|
|
|
|
### `site-dev` (development)
|
|
- `node:22` dev container running `astro dev` with HMR; `site/` bind-mounted, `node_modules` in a named volume (Windows-host performance), port 4321 published.
|
|
- Relay runs alongside via `dotnet watch` in 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 Traefik network (`traefik_proxy`); the host Traefik (existing, TLS-terminating, Cloudflare-fronted) discovers the containers via labels and routes `cesnimda.co.uk` → `site:8080` and `cesnimda.co.uk/api/contact` → `relay:8081` (the relay's `PathPrefix` rule outranks the site's `Host` rule). 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 `site` image 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 build` beyond 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.
|