Web and private services lose traffic and receive SIGTERM when a release replaces them. An app that exits on the first signal drops requests that were already accepted. This page points at a downloadable sample that finishes bounded work, refuses new work while draining, and exits within a configured deadline.
Source pin: Bex 88f0a89792d705a079075c383cf0a5a9561acc06
(deployment_projection.go: 10s preStop.sleep for traffic-serving pods;
workers and disk-attached Recreate services get no preStop;
maxShutdownDelaySeconds is the post-SIGTERM budget, default 30s; pod grace =
preStop + that delay). The pin documents source behavior — not which revision
is deployed at api.bex.co.
What the sample does
The http-shutdown example:
- Listens on
0.0.0.0:$PORTwithGET /readyzfor readiness - Serves bounded
GET /work?ms=requests and an SSEGET /streampath - On the first
SIGTERM/SIGINT, entersdrainingonce — readiness fails and new work (including on keep-alive sockets) returns 503 - Lets accepted work finish inside
SHUTDOWN_DEADLINE_MS, then exits - Forces unfinished streams/work when the deadline elapses
The application does not sleep for the platform preStop window. preStop runs before SIGTERM so endpoints can drain; your process budget starts at the signal.
Run locally
curl -fsSLO https://bex.co/examples/http-shutdown-latest.zip
curl -fsSLO https://bex.co/examples/http-shutdown-latest.zip.sha256
shasum -a 256 -c http-shutdown-latest.zip.sha256
unzip http-shutdown-latest.zip
cd http-shutdown
npm test
npm run rehearseNode 22+. No npm dependencies. npm test and npm run rehearse spawn a real
child process and use sockets/signals; they clean up only that child.
Local success is not evidence of Traefik endpoint propagation or whole-service zero downtime.
Deploy on Bex
Use the sample render.yaml: type: web, healthCheckPath: /readyz,
maxShutdownDelaySeconds: 30, and SHUTDOWN_DEADLINE_MS below that budget.
Docker CMD is exec-form (["node","server.mjs"]) so Node is PID 1 and
receives signals.
Workers and disk-backed Recreate services skip the 10s preStop drain at the pinned revision — they still get SIGTERM and the configured shutdown delay.
Troubleshooting
| Symptom | Check |
|---|---|
| Readiness never passes | HOST/PORT, process listening on 0.0.0.0, probe path /readyz |
| Requests cut off on deploy | Signal reaches the app (exec-form entrypoint); drain budget vs work duration |
| Process never exits | SHUTDOWN_DEADLINE_MS must bound streams; avoid ignoring SIGTERM |
| Still serving after SIGTERM | Expected briefly for in-flight work; new work must 503 while draining |