Opinionated starting points for running the Archive Node API in production. They
are references to adapt, not turnkey configs — review image tags, sizing, and
secret management for your environment. Read docs/security.md
first for the deployment contract (TLS gateway, read-only DB role, private
Postgres).
These manifests require image
>= 1.0.0and are pinned to it. The readiness probe and/metricsscrape target endpoints that0.0.ximages do not serve — on an older image the readiness probe 404s forever, no pod goes Ready, and the Service ends up with no endpoints at all.
Kubernetes — kubernetes.yaml
A Deployment + Service + HorizontalPodAutoscaler +
PodDisruptionBudget with production defaults baked in. Create the Postgres
Secret separately so routine kubectl apply runs never overwrite a real
connection string with the example placeholder.
- Liveness probe on
/healthcheck(process up) and readiness probe on/readiness(database reachable) — a node with a dead DB stops receiving traffic without being restarted. Readiness tolerates up to 60s of database slowness before removing endpoints. - Resource requests/limits and a 2 to 6 replica HPA on CPU; the HPA owns the replica count.
- Hardened pod: non-root,
readOnlyRootFilesystem,allowPrivilegeEscalation: false, all capabilities dropped,RuntimeDefaultseccomp, no mounted service account token, and a scratchemptyDirmounted at/tmp. - Prometheus scrape annotations pointing at
/metrics, withENABLE_METRICS=true. preStopsleeps 15s before SIGTERM, thenterminationGracePeriodSeconds: 45leaves room for the app's own 10sSHUTDOWN_TIMEOUT_MSdrain before SIGKILL.
# once, with your real connection string (never committed):
kubectl create secret generic archive-node-api \
--from-literal=PG_CONN='postgres://archive_api_ro:...@postgres:5432/archive'
kubectl apply -f deploy/kubernetes.yamlPut a TLS-terminating Ingress/gateway in front and set TRUST_PROXY to the
number of hops it adds. The gateway must set X-Forwarded-For, but the API
ignores that header while TRUST_PROXY=0 (the safe default for a directly
exposed server), so leaving it unset behind an ingress collapses every client
into a single rate-limit bucket. See docs/security.md.
Docker Compose — docker-compose.prod.yml
Runs only the published image against an external Postgres (contrast with the
repo-root docker-compose.yml, which is for local dev with a bundled DB).
Compose surfaces the image's /healthcheck as container health for images
>= 1.0.0; this is a liveness check, not database-aware readiness.
PG_CONN='postgres://archive_api_ro:...@db:5432/archive' \
docker compose -f deploy/docker-compose.prod.yml up -dThe bottleneck is Postgres, not this server. Note that listing several hosts in
PG_CONN gives you failover, not read throughput — the client sticks to the
first host and only moves on when the connection fails. To spread reads across
replicas, put a load balancer (PgBouncer, HAProxy, a managed reader endpoint) in
front of Postgres and point PG_CONN at it. See the benchmark note in the root
README.md and use npm run benchmark to
size your own deployment.