# Deployment architecture Four stages, one Compose definition. Foundational decisions: ADR 0014 (CI/CD), ADR 0015 (backup), kickoff topology decision (Dev local on the developer's machine; Test and Int on the operator's VPS `188.245.116.44`; the Prod host is decided at go-live — the architecture must keep that choice and any later move cheap). DNS status: `*.dorfteich.online` and `*.dorfteich.cloud` already point to the VPS. ## The Compose stack Every stage (and every self-hosted instance) runs the same services: | Service | Image | Notes | | --- | --- | --- | | `web` | `dorfteich-web` | nginx: SPA assets, fonts; SPA fallback routing | | `api` | `dorfteich-api` | NestJS; runs `prisma migrate deploy` on start | | `collab` | `dorfteich-collab` | Hocuspocus WebSocket server | | `db` | `postgres:` | volume `db-data` | | `pandoc` | `pandoc/core:` (server mode) | internal only | | `gotenberg` | `gotenberg/gotenberg:` | internal only | | `backup` | `dorfteich-backup` | cron sidecar: pg_dump, volume archive, prune, mirror (ADR 0015) | Volumes: `db-data`, `uploads` (uploads + installed plugins), `backups`. Networks: `frontend` (reverse proxy ↔ web/api/collab) and `internal` (api/collab ↔ db/pandoc/gotenberg); db and converters are never exposed. Ingress is a host-level reverse proxy (existing Caddy/Traefik/nginx on the host), routing: ``` / → web /api/ → api /collab → collab (WebSocket upgrade required) /media/ → api (permission-checked file streaming) ``` Self-hosters without a proxy can enable the optional `caddy` Compose profile (bundled Caddy with automatic TLS). ## Stages | Stage | Where | Domain | Purpose | Data | | --- | --- | --- | --- | --- | | **Dev** | contributor machine (e.g. the operator's MacBook), Docker Desktop | `localhost` | feature work; hot reload via `compose.dev.yml` overlay (source mounts, vite dev server) | fixtures/seed script | | **Test** | VPS `188.245.116.44`, `/home/DOCKER/dorfteich-test/` | `test.dorfteich.cloud` | auto-deploy target of `main`; e2e suite runs here | reset-able; seeded | | **Int** | VPS `188.245.116.44`, `/home/DOCKER/dorfteich-int/` | `int.dorfteich.cloud` | stable preview; manual/exploratory testing; release candidates | persistent test data | | **Prod** | host decided at go-live (M8): the VPS or a dedicated host | `dorfteich.online` | public flagship instance | real data; full backup + mirror | Stage layout follows the operator's Docker host convention: compose file + `.env` under `/home/DOCKER/dorfteich-/`, bulk data volumes under `/home/RAID/DOCKER/dorfteich-/` (bind-mounted). **Prod relocation readiness** (kickoff requirement): all state lives in the three volumes + `.env`; the documented move procedure is: stop stack → final backup → restore backup set on the new host → switch DNS. The backup sidecar's restore runbook doubles as the migration procedure, and Test restore drills (ADR 0015) keep it honest. ## Configuration - One `.env` per stage (never in git; `.env.example` in the repo documents every variable): database credentials, `APP_BASE_URL`, collab token signing key, SMTP settings, stage name shown in the UI for non-Prod. - First-run **setup wizard** (kickoff decision): when the API starts against an empty database it exposes only `/setup` (create Site Admin account, SMTP, instance name/locale, registration mode); the wizard locks itself after completion. `.env` can pre-seed these for automated deploys (Test/Int use exactly that). ## Pipeline (ADR 0014, concrete) ```mermaid flowchart LR PR[PR: lint + typecheck + unit + build] -->|merge| M[main] M --> B[build images :sha] B --> DT[deploy Test] DT --> E2E[Playwright e2e vs Test] E2E -->|green| DI[deploy Int - tag int] DI --> REL{manual: tag vX.Y.Z + approval} REL --> DP[deploy Prod - semver tag] ``` - Deploy jobs SSH into the stage directory and run `docker compose pull && docker compose up -d`; migrations apply on api start. Rollback = re-deploy the previous tag (migrations must be backward-compatible one release back — contributor rule for schema stories). - The e2e suite is the Int-promotion gate; flaky tests are defects. - Release notes are generated from merged PR titles; releases with data-affecting migrations are labeled `migration` and called out. ## Self-hosting distribution - Published artifacts per release: versioned images in the Gitea registry (mirrored to a public registry at first public release), a reference `docker-compose.yml` + `.env.example`, and the install/update/backup guide (`docs/self-hosting/`, written as part of the docs milestone). - Minimum requirements: Docker + Compose, 2 GB RAM, a domain (TLS via own proxy or the `caddy` profile). Setup = compose up + browser wizard. - Updates: `docker compose pull && up -d` on a new semver tag; migrations run automatically; the release notes flag anything manual. Downgrades are supported one release back.