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

146 lines
6.2 KiB
Markdown

# 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 <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_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.