Files
terdut-server/docs/deployment.md
T
Niklas Ye 44b2eb2cc3 Rewrite the README as highlights with screenshots; move the detail into docs/
The README was 1,240 lines of reference material and still described a
SQLite quick start. It is now a short tour (highlights, screenshots of the
web UI, an accurate quick start against Postgres), and each topic has its
own page under docs/ with an index: deployment, configuration, Alertmanager,
incidents, notifications, escalation, dead man's switches, single sign-on,
web UI, API and development. SERVICE-ACCOUNTS.md is rewritten from a
proposal into a reference, and TEAM-LOOKUP.md is gone with the endpoint it
described. The "Upgrading to ..." sections for an unreleased product are
dropped.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-09 14:56:13 +02:00

3.8 KiB

Deployment

Running the server in a container and on Kubernetes with the Helm chart. Back to the README and the documentation index.

Docker

docker build -t terdut-server .
docker run -p 8080:8080 \
  -e TERDUT_DB_DSN='postgres://terdut:secret@host.docker.internal:5432/terdut?sslmode=disable' \
  terdut-server

The server creates its own schema on startup and needs a reachable Postgres; it stores nothing on disk, so there is no volume to mount.

Kubernetes

A Helm chart is published from this repository as an OCI artifact, versioned in lockstep with the app — chart x.y.z is always app vx.y.z:

helm upgrade --install terdut-server oci://git.ryuvia.com/niklas/terdut-server \
  --version 0.9.2 \
  --namespace terdut-server --create-namespace \
  --set networking.hostname=terdut.example.com

The chart expects a Gateway API Gateway named envoy-main in the envoy-gateway-system namespace to already exist — it renders an HTTPRoute against it rather than an Ingress. TLS is terminated at the gateway, so the server itself never sees a certificate.

Value Default Description
networking.hostname terdut.example.com Hostname the HTTPRoute serves
networking.listener "" Gateway listener (sectionName) to bind to. Empty attaches to every matching listener, including plaintext HTTP — set it to the HTTPS listener's name to serve TLS only
networking.servicePort 8080 Port the route forwards to; keep in sync with service.port
bootstrap.enabled true Runs a post-install hook that creates the first user and stores its API key in the <release>-admin-key Secret. Already-bootstrapped servers are left alone
database.dsn "" Required. Postgres DSN, with no password in it. The chart provisions no database
database.passwordSecret.name "" Secret supplying PGPASSWORD. With the Zalando postgres operator, the Secret it generates for the role
database.passwordSecret.key password Key within that Secret

The API key travels in an Authorization: Bearer header, so set networking.listener whenever the hostname is reachable outside a trusted network.

The database

The chart provisions no database: it takes a DSN and expects a Postgres that already exists. In this cluster the wrapper chart declares an acid.zalan.do/v1 postgresql CR; anywhere else, any reachable Postgres 14+ will do.

The DSN carries no password. pgx falls back to libpq's environment variables for whatever the DSN leaves out, so the password arrives as PGPASSWORD from a Secret and never appears in values, in the rendered manifest or in kubectl describe pod. With the postgres operator that Secret is the one it generates for the role, so a rebuild mints a new password with nothing to keep in sync — the same wiring miniflux uses.

The server migrates its own schema on startup, so a new database only has to exist and be writable.

Backups

Postgres is backed up where it runs, not from here. The database pod carries a k8up k8up.io/backupcommand annotation that streams a pg_dump, the same way gitea and immich do in this cluster.

On Kubernetes with the operator

terdut-operator runs a server for you from a TerdutServer object and manages its teams, escalation ladders, dead man's switches and alert sources as Kubernetes objects. It hands the server a generated key through TERDUT_OPERATOR_KEY (see Configuration and SERVICE-ACCOUNTS.md), and the server then treats configuration as operator-managed (TERDUT_OPERATOR_MODE), refusing edits made by hand in the web UI. Use the Helm chart above for a plain install, the operator when you want that configuration in gitops.