Master Graceful Docker Container Shutdown: Signals, Exit Codes, and Go Example
This article explains how to achieve graceful shutdown of Docker containers by handling Linux signals, interpreting common Docker exit codes, and using a Go program that registers SIGTERM and SIGINT, while also covering docker stop, docker kill, and daemon signal handling.
1. Overview
Any application should receive a stop notification before termination to release resources, close connections, and finish pending requests, such as completing HTTP requests or flushing file buffers.
2. Common Linux Signals
Signals are inter‑process messages. Programs typically handle SIGTERM for graceful termination, while SIGKILL forces immediate exit. SIGHUP is used for hot‑reloading configuration, SIGINT for Ctrl‑C, SIGQUIT for graceful quit, SIGCHLD for child‑process exit, etc.
SIGHUP (1) – sent when a terminal closes; used by services like wget, Docker, Nginx for config reload.
SIGINT (2) – generated by Ctrl‑C.
SIGQUIT (3) – generated by Ctrl‑\, used by Nginx for graceful stop.
SIGKILL (9) – cannot be caught or ignored; forces immediate termination.
SIGTERM (15) – termination request that can be caught; default signal for
docker stop.
SIGCHLD (17) – sent to a parent when a child exits; used by multi‑process services like Nginx.
3. Common Docker Exit Codes
Exit Code 1
Program error or missing file in Dockerfile.
Examples: division by zero, nil pointer, crash.
Exit Code 137
Container received SIGKILL (kill -9).
Often caused by OOMKilled when memory limits are low.
Exit Code 139
Container received SIGSEGV (invalid memory access).
Usually due to buggy code or base image issues.
Exit Code 143
Container received SIGTERM (docker stop).
docker stop may fall back to SIGKILL after timeout.
Other Exit Codes
126 – permission or non‑executable command.
127 – command not found.
1 or 255 – custom exit values.
Signals terminated processes have their signal number added to 128 to form the exit status (e.g., SIGKILL → 137, SIGTERM → 143).
4. Docker Signal Support
4.1 docker stop
docker stopsends SIGTERM to PID 1, waits (default 10 s) and then sends SIGKILL. The timeout can be changed with
--time/-t.
<code>→ docker stop --help
Usage: docker stop [OPTIONS] CONTAINER [CONTAINER…]
Options:
--help Print usage
-t, --time int Seconds to wait for stop before killing it (default 10)
</code>4.2 docker kill
docker killsends SIGKILL by default but can send any signal with
-s/--signal, e.g.,
docker kill --signal=SIGINT mycontainer.
<code>→ docker kill --help
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER…]
Options:
--help Print usage
-s, --signal string Signal to send to the container (default "KILL")
</code>4.3 docker rm
docker rm -fforces removal by first sending SIGKILL to the running container.
4.4 Docker daemon signals
The daemon reloads its configuration on SIGHUP. Example:
kill -SIGHUP $(pidof dockerd)or
systemctl reload docker.
<code>root@host# kill -SIGHUP $(pidof dockerd)
root@host# systemctl reload docker
</code>5. Graceful Shutdown Example (Go)
The following Go program registers SIGTERM and SIGINT, prints a message when a signal is received, and exits.
<code>// main.go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
fmt.Println("Program started…")
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM)
signal.Notify(ch, syscall.SIGINT)
s := <-ch
switch {
case s == syscall.SIGINT:
fmt.Println("SIGINT received!")
case s == syscall.SIGTERM:
fmt.Println("SIGTERM received!")
}
fmt.Println("Exiting…")
}
</code>Build the binary for Linux:
<code>CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o graceful
</code>Run the container:
<code>docker build -t graceful-golang-case:1.0.0 .
docker run -d --name graceful graceful-golang-case:1.0.0
docker logs graceful
docker stop --time=120 graceful
</code>Using
docker stopallows the program to handle SIGTERM and exit cleanly, whereas
docker killor
docker rm -fwould terminate it abruptly.
6. Summary
For graceful Docker container termination, ensure your application handles SIGTERM, prefer
docker stop(which sends SIGTERM then SIGKILL after a timeout), and use the exec form of
CMDor
ENTRYPOINTso signals reach the intended process.
Q&A
Q1: How to verify that a registered SIGTERM handler was executed?
A: Check the container logs for the expected output.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.