How to Gracefully Stop Docker Containers Using Signals, ENTRYPOINT, and CMD
This article explains Linux signals, the difference between exec and shell forms of Dockerfile ENTRYPOINT and CMD, and demonstrates with Go and shell script examples how to configure containers so that docker stop sends SIGTERM correctly for a clean shutdown instead of a forced kill.
1. Linux Signals
Signals are the Linux kernel's mechanism for notifying processes of events, often called software interrupts. Standard signals are numbered 1‑31 and can be listed with
# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT
4) SIGILL 5) SIGTRAP 6) SIGABRT
7) SIGBUS 8) SIGFPE 9) SIGKILL
10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM
... SIGHUP: Sent when a terminal disconnects; often used to tell daemons to reload configuration. SIGINT: Generated by Ctrl‑C ; default action terminates the process. SIGQUIT: Generated by Ctrl‑\ ; terminates and produces a core dump. SIGKILL: Uncatchable kill signal; always terminates the process. SIGTERM: Default termination signal used by kill, killall, pkill. Well‑behaved programs should handle it to clean up resources before exiting. SIGTSTP: Sent by Ctrl‑Z to stop a job.
Note that Ctrl‑D does not send a signal; it signals end‑of‑file on stdin.
2. ENTRYPOINT and CMD
Both instructions define the command that runs when a container starts. Each has two syntax forms:
Exec form (recommended): CMD ["executable","param1","param2"] or ENTRYPOINT ["executable","param1","param2"] Shell form: CMD executable param1 param2 (runs via /bin/sh -c) or ENTRYPOINT executable param1 param2 When the shell form is used, the process runs under /bin/sh -c, which does not forward Unix signals to the underlying program. Consequently, docker stop (which sends SIGTERM by default) cannot be caught, and Docker falls back to SIGKILL after the timeout.
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)By default docker stop sends SIGTERM and, after 10 seconds, SIGKILL if the container has not exited.
docker kill --help
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
Options:
--help Print usage
-s, --signal string Signal to send to the container (default "KILL")3. Practical Examples
3.1 Example 1 – Go signal handler
A small Go program that listens for SIGINT and SIGTERM and prints the received signal before exiting:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigs
fmt.Println()
fmt.Println(sig)
done <- true
}()
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}Dockerfile using the exec form:
FROM busybox
COPY signals /signals
CMD ["/signals"] # exec formatBuild and run:
docker build -t signals .
# In one terminal
docker run -it --rm --name signals signals
awaiting signal
# In another terminal
time docker stop signals
signals
docker stop signals 0.01s user 0.02s system 4% cpu 0.732 totalThe program receives SIGTERM, prints the signal, and exits in ~0.73 s – a graceful shutdown.
Changing the Dockerfile to the shell form:
FROM busybox
COPY signals /signals
CMD /signals # shell formatRunning the same test now yields:
time docker stop signals
signals
docker stop signals 0.01s user 0.01s system 0% cpu 10.719 totalThe program never receives the signal; Docker kills it after the 10‑second timeout.
3.2 Example 2 – Using a shell script as the container entrypoint
When a shell script is used as CMD, the script must exec the real binary to forward signals:
# Dockerfile
FROM busybox
COPY signals /signals
COPY start.sh /start.sh
CMD ["/start.sh"]
# start.sh (initial version)
#!/bin/sh
/signalsRunning this version still results in a forced kill because the script runs /signals as a child process without exec:
time docker stop signals
signals
docker stop signals 0.01s user 0.02s system 0% cpu 10.765 totalFix by adding exec in the script:
# start.sh (fixed)
#!/bin/sh
exec /signals # replace the shell with the binaryRebuilding and stopping now behaves gracefully:
time docker stop signals
signals
docker stop signals 0.02s user 0.02s system 4% cpu 0.744 totalThus, to achieve graceful container termination, always use the exec form for ENTRYPOINT and CMD, and ensure any wrapper shell scripts also invoke the target binary with exec so that signals propagate correctly.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
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.
