# Self-hosting Dorfteich Everything you need to install, run, update, and back up your own Dorfteich with Docker — this guide is the complete contract: if a step here does not work, that is a bug (issue #88). > Docs are English-only by decision (issue #88): the product UI is fully > localized (de/en), operator documentation is not — one authoritative text > beats two drifting ones. ## Requirements - Docker Engine with the Compose plugin (`docker compose version` ≥ 2.20). - 2 GB RAM, ~2 GB disk for images plus room for your content and backups. - A domain pointing at the host — TLS via your own reverse proxy **or** the bundled `caddy` profile (below). - Outbound SMTP relay (optional at install time: the setup wizard can configure it later, or you skip mail entirely at first). ## Install 1. Create a directory and fetch the two reference files from the repository (`deploy/compose/`): `docker-compose.yml`, `.env.example` — plus `Caddyfile` if you want the `caddy` profile. ```sh mkdir dorfteich && cd dorfteich # copy docker-compose.yml, .env.example (and Caddyfile) here cp .env.example .env && chmod 600 .env ``` 2. Edit `.env` — the minimum: - `POSTGRES_PASSWORD`, `COLLAB_TOKEN_SECRET`: long random strings (`openssl rand -base64 32`). - `IMAGE_PREFIX=gitea.101010.cloud/stwaidele/dorfteich` and `TAG`: releases are semver tags (`v1.2.3`); until the first public release, `test` tracks the latest verified build. - `APP_BASE_URL=https://wiki.example.com` — must be exactly what browsers will use; e-mail links and the CSRF origin check derive from it. - Every other variable is documented inline in `.env.example` with its default and effect; nothing outside that file configures the stack. 3. Start: ```sh docker compose up -d # behind your own reverse proxy docker compose --profile caddy up -d # or with the bundled TLS ingress ``` - **Own proxy:** route `/*` → `127.0.0.1:$WEB_PORT`, `/api/*` → `$API_PORT`, `/collab*` → `$COLLAB_PORT` (**WebSocket upgrade required** on `/collab`). - **`caddy` profile:** set `DOMAIN=wiki.example.com` in `.env`; Caddy publishes 80/443 and obtains Let's Encrypt certificates automatically (both ports must be reachable from the internet). `DOMAIN=localhost` issues an internal-CA certificate — for smoke tests only. 4. Open `https://your-domain/` — a fresh instance shows the **first-run setup wizard**. ## First-run wizard Six steps, all in the browser (issue #80/#81): language → the Site-Admin account (created verified, you are signed in immediately) → instance name and default language → SMTP relay (**Save runs a live test** and sends a test mail to you; _Skip_ leaves mail unconfigured — sign-up verification will not work until an admin adds a relay) → registration mode (open/closed) → summary + finish. The wizard locks itself permanently on completion. Until it completes, the api answers everything but the wizard and health endpoints with `503 setup_required` — that is not an error. Unattended installs skip the wizard by pre-seeding: set the `SETUP_ADMIN_*` variables in `.env` before the first start (see `.env.example`). ## Updating ```sh # edit .env: TAG=v1.3.0 docker compose pull && docker compose up -d ``` Database migrations run automatically at api start. Release notes flag releases with a `migration` label and any manual steps. **Downgrade window: one minor release** — `TAG` back + `pull` + `up -d` is supported one step back; further back, restore the backup taken before the update instead (the nightly sidecar gives you one at most 24 h old). ## Backups & restore Enabled by default (ADR 0015): the `backup` sidecar dumps the database and archives the uploads/plugins volumes nightly at `BACKUP_TIME` onto the `backups` volume, prunes by `BACKUP_RETENTION_DAYS`, writes `status.json`, and — with `BACKUP_MAIL_TO` set — mails you on failure. - On-demand backup: `docker compose run --rm -e BACKUP_RUN_ONCE=1 backup` - List sets: `docker compose exec backup ls /backups` - Restore: `./restore.sh ` (fetch `deploy/backup/restore.sh` next to your compose file) — details in `docs/operations/restore-runbook.md`. - Copy the `backups` volume off the host regularly; a backup on the same disk protects against mistakes, not against losing the host. ## Health & troubleshooting - `GET /api/v1/readyz` is the instance's own diagnosis. HTTP 503 = database/migrations broken (the instance cannot serve). HTTP 200 with `"status":"degraded"` = a warning-level check: `converter`/`renderer` down (import/export/PDF degrade, everything else works) or `backup` stale (last success older than 26 h). Each check carries a `detail`. Monitor set: `deploy/monitoring.md`. - Logs: `docker compose logs api` (or `web`, `collab`, `backup`, `db`) — structured JSON, rotated by Docker. - **Proxy pitfalls:** editor never connects / "offline" although the page loads → the proxy does not upgrade WebSockets on `/collab`. All mutations fail with 403 `csrf_origin_mismatch` → `APP_BASE_URL` does not match the URL in the browser (scheme and host must be identical). E-mail links point at the wrong host → same variable. - Wizard reappears after a restart → the database volume was not persisted; never run without the `db-data` volume. - `docker compose ps` shows `unhealthy` → that container's liveness check fails; a _degraded_ readyz alone never marks containers unhealthy and never restarts anything. ## Building from source instead Clone the repository and build the images locally — the reference compose carries the build contexts already: ```sh docker compose build && docker compose up -d ``` Same layout, same volumes; you trade the registry pull for a local toolchain (Node 22 build stages run inside Docker, nothing else needed). ## Verified install The guide is verified by a scripted clean-machine run (`deploy/self-hosting-verify.sh`): fresh directory, reference compose + `.env.example` only, `--profile caddy` with an internal-CA certificate, asserting that the wizard answers over TLS. Run it yourself on any Docker host — it uses its own compose project name and high ports, then removes everything.