Docker Containers Without a Proper Init Process May Take a Long Time to Terminate
Posted on by Gentaro "hibariya" Terada
When stopping a container one way or another, like by Ctrl+C or docker stop
, Docker sends SIGTERM to the process by default. If the first process (PID = 1) on the container is not a proper init process, the termination may take a long time to be done. It's because processes running as PID 1 are treated differently than usual ones.
In Linux, a process running as PID 1 ignores any signal unless the process implements signal handlers on its own. While some programs like ruby
trap signals, many others like tail
do not.
# this will treat signals as usually expected
docker run --rm -it ruby:slim ruby -e STDIN.read
# this will ignore signals and take longer time to terminate
docker run --rm -it ruby:slim tail -f /dev/stdin
The first one (ruby
) in the example above can be stopped by either Ctrl+C or docker stop
because ruby
comes with the signal handlers for SIGINT/SIGTERM out of the box. On the other hand, the second one (tail
) will ignore Ctrl+C and docker stop
will take a much longer time to terminate than the other since it does not handle these signals. By default, docker stop
sends SIGTERM to the process and waits 10 seconds. If the process does not stop in the grace period, then Docker sends SIGKILL to terminate no matter what. In this example, the second one will be killed that way.
The same thing goes on Docker Compose. Sometimes I intentionally run a command that does nothing and not consume many resources like tail -f /dev/null
to keep the container idle for some reasons such as providing a place to run one-off commands. I have written something like that several times for Docker Compose and Kubernetes. These containers never handle signals without a proper init process. It can be reproduced with the following docker-compose.yml
.
services:
idler:
image: debian:bullseye-slim
command: ['tail', '-f', '/dev/null']
If you run docker-compose up
with this file and then hit Ctrl+C, you can see the container takes a certain amount of time to stop.
To fix this problem, we have to use a proper init process as the entry point and run the original process on top of that. One of the easiest way to do that is to pass --init
option to docker
. It tells Docker to run the original entry point on a built-in init process that handles the signals as expected.
# this will handle Ctrl+C and `docker stop` immediately
docker run --rm -it --init ruby:slim tail -f /dev/stdin
In docker-compose.yml
, you can pass init: true
to do that.
services:
idler:
image: debian:bullseye-slim
command: ['tail', '-f', '/dev/null']
init: true