Graceful shutdown

docker stop sends SIGTERM to PID 1, waits for the grace period (10 seconds by default), then sends SIGKILL. A clean stop needs the app to receive the signal, and enough time to finish its work.

How it works

With the exec form of CMD, or exec at the end of a wrapper script, the app itself is PID 1 and gets SIGTERM. stop_grace_period in Compose (docker stop -t) sets how long Docker waits. init: true makes tini PID 1 to forward signals and reap zombies, but it signals its direct child only.

What breaks

A shell as PID 1 ignores SIGTERM: every stop lasts the full grace period and ends with SIGKILL (exit code 137), cutting requests, jobs and transactions in the middle. Under tini, a wrapper shell dies at once (exit code 143) and the app goes with it. Work longer than the grace period is killed too.

In a Dockerfile

FROM node:20-alpine
WORKDIR /app
COPY . .
# exec: node replaces the shell and receives SIGTERM itself.
CMD ["sh", "-c", "node src/migrate.js && exec node src/worker.js"]

See it break, then fix it

Write the Dockerfile or the compose file, type the docker commands, and watch a simulated server run them through reboots, upgrades and traffic. Free to start.