Operations 10 min read

How to Gracefully Shut Down Docker Containers Using Signals, ENTRYPOINT, and CMD

This article explains Linux signal fundamentals, common signal types, and how Docker's ENTRYPOINT and CMD directives affect signal propagation, demonstrating with Go examples why using the exec form and proper script handling enables containers to terminate gracefully instead of being force‑killed.

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

After rereading Docker's official reference, I realized many details about graceful container shutdown remain worth exploring. While a Dockerfile often works by copying examples, understanding signals and the ENTRYPOINT/CMD directives makes the process more interesting.

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:

# 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; forces termination.

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

SIGTSTP – sent by Ctrl‑Z to stop a job.

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

Programs can catch signals and run custom handlers:

Most of this knowledge comes from the "Linux/UNIX System Programming Manual" (chapters 20‑22).

2. ENTRYPOINT and CMD

ENTRYPOINT and CMD specify the program that runs when a container starts.

CMD has three forms:

Exec form: CMD ["executable","param1","param2"] (recommended).

Parameter form: CMD ["param1","param2"] (used as arguments to ENTRYPOINT).

Shell form: CMD command param1 param2 (runs via /bin/sh -c).

ENTRYPOINT also has two forms:

Exec form: ENTRYPOINT ["executable","param1","param2"] (recommended).

Shell form: ENTRYPOINT command param1 param2.

Regardless of which directive you use, the exec form is preferred because the shell form launches the command via /bin/sh -c, which does not forward signals to the program. Consequently, docker stop cannot deliver signals, preventing graceful shutdown.

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)

By default, docker stop sends SIGTERM and waits 10 seconds before sending 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")

The -s option lets you specify a different signal.

3. Examples

3.1 Example 1

A simple Go signal handler:

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 exec form:

FROM busybox

COPY signals /signals

CMD ["/signals"]    # exec format

Running the container and stopping it with docker stop shows the signal being received and the shutdown completing in ~0.73 s.

Changing CMD to shell form:

FROM busybox

COPY signals /signals

CMD /signals    # shell format

Now docker stop takes the full 10 s timeout because the program never receives the signal.

3.2 Example 2

If the program is started via a shell script, the signal still does not reach it unless the script uses exec:

# Dockerfile
FROM busybox

COPY signals /signals
COPY start.sh /start.sh

CMD ["/start.sh"]

# start.sh
#!/bin/sh

/signals

Running this container also results in a forced kill.

Fix by adding exec in the script:

# start.sh
#!/bin/sh

exec /signals   # forward signals

After rebuilding, docker stop again shuts down gracefully in ~0.74 s.

Conclusion: to achieve graceful container termination, always use the exec form for ENTRYPOINT/CMD and, when invoking shell scripts, prepend 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.

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