How it works
The explanation, with examples you can run.
The PID 1 trap
The first process in a PID namespace is PID 1, and the kernel treats it like an init system: signals for which it has not installed a handler are ignored, instead of getting their default action (for SIGTERM, terminate). Many runtimes install no SIGTERM handler, so as PID 1 they just keep running.
// nohandler.js: no SIGTERM handlerrequire("node:http").createServer((q, s) => s.end("hi")).listen(3000)docker run -d --name s1 nohandlertime docker stop -t 10 s1# real 10.13 <- waited out the whole timeoutdocker inspect s1 --format '{{.State.ExitCode}}'# 137 <- 128 + 9: SIGKILL docker run -d --init --name s2 nohandlerdocker exec s2 ps# PID USER COMMAND# 1 root /sbin/docker-init -- docker-entrypoint.sh node /nohandler.js# 7 root node /nohandler.jstime docker stop s2# real 0.13docker inspect s2 --format '{{.State.ExitCode}}'# 143 <- 128 + 15: SIGTERM, default actionWith --init, Docker's docker-init (backed by tini) is PID 1. It passes the signal on to node, which, no longer PID 1, gets the normal default action and exits at once. An init also reaps zombie processes: children whose parent died are re-parented to PID 1, and something must collect their exit status.
Shell form hides your process
CMD node server.js # shell form: runs as /bin/sh -c "node server.js"CMD ["node", "server.js"] # exec form: node itself is the main processWith the shell form the main process may be a shell, which does not forward SIGTERM to its child, so your app never hears about the stop. Use the exec form for CMD and ENTRYPOINT. Wrapper scripts should end with exec "$@" (or exec node server.js) so the app replaces the shell. Launchers such as npm start add another process in between; run the runtime directly in production images.
Handling the signal
process.on("SIGTERM", () => { console.log("SIGTERM: closing") server.close(() => process.exit(0)) // stop accepting, finish open requests, exit})docker run -d --name s3 hello:1 # the server from the Dockerfile topic, with this handlertime docker stop s3# real 0.11docker logs s3# listening on 3000# SIGTERM: closingdocker inspect s3 --format '{{.State.ExitCode}}'# 0Graceful shutdown means: stop taking new work, let in-flight requests or jobs finish (or hand them back to a queue), close database connections, flush logs, and exit 0 well before the timeout. If shutdown legitimately takes longer, raise the timeout (docker stop -t, --stop-timeout, Compose stop_grace_period, or Kubernetes terminationGracePeriodSeconds) rather than letting the kill arrive mid-write.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.