When a deployment replaces a container, SIGTERM means “start leaving now”, not “you have shut down safely”. A reliable service first disappears from traffic, then drains the work it already accepted, then releases dependencies before the grace period ends.
Make the application receive the signal
Use Docker's exec-form command so the runtime is PID 1 or receives forwarded signals:
CMD ["node", "server.js"]
Avoid CMD node server.js or a wrapper shell unless it explicitly forwards signals. A handler in the child process is useless if PID 1 swallows the signal.
Drain in a defined order
process.on("SIGTERM", async () => {
draining = true; // readiness endpoint returns 503
server.close(); // stop accepting new HTTP connections
await Promise.race([workers.drain(), timeout(25_000)]);
await database.end();
process.exit(0);
});| Step | Failure it prevents |
|---|---|
| Fail readiness | Load balancer keeps selecting the pod |
| Close listener | New requests extend drain forever |
| Drain or hand off jobs | Duplicate or half-complete background work |
| Bound the wait | Orchestrator sends uncatchable SIGKILL |
A signal handler that only logs
SIGTERMis not graceful shutdown. It has not changed readiness, stopped intake, or protected work already in progress.
Test the shutdown path
Hold a request open, send docker stop, and inspect whether the response completes before the configured timeout. Repeat with a queue message and a database transaction. Kubernetes adds its own timings—terminationGracePeriodSeconds, readiness propagation, and any preStop hook—so make the application's timeout shorter than the platform's deadline.
Graceful shutdown is backpressure at deploy time: stop accepting responsibility before you relinquish the process.
