dorfteich/docs/self-hosting/README.md
Claude Fable 5 4b55fb92ac
All checks were successful
CD / Build and push images (push) Successful in 1m7s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m8s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 3m13s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m18s
CI / Import/export fidelity gate (push) Successful in 46s
Write the self-hosting guide and add the optional caddy TLS profile (#88)
docs/self-hosting/README.md is the complete operator contract: install
from the two reference files, first-run wizard walkthrough, update
procedure with the one-release downgrade window, backup/restore with the
sidecar, readyz-based troubleshooting (incl. the classic proxy/WebSocket
and APP_BASE_URL/CSRF mistakes), and a build-from-source note; linked
from the repository README; English-only by documented decision. The
reference compose gains a `caddy` profile (new Caddyfile) that publishes
80/443 and terminates TLS via Let's Encrypt for $DOMAIN — localhost uses
Caddy's internal CA for smoke tests. deploy/self-hosting-verify.sh
scripts the clean-machine test: a fresh directory with only the
published files boots to the wizard answering over TLS, then removes
itself; verified green on the stage host.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-11 21:21:10 +02:00

6.2 KiB

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.

    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:

    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

# 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 releaseTAG 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 <backup-id> (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_mismatchAPP_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:

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.