Operations 11 min read

How to Gracefully Shut Down Docker Containers Using Signals and ENTRYPOINT

This article explains Linux signal fundamentals, the role of Dockerfile ENTRYPOINT and CMD directives, and demonstrates with Go and shell examples how using the exec form enables containers to receive termination signals for clean shutdowns instead of being force‑killed.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
How to Gracefully Shut Down Docker Containers Using Signals and ENTRYPOINT

1 Signals

Signals are a notification mechanism for processes in Linux, sometimes called software interrupts.

Linux defines standard signals numbered 1–31, which 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
...

Common signals include:

SIGHUP – sent when a terminal disconnects; daemons often reload configuration on this signal.

SIGINT – generated by Ctrl‑C; default action terminates the process.

SIGQUIT – generated by Ctrl‑\; terminates the process and creates a core dump.

SIGKILL – cannot be caught or ignored; always terminates the process.

SIGTERM – the standard termination signal used by docker stop ; well‑designed programs handle it to clean up resources before exiting.

SIGTSTP – job‑control stop signal generated by Ctrl‑Z.

Note that Control‑D does not send a signal; it signals EOF on stdin.

Programs can catch signals and run handler functions:

The above knowledge mainly comes from "The Linux/UNIX System Programming Handbook", chapters 20‑22.

2 ENTRYPOINT, CMD

Both directives specify the program that runs when a container starts.

CMD has three formats:

CMD ["executable","param1","param2"] (exec format, recommended)

CMD ["param1","param2"] (as arguments to ENTRYPOINT)

CMD command param1 param2 (shell format, defaults to /bin/sh -c)

ENTRYPOINT also has two formats:

ENTRYPOINT ["executable","param1","param2"] (exec format, recommended)

ENTRYPOINT command param1 param2 (shell format)

Using the exec format is preferred because the shell format runs the command via /bin/sh -c, which does not forward signals to the program, preventing graceful shutdown when docker stop sends SIGTERM.

➜  ~ docker stop --help

Usage:  docker stop [OPTIONS] CONTAINER [CONTAINER...]

Stop one or more running containers

Options:
      --help       Print usage
  -t, --time int   Seconds to wait for stop before killing it (default 10)
docker stop

sends SIGTERM by default and, after the timeout, SIGKILL.

➜  ~ docker kill --help

Usage:  docker kill [OPTIONS] CONTAINER [CONTAINER...]

Kill one or more running containers

Options:
      --help       Print usage
  -s, --signal string   Signal to send to the container (default "KILL")

3 Examples

3.1 Example 1

A simple Go signal handler:

➜  ~ cat signals.go
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 (exec CMD):

FROM busybox

COPY signals /signals

CMD ["/signals"]    # exec format

Running the container and stopping it:

➜  ~ docker run -it --rm --name signals signals
awaiting signal
terminated
exiting
➜  ~ time docker stop signals
signals
docker stop signals  0.01s user 0.02s system 4% cpu 0.732 total

Graceful shutdown occurs in ~0.73 s.

Changing CMD to shell format:

FROM busybox

COPY signals /signals

CMD /signals        # shell format
➜  ~ docker run -it --rm --name signals signals
awaiting signal
➜  ~ time docker stop signals
signals
docker stop signals  0.01s user 0.01s system 0% cpu 10.719 total

Without signal forwarding, the container is force‑killed after the default 10 s timeout.

3.2 Example 2

When the program is started via a shell script, signals still need to be forwarded. Dockerfile:

FROM busybox

COPY signals /signals
COPY start.sh /start.sh   # add shell script

CMD ["/start.sh"]
#!/bin/sh
/signals

Running this yields the same problem as the shell CMD case. Adding exec in the script fixes it:

#!/bin/sh
exec /signals   # forward signals
➜  ~ docker run -it --rm --name signals signals
awaiting signal
terminated
exiting
➜  ~ time docker stop signals
signals
docker stop signals  0.02s user 0.02s system 4% cpu 0.744 total

Conclusion: to achieve graceful container shutdown, always use the exec form for ENTRYPOINT/CMD, and if a shell script is involved, prepend exec so that signals reach the actual process.

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.

DockerGoGraceful Shutdown
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

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.