Cloud Native 11 min read

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.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How to Gracefully Stop Docker Containers Using Signals, ENTRYPOINT, and CMD

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 format

Build 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 total

The 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 format

Running the same test now yields:

time docker stop signals
signals
docker stop signals  0.01s user 0.01s system 0% cpu 10.719 total

The 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
/signals

Running 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 total

Fix by adding exec in the script:

# start.sh (fixed)
#!/bin/sh
exec /signals   # replace the shell with the binary

Rebuilding and stopping now behaves gracefully:

time docker stop signals
signals
docker stop signals  0.02s user 0.02s system 4% cpu 0.744 total

Thus, 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerGoContainerGraceful ShutdowncmdsignalsENTRYPOINT
Liangxu Linux
Written by

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

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.