Operations

Health checks & readiness

What /health and /ready actually verify — and what to alert on.

Ductor exposes liveness and readiness endpoints on the HTTP listener, plus a health CLI subcommand. The distinction between liveness and readiness matters: liveness answers "is the process alive?", readiness answers "can it serve traffic right now?".

The endpoints

All are served on the HTTP listener (server.http_addr, default :8080):

PathKindReturns
/healthLiveness200 {"status":"ok"} as long as the process is running
/readyReadiness200 when internal readiness is set and backing dependencies are reachable; 503 otherwise

Registered aliases

/healthz and /livez are registered aliases for /health; /readyz is a registered alias for /ready. Kubernetes convention accepts either spelling — use whichever your probe tooling expects. This page uses the canonical /health and /ready throughout.

Liveness — process is up

The liveness handler returns 200 unconditionally while the process runs. It does not check dependencies — a liveness failure means the process itself is wedged and should be restarted. Use it for the container/Kubernetes liveness probe so a transient database blip doesn't cause a restart loop.

Readiness — safe to route traffic

The readiness handler returns 200 only when both are true:

  1. Internal readiness is set — the app finished booting and wiring.
  2. Dependencies are reachable — a live check against Postgres and Redis, bounded by a short timeout so a slow backend doesn't hang the probe.
no yes no yes GET /ready internal readiness set? 503 Postgres + Redis reachable? 200

Because readiness re-checks dependencies on every call, it flips to 503 if Postgres or Redis becomes unreachable after boot — not just at startup. Use it for the readiness probe and load-balancer health so an unhealthy pod is pulled from rotation without being killed.

The CLI check

ductor health performs an HTTP GET /health against a running server and prints OK or the error. It's handy for scripts and the container healthcheck:

ductor health --http-addr :8080

The container image's built-in healthcheck does the equivalent (wget --spider http://localhost:8080/health).

Probe configuration

Docker Compose

The image ships a healthcheck against /health (interval 10s, timeout 3s, 3 retries). No extra config needed.

Kubernetes (Helm)

The chart wires all three probes to the right endpoints:

ProbePathWhy
startupProbe/readyHold traffic until boot + dependencies are good
readinessProbe/readyPull the pod from Service endpoints when not ready
livenessProbe/healthRestart only when the process is wedged

Don't point liveness at /ready

If you use a readiness endpoint for the liveness probe, a brief Postgres outage makes /ready fail, Kubernetes restarts every pod, and you turn a dependency blip into an outage. Keep liveness on /health and readiness on /ready.

What to alert on

  • Readiness failing across replicas — dependencies (Postgres/Redis) unreachable, or a bad rollout. This is your primary "is Ductor serving?" alert.
  • Coordinator health — stuck runs and dead-lettered wakeups (workflow_dag.coordinator.stuck_run_threshold, workflow_dag.coordinator.wakeup_dead_letter_threshold) surface in the DAG metrics.
  • Pending migrationsserve logs a warning at startup; ductor schema status exits non-zero. Gate deploys on it.

See Observability for wiring these signals into metrics and alerts.