Handling Signals and Graceful Shutdowns: A Docker Guide
Learn how to handle SIGTERM, implement graceful stops, and verify process cleanup in your Docker containers to ensure production-grade stability.

Previously in this course, we explored Advanced Dockerfile Directives to improve container robustness. Now, we shift our focus from "how it starts" to "how it stops." When you run docker stop, you aren't just flipping a switch; you are initiating a communication sequence between the Docker daemon and your application. Understanding this sequence is the difference between a system that recovers gracefully and one that leaves corrupted data or "zombie" connections in its wake.
Understanding the Signal Lifecycle
When you request a container to stop, the Docker daemon sends a SIGTERM (signal 15) to the primary process (PID 1) inside the container. This is a polite request: "Please finish what you are doing and exit."
If your application ignores this signal, Docker waits for a "grace period" (defaulting to 10 seconds). If the process is still running after that period expires, Docker sends SIGKILL (signal 9), which immediately terminates the process without allowing it to clean up resources, close database handles, or finish writing logs.
| Signal | Meaning | Docker Behavior |
|---|---|---|
| SIGTERM | Request to terminate | Sent by docker stop; allows cleanup |
| SIGKILL | Immediate termination | Sent after timeout; no cleanup possible |
Why PID 1 Matters
In Linux, the process with PID 1 is the init process and is responsible for reaping orphan processes. If you start your app using a shell script (e.g., CMD ["./run.sh"]), the shell becomes PID 1. Shells often fail to forward signals to child processes, meaning your app never receives the SIGTERM and is eventually killed by the 10-second timeout.
To fix this, use the exec form in your Dockerfile:
Dockerfile# GOOD: The application becomes PID 1 CMD ["node", "server.js"] # BAD: The shell becomes PID 1 and may swallow signals CMD ["./run.sh"]
Worked Example: Implementing a Graceful Shutdown
Let's look at a concrete implementation using a Node.js server. If you want to dive deeper into specific framework patterns, you can check out how we manage Node.js Graceful Shutdown.
The Code (server.js):
JAVASCRIPTconst http = require(CE9178">'http'); const server = http.createServer((req, res) => { res.end(CE9178">'Hello World'); }); server.listen(3000, () => console.log(CE9178">'Server running on port 3000')); // Listen for the SIGTERM signal process.on(CE9178">'SIGTERM', () => { console.log(CE9178">'SIGTERM received. Shutting down gracefully...'); // Stop accepting new connections server.close(() => { console.log(CE9178">'HTTP server closed. Process exiting.'); process.exit(0); }); });
When you run docker stop <container_id>, the output will show your custom log message rather than an abrupt termination. This ensures active database transactions or long-running requests are handled properly, similar to the strategies we use for Laravel Horizon graceful shutdowns.
Hands-on Exercise: Verifying Cleanup
- Build and run the code above in a container.
- Open a second terminal and run
docker stop <container_id>. - Watch the logs (using
docker logs -f <container_id>). You should see your "Shutting down" message. - If you see the container stop immediately without your log, check if you are using the shell form (
CMD ./server.js) instead of the exec form (CMD ["node", "server.js"]).
Common Pitfalls
- The Shell Trap: As mentioned, avoid
CMD ./script.sh. If you must use a script, useexec ./script.shas the last line of the script to replace the shell process with your application. - Ignoring Signals: Some frameworks swallow signals by default. Ensure your application explicitly subscribes to
SIGTERMandSIGINT. - Too Short a Timeout: If your app takes 15 seconds to flush a massive buffer to a database, the default 10-second Docker timeout will kill it. Use
docker stop --time=30 <container_id>to give it more room.
FAQ
Q: Does docker kill send SIGTERM?
A: No, docker kill sends SIGKILL immediately. Only use this for unresponsive containers.
Q: Can I change the default 10-second timeout?
A: Yes, use the --time or -t flag with docker stop.
Q: Why does my app exit with code 137?
A: That is the standard exit code for a process terminated by SIGKILL (128 + 9). It indicates your process didn't shut down in time.
Recap
Graceful shutdowns are the hallmark of production-ready infrastructure. By ensuring your process is PID 1, listening for SIGTERM, and performing cleanups like closing connections, you prevent data corruption and improve service reliability.
Up next: We will explore how to use Docker Compose Profiles to manage different service subsets for your development environment.
Work with me

CI/CD Pipeline & Docker Containerization
Ship with confidence: automated CI/CD pipelines and Docker setups so every push is tested and deployed โ no more manual, error-prone releases.

VPS Server Setup, Deployment & Hardening
Get your app live on a fast, secure server โ properly configured, hardened, and deployment-ready. No more wrestling with the command line.


