Any process executed by docker run must handle a signal itself to receive that signal.

Alternatively use the --init flag to run the tini init as PID 1 which will handle and forward signals for you.

The sh shell can also be injected as the PID 1 process in the container, depending on how the command (CMD) is specified.

Detail

A SIGTERM is propagated by the docker run command to the Docker daemon by default but it will not take effect unless the signal is specifically handled in main process being run by Docker.

The first process you run in a container will have PID 1 in that containers PID namespace. This process is treated as a special process by the linux kernel. It will not be sent a signal unless the process has a handler installed for that signal. It is also PID 1's job to forward signals onto other child processes.

docker run and other commands are API clients for the Docker Remote API hosted by the docker daemon. The docker daemon runs as a seperate process and is the parent for the commands you run inside a container context. This means that there is no direct sending of signals between docker run and the daemon, in the standard unix manner.

The docker run and docker attach commands have a --sig-proxy flag that defaults signal proxying to true. You can turn this off if you want.

docker exec does not proxy signals.

In a Dockerfile, be careful to use the "exec form" when specifying CMD and ENTRYPOINT defaults, if you don't want sh to become the PID 1 process (Kevin Burke):

CMD [ "/path/executable", "param1", "param2" ]

Signal Handling Go Example

Using the sample Go code here: https://gobyexample.com/signals

Run a regular process that doesn't handle signals in the background. Then the example Go daemon that traps signals also in the background. I'm using sleep for the process that doesn't install "daemon" signal handlers.

$ docker run busybox sleep 6000 &
$ docker run gosignal &

With a ps tool that has a "tree" view, you can see the two distinct process trees. One for the docker run process under sshd. The other for the actual container processes, under docker daemon.

$ pstree -p
init(1)-+-VBoxService(1287)
        |-docker(1356)---docker-containe(1369)-+-docker-containe(1511)---gitlab-ci-multi(1520)
        |                                      |-docker-containe(4069)---sleep(4078)
        |                                      `-docker-containe(4638)---main(4649)
        `-sshd(1307)---sshd(1565)---sshd(1567)---sh(1568)-+-docker(4060)
                                                          |-docker(4632)
                                                          `-pstree(4671)

The details of docker hosts processes:

$ ps -ef | grep "docker r\|sleep\|main"
docker    4060  1568  0 02:57 pts/0    00:00:00 docker run busybox sleep 6000
root      4078  4069  0 02:58 ?        00:00:00 sleep 6000
docker    4632  1568  0 03:10 pts/0    00:00:00 docker run gosignal
root      4649  4638  0 03:10 ?        00:00:00 /main

Killing

I can't kill the docker run busybox sleep command:

$ kill 4060
$ ps -ef | grep 4060
docker    4060  1568  0 02:57 pts/0    00:00:00 docker run busybox sleep 6000

I can kill the docker run gosignal command, that has launched the go process with the signal handlers:

$ kill 4632
$ 
terminated
exiting

[2]+  Done                       docker run gosignal

Signals via docker exec

If I docker exec a new sleep process in the already running sleep container, I can send an ctrl-c and interrupt the docker exec itself, but that doesn't forward to the actual process:

$ docker exec 30b6652cfc04 sleep 600
^C
$ docker exec 30b6652cfc04 ps -ef
PID   USER     TIME   COMMAND
    1 root       0:00 sleep 6000   <- original
   97 root       0:00 sleep 600    <- execed still running
  102 root       0:00 ps -ef
Answer from Matt on Stack Overflow
Top answer
1 of 3
26

Any process executed by docker run must handle a signal itself to receive that signal.

Alternatively use the --init flag to run the tini init as PID 1 which will handle and forward signals for you.

The sh shell can also be injected as the PID 1 process in the container, depending on how the command (CMD) is specified.

Detail

A SIGTERM is propagated by the docker run command to the Docker daemon by default but it will not take effect unless the signal is specifically handled in main process being run by Docker.

The first process you run in a container will have PID 1 in that containers PID namespace. This process is treated as a special process by the linux kernel. It will not be sent a signal unless the process has a handler installed for that signal. It is also PID 1's job to forward signals onto other child processes.

docker run and other commands are API clients for the Docker Remote API hosted by the docker daemon. The docker daemon runs as a seperate process and is the parent for the commands you run inside a container context. This means that there is no direct sending of signals between docker run and the daemon, in the standard unix manner.

The docker run and docker attach commands have a --sig-proxy flag that defaults signal proxying to true. You can turn this off if you want.

docker exec does not proxy signals.

In a Dockerfile, be careful to use the "exec form" when specifying CMD and ENTRYPOINT defaults, if you don't want sh to become the PID 1 process (Kevin Burke):

CMD [ "/path/executable", "param1", "param2" ]

Signal Handling Go Example

Using the sample Go code here: https://gobyexample.com/signals

Run a regular process that doesn't handle signals in the background. Then the example Go daemon that traps signals also in the background. I'm using sleep for the process that doesn't install "daemon" signal handlers.

$ docker run busybox sleep 6000 &
$ docker run gosignal &

With a ps tool that has a "tree" view, you can see the two distinct process trees. One for the docker run process under sshd. The other for the actual container processes, under docker daemon.

$ pstree -p
init(1)-+-VBoxService(1287)
        |-docker(1356)---docker-containe(1369)-+-docker-containe(1511)---gitlab-ci-multi(1520)
        |                                      |-docker-containe(4069)---sleep(4078)
        |                                      `-docker-containe(4638)---main(4649)
        `-sshd(1307)---sshd(1565)---sshd(1567)---sh(1568)-+-docker(4060)
                                                          |-docker(4632)
                                                          `-pstree(4671)

The details of docker hosts processes:

$ ps -ef | grep "docker r\|sleep\|main"
docker    4060  1568  0 02:57 pts/0    00:00:00 docker run busybox sleep 6000
root      4078  4069  0 02:58 ?        00:00:00 sleep 6000
docker    4632  1568  0 03:10 pts/0    00:00:00 docker run gosignal
root      4649  4638  0 03:10 ?        00:00:00 /main

Killing

I can't kill the docker run busybox sleep command:

$ kill 4060
$ ps -ef | grep 4060
docker    4060  1568  0 02:57 pts/0    00:00:00 docker run busybox sleep 6000

I can kill the docker run gosignal command, that has launched the go process with the signal handlers:

$ kill 4632
$ 
terminated
exiting

[2]+  Done                       docker run gosignal

Signals via docker exec

If I docker exec a new sleep process in the already running sleep container, I can send an ctrl-c and interrupt the docker exec itself, but that doesn't forward to the actual process:

$ docker exec 30b6652cfc04 sleep 600
^C
$ docker exec 30b6652cfc04 ps -ef
PID   USER     TIME   COMMAND
    1 root       0:00 sleep 6000   <- original
   97 root       0:00 sleep 600    <- execed still running
  102 root       0:00 ps -ef
2 of 3
8

So there are two factors at play here:

1) If you specify a string for an entrypoint, like this:

ENTRYPOINT /go/bin/myapp

Docker runs the script with /bin/sh -c 'command'. This intermediate script gets the SIGTERM, but doesn't send it to the running server app.

To avoid the intermediate layer, specify your entrypoint as an array of strings.

ENTRYPOINT ["/go/bin/myapp"]

2) I built the app I was trying to run with the following string:

docker build -t first-app .

This tagged the container with the name first-app. Unfortunately when I tried to rebuild/rerun the container I ran:

docker build .

Which didn't overwrite the tag, so my changes weren't being applied.

Once I did both of those things, I was able to kill the process with ctrl+c, and bring down the running container.

🌐
Medium
lucaspin.medium.com › where-is-my-sigterm-docker-ff7fd8aec757
Where is my SIGTERM, Docker?. This issue has robbed me of a good half… | by Lucas Pinheiro | Medium
January 21, 2023 - Here’s what my Dockerfile looked like: FROM ubuntu:20.04 # ... a lot of commands not relevant to the issue at hand ... # Copy the agent binary to the container COPY build/agent /app/agent # Start the container using the agent binary CMD /app/agent start --config-file /app/config/semaphore-agent.ymldocker · When the Kubernetes deployment is created, the agent starts just fine. In an ideal world, when the agent is interrupted, it should receive a SIGTERM signal and then disconnect from the API before completely shutting down.
Discussions

signals - Process not receiving SIGTERM in Docker container - Unix & Linux Stack Exchange
I've got a simple Python process running in a Docker container: Dockerfile: FROM ubuntu:18.04 RUN apt -y update && apt -y install python3 COPY app.py /app/ WORKDIR /app ENTRYPOINT ["./... More on unix.stackexchange.com
🌐 unix.stackexchange.com
[BUG] Inconsistent handling of SIGTERM based on whether shell is interactive
Version: 0.20.0 Path: ... Experimental: false Insecure Registries: hubproxy.docker.internal:5555 127.0.0.0/8 Live Restore Enabled: false · I haven't tested this extensively, but it appears that SIGTERM also doesn't terminate the running command; this, along with ... More on github.com
🌐 github.com
13
August 12, 2023
bash - How to catch SIGTERM properly in Docker? - Stack Overflow
However, it does not work right when I run docker stop on the container. How can I make my shell script properly forward the SIGTERM it is supposedly receiving? More on stackoverflow.com
🌐 stackoverflow.com
Received a SIGTERM signal for docker container
Okay can you list what you have installed on the server other than jellyfin and can you check for any crontabs that could be acting up You also should check what plugins you have and what scheduled tasks are enabled in jellyfin that ran taking less than 0 seconds to complete More on reddit.com
🌐 r/jellyfin
4
2
May 11, 2023
🌐
OneUptime
oneuptime.com › home › blog › how to handle docker container graceful shutdown and signal handling
How to Handle Docker Container Graceful Shutdown and Signal Handling
January 16, 2026 - When Docker stops a container, it sends SIGTERM by default, waits for a grace period, then sends SIGKILL. If your application doesn't handle SIGTERM properly, it gets forcefully terminated, potentially losing in-flight requests, corrupting data, ...
🌐
Hynek
hynek.me › articles › docker-signals
Why Your Dockerized Application Isn’t Receiving Signals
June 19, 2017 - In principle it’s really simple: when you – or your cluster management tool – run docker stop, Docker sends a configurable signal to the entrypoint of your application; with SIGTERM being the default.
🌐
CloudBees
cloudbees.com › blog › trapping-signals-in-docker-containers
Trapping Signals in Docker Containers
May 4, 2015 - There are essentially two commands that you can use to stop it: docker stop and docker kill. Behind the scenes, docker stop stops a running container by sending it SIGTERM signal, letting the main process process it and, after a grace period, using SIGKILL to terminate the application.
🌐
Docker
docs.docker.com › reference › docker container › docker container stop
docker container stop | Docker Docs
I'm Gordon, your AI assistant for Docker and documentation questions. ... You've reached the maximum of questions per thread. For better answer quality, start a new thread. Start a new thread · Answers are generated based on the documentation. ... The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL.
🌐
Kmg
kmg.group › posts › 2022-05-23-docker-stop-containers-with-signals
Docker: stop containers with the proper signals - Blog - KMG Group
May 23, 2022 - The default behavior is that docker sends the SIGTERM signal to the entry point process (normally with process id 1 in the container). If the container is still running after 10 seconds, docker stop and docker-compose down will send the SIGKILL ...
Find elsewhere
🌐
GitHub
github.com › docker › compose › issues › 10898
[BUG] Inconsistent handling of SIGTERM based on whether shell is interactive · Issue #10898 · docker/compose
August 12, 2023 - When docker compose up is run interactively from a shell, then pressing Ctrl+C (which sends a SIGINT signal to the Docker process) will have the same effect as if I run docker compose stop on the same config file from another terminal - the ...
Author   docker
🌐
Medium
medium.com › @gchudnov › trapping-signals-in-docker-containers-7a57fdda7d86
Trapping signals in Docker containers | by Grigorii Chudnov | Medium
May 24, 2019 - Behind the scenes, `docker stop` stops a running container by sending it SIGTERM signal, let the main process process it, and after a grace period uses SIGKILL to terminate the application.
🌐
Reddit
reddit.com › r/jellyfin › received a sigterm signal for docker container
r/jellyfin on Reddit: Received a SIGTERM signal for docker container
May 11, 2023 -

Greetings,
I have jellyfin server running as a docker container (rather a podman container) and I have been using it for months now but just this evening, I have tried to watch something off of it and every few minutes, the jellyfin service in the container will shutdown and restart.

The following is the only thing that I can see in the logs that appears to be related `Main: Received a SIGTERM signal, shutting down`. I am not sure how to diagnose this issue since that appears to be the only log entry in both docker and in the server that relates to this issue. Is there something I am missing?

The thing that perplexes me is that this issue just came out of nowhere. I tried updating the whole system as well as the container but the issue persists

🌐
OneUptime
oneuptime.com › home › blog › how to use the stopsignal instruction in dockerfiles
How to Use the STOPSIGNAL Instruction in Dockerfiles
February 8, 2026 - FROM python:3.11-slim WORKDIR /app COPY app.py . # SIGTERM is the default, which matches our signal handler STOPSIGNAL SIGTERM CMD ["python", "-u", "app.py"] The -u flag makes Python output unbuffered so you can see the shutdown messages in docker logs.
🌐
GitHub
github.com › docker › cli › issues › 5241
`SIGTERM` sent to `docker run` command doesn't send a SIGTERM to the main process running inside the container anymore. · Issue #5241 · docker/cli
July 8, 2024 - Description In Docker version 26.1.4, sending a SIGTERM to the docker run process would also send a SIGTERM to the main process inside the container. However, in version 27.0.3, this behavior has changed. Now, the process exits with the ...
Author   docker
Top answer
1 of 6
9

Actually, your Dockerfile and start.sh entrypoint script work as is for me with Ctrl+C, provided you run the container with one of the following commands:

  • docker run --name tmp -it tmp
  • docker run --rm -it tmp

Documentation details

As specified in docker run --help:

  • the --interactive = -i CLI flag asks to keep STDIN open even if not attached
    (typically useful for an interactive shell, or when also passing the --detach = -d CLI flag)
  • the --tty = -t CLI flag asks to allocate a pseudo-TTY
    (which notably forwards signals to the shell entrypoint, especially useful for your use case)

Related remarks

For completeness, note that there are several related issues that can make docker stop take too much time and "fall back" to docker kill, which can arise when the shell entrypoint starts some other process(es):

  • First, when the last line of the shell entrypoint runs another, main program, don't forget to prepend this line with the exec builtin:
    exec prog arg1 arg2 ...
  • But when the shell entrypoint is intended to run for a long time, trapping signals (at least INT / TERM, but not KILL) is very important;
    {see also this SO question: Docker Run Script to catch interruption signal}
  • Otherwise, if the signals are not forwarded to the children processes, we run the risk of hitting the "PID 1 zombie reaping problem", for instance
    {see also this SO question for details: Speed up docker-compose shutdown}
2 of 6
6

The reason is that bash will not handle signals until the foreground process terminates, the sleep 10000 in your case. Your traps do work, but you would have to wait 10000 seconds first.

Your fix is

#!/bin/bash
pid=
trap 'echo SIGINT; [[ $pid ]] && kill $pid; exit' SIGINT
trap 'echo SIGTERM; [[ $pid ]] && kill $pid; exit' SIGTERM
echo Starting script
sleep 10000 & pid=$!
wait
pid=

Also you probably want to replace trapping specific signals with trapping any exit

trap 'echo EXIT; [[ $pid ]] && kill $pid; exit' EXIT

These and more are very well explained at http://mywiki.wooledge.org/SignalTrap#When_is_the_signal_handled.3F

🌐
Semisignal
semisignal.com › sigterm-and-pid1
SIGTERM and PID1 | semi/signal
November 3, 2024 - If the process running is an application or script where the source code is available, the solution is obvious, write a handler for SIGTERM. If writing a handler isn’t possible, add an init program to the container and use it to run the application or script. tini works great here, as it’s lightweight and designed for containers (it’s also what docker run uses when the --init flag specified).
🌐
DEV Community
dev.to › lucaspin › where-is-my-sigterm-docker-1n30
Where is my SIGTERM, Docker? - DEV Community
January 21, 2023 - Here's what my Dockerfile looked like: FROM ubuntu:20.04 # ... a lot of commands not relevant to the issue at hand ... # Copy the agent binary to the container COPY build/agent /app/agent # Start the container using the agent binary CMD /app/agent start --config-file /app/config/semaphore-agent.yml · When the Kubernetes deployment is created, the agent starts just fine. In an ideal world, when the agent is interrupted, it should receive a SIGTERM signal and then disconnect from the API before completely shutting down.
🌐
OneUptime
oneuptime.com › home › blog › how to set up docker container signal handling
How to Set Up Docker Container Signal Handling
January 25, 2026 - sequenceDiagram participant D as Docker participant P as PID 1 (Container) participant A as Application D->>P: SIGTERM P->>A: Forward SIGTERM A->>A: Graceful shutdown A->>P: Exit P->>D: Container stopped Note over D,A: If timeout (10s default) D->>P: SIGKILL Note over P: Immediate termination
Top answer
1 of 1
9

I can reproduce the issue you raise, while it does not show up when I replace the base image with debian:10, for example.

It happens the issue is not due to alpine but to the gitlab/gitlab-runner:alpine image itself, namely this Dockerfile contains the following line:

STOPSIGNAL SIGQUIT

To be more precise, the line above means docker stop will send a SIGQUIT signal to the running containers (and wait for a "graceful termination time" before killing the containers, as if a docker kill were issued in the end).

If this Dockerfile directive is not used, the default signal sent by docker stop is SIGTERM.

Beware that SIGKILL would be a very poor choice for STOPSIGNAL, given that the KILL signal cannot be trapped.

So, your first example should work if you use the following line:

trap deregister_runner SIGINT SIGQUIT SIGTERM

This way, your cleanup function deregister_runner will be triggered anytime you issue a docker stop, or use the Ctrl-C keybinding (thanks to SIGINT).

Finally, two additional notes related to this question of Docker, bash and signals:

  • The "graceful termination time" (between stop and kill) can be customized, and there are some pitfalls when using a Bash entrypoint (regarding the "signal propagation"). I explained both issues in more detail in this SO answer: Speed up docker-compose shutdown.

  • Beware that in many alpine images, bash is not pre-installed, e.g.:

    $ sudo docker run --rm -it alpine /bin/bash
      /usr/bin/docker: Error response from daemon: OCI runtime create failed:
      container_linux.go:346: starting container process caused
      "exec: \"/bin/bash\": stat /bin/bash: no such file or directory": unknown.
    

    (fortunately, this was not the case of gitlab/gitlab-runner:alpine, which indeed contains the bash package :)

🌐
OneUptime
oneuptime.com › home › blog › how to fix docker container exit code 143 (sigterm)
How to Fix Docker Container Exit Code 143 (SIGTERM)
February 8, 2026 - The math is 128 + 15 = 143. Unlike exit code 137 (SIGKILL), which is a forceful kill, SIGTERM is a polite request to shut down. The process has the chance to clean up before exiting. Exit code 143 is not inherently a problem. It is the normal result of docker stop.