Docker path · Running containers · Topic 11 of 12

PID 1, signals and graceful shutdown

Not started

Stopping a container is a negotiation. docker stop sends the container's main process a signal (SIGTERM unless the image sets STOPSIGNAL), waits for the stop timeout, which the daemon sets to 10 seconds for Linux containers unless you change it, and then sends SIGKILL, which cannot be caught. A well-behaved process uses those seconds to stop accepting work, finish what is in flight and exit. Kubernetes does the same, with a 30-second default.

Why it matters: Every deploy stops containers. If your process ignores SIGTERM, each stop waits out the whole grace period and then kills it mid-request.

  1. 1docker stop is a two-step

    It sends the stop signal (SIGTERM unless STOPSIGNAL says otherwise), waits the timeout (10 s by default on Linux), then sends SIGKILL.

  2. 2PID 1 is special

    The kernel does not apply default signal actions to PID 1, so a process with no SIGTERM handler simply ignores it.

  3. 3Exec form, not shell form

    CMD node server.js runs under /bin/sh -c; CMD ["node", "server.js"] runs node directly, so it receives the signal.

  4. 4An init helps

    --init runs docker-init (tini) as PID 1; it forwards signals to your process and reaps zombie child processes.

1

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 action

With --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 process

With 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}}'# 0

Graceful 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.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.A container takes exactly the stop timeout to stop and exits with code 137. What happened?
  2. 2.Why does a Node server with no SIGTERM handler stop immediately on your laptop but not in a container?
  3. 3.What does docker run --init change?

0 of 3 right so far. Get all three to finish this topic.