Skip to content

Health Endpoint

/health Endpoint

Returns JSON with server status, version, and uptime:

{"status":"ok","version":"3.18.0","uptime_seconds":3600}
  • Response time: < 5ms (no I/O, no Lua, no auth)
  • Method: GET only
  • Route: Registered before the catch-all IIIF handler

The version field is kept (not scrubbed as an information-disclosure concern): UptimeRobot and release-train checks read it, and the Docker image tag already discloses the running version, so removing it from the body closes no attack surface.

Healthcheck Ownership

The image does not embed a Docker HEALTHCHECK. HEALTHCHECK is a Docker-specific extension, not part of the OCI image spec, so the Bazel rules_oci image carries none. Liveness is owned by the orchestration layer — the docker-compose / Swarm healthcheck: stanza (provisioned in ops-deploy, INFRA-1226):

healthcheck:
  test: ["CMD", "/sbin/sipi", "health", "--port", "1024"]
  interval: 30s
  timeout: 5s
  start_period: 10s
  retries: 3

The check uses the self-contained sipi health subcommand, which probes http://127.0.0.1:1024/health and exits 0 (healthy) or 1 (unhealthy). Because the probe is a sipi subcommand, the image needs no curl binary for the healthcheck.

Swarm behavior: Docker Swarm has a single HEALTHCHECK — no separate liveness/readiness probes. When HEALTHCHECK fails after retries, Swarm kills and replaces the container. The start_period gives 10s for initialization (failures don't count, but container receives traffic during this period).

Design consequence: /health returns healthy once initialization completes. Only return unhealthy for truly fatal conditions (deadlocks, corrupted state), not for transient load.

Wedged Decode Threads

A JP2 decode that blows the seam deadline (DEV-7080) leaves its thread running — Kakadu offers no clean abort, so the FFI seam detaches it rather than blocking the request indefinitely. The leaked thread is counted by the wedged_threads gauge (Sipi::observability::Metrics), exported over OTLP alongside the other decode/memory gauges and reported by /health. It is never decremented — a wedged thread is not reclaimable short of a restart. Operators restart the process once wedged_threads reaches nthreads - 1: at that point the decode pool is one lane from total wedge, a silent outage.

Traefik Configuration

The /health endpoint is exposed externally via Traefik for UptimeRobot monitoring:

- traefik.http.routers.{{ STACK }}-iiif-health.rule=Host(`{{ DSP_IIIF_HOST }}`) && Path(`/health`)
- traefik.http.routers.{{ STACK }}-iiif-health.service={{ STACK }}-iiif
- traefik.http.routers.{{ STACK }}-iiif-health.entrypoints=websecure
- traefik.http.routers.{{ STACK }}-iiif-health.tls=true
- traefik.http.routers.{{ STACK }}-iiif-health.tls.certresolver=leresolver

UptimeRobot Configuration

Monitor https://iiif.example.com/health. Expected status: 200, expected body contains: "status":"ok". The Traefik label is provisioned in ops-deploy.