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