The job worker health endpoint
Audience: operator · Profile:
both· Read this when: you want to monitor the worker, or a health check reports it unhealthy.
Part of Job Worker.
The worker serves GET /api/health on WORKER_HEALTH_PORT (8080; 0 disables it). It is
the only thing it serves. Nothing publishes that port — the Container App has no ingress
and compose adds no ports: mapping — so it is reachable from the pod or host network,
which is where platform probes come from.
It exists because a worker with no port cannot be probed at all. ACA probes need an
httpGet or tcpSocket target and there is no exec probe, so before this the platform
had no way to check the runner. On 2026-08-28 the app and the worker cold-started together,
deadlocked inside init_db(), and sat at 0% CPU for ~50 minutes while ACA reported the
revision Healthy with restartCount: 0 — because with no probe there is nothing to fail.
It reports unhealthy until the run loop is actually turning, and that is the part that
matters. jobs_worker.main() starts the listener before init_db(), so during a wedge
there it answers 503 with a reason rather than refusing the connection. A listener that
answered 200 as soon as it bound would have returned 200 straight through that outage:
the process was alive the whole time — what was not true is that it was working.
Two independent signals, catching different faults:
| Response | Meaning | Which probe reads it |
|---|---|---|
503, phase = starting / db_ready |
startup never reached the run loop | Startup |
503, seconds_since_last_pass over the limit |
loop died after a clean start | Liveness |
200, phase = running |
claiming work normally | both |
The loop announces a pass each time round; 120s without one is treated as stalled. That is
two RECONCILE_INTERVALs, sized so a slow sweep on a large jobs table cannot manufacture
a restart — a false restart during a 30-minute terraform apply costs more than noticing a
genuine stall a minute late. Note this is supervisor liveness: the loop never awaits a
job, so a long-running job does not make it look stalled, and a thread-pool exhaustion that
leaves jobs at 0% is a different fault (see Threads) that this will not catch.
curl -s localhost:8080/api/health
To probe it on Container Apps — and the same caveat as any probe change applies, it mints a new revision, so not during an incident:
| Probe | Type | Period | Failures |
|---|---|---|---|
| Startup | httpGet /api/health:8080 |
30s | 10 |
| Liveness | httpGet /api/health:8080 |
30s | 3 |
Do not add that probe until the deployed image actually contains the listener. The image
must be at least the release carrying web_dashboard/worker_health.py; point a probe at
8080 on an older image and nothing answers, the startup probe fails its whole budget, and
ACA restart-loops a worker that was fine. Check with the curl above first. Readiness is
pointless here — there is no ingress to be removed from — so Startup and Liveness are the
whole set. See cloud-hosting.md for the app's own probes and for
why the Caddy sidecar needs a TCP probe instead.