Let a Docker container spin & stop too
Have you ever wanted to keep a Docker container running after it finishes running a script? e.g. if your container exits once your script finishes it will result in rolling restarts (which probably sends you a bunch of alerts — yay!).
Well, good person, I have some shell script magic for you!
Disclaimer: in this scenario, you should always assume that the container may run multiple times (e.g. machine dies and container gets rescheduled elsewhere). Here we just want to prevent it restarting constantly and unnecessarily alerting people.
Important background knowledge
First, some background on Docker & signals:
- Running
docker stop [container]
will (by default) send it a SIGTERM. - After a while, if that doesn’t work, it will send a SIGKILL.
- Running
docker kill [container]
will send a SIGKILL also.
Second, some notes on PID 1 inside a Docker container:
- Docker has two run methods: shell or exec
- When you use exec, your entrypoint is PID 1
- When you use shell, your entrypoint is a child process of PID 1
- PID 1 will receive the SIGTERM
- Docker has some pretty confusing logic which determines whether shell or exec are used based on how you configure your ENTRYPOINT and COMMAND in your Dockerfile.
- I highly recommend reading the official Dockerfile reference section on Understand how CMD and ENTRYPOINT interact, and also ENTRYPOINT vs CMD: Back to Basic by John Zaccone, to better understand how to configure shell vs. exec.
Example Dockerfile
We’re going to keep our example minimal:
FROM alpine:latest
COPY ./run.sh /run.sh
ENTRYPOINT ["/run.sh"]
FROM
= use the latest alpine image as our base.
COPY
= copy our (soon to be unveiled run.sh
script) into the container
ENTRYPOINT
= set the run.sh
to be our entrypoint using exec (so, PID=1)
The run.sh magic
As promised, here is the shell script magic!
#!/bin/sh
echo "Sleeping..."
# Spin until we receive a SIGTERM (e.g. from `docker stop`)
trap 'exit 143' SIGTERM # exit = 128 + 15 (SIGTERM)
tail -f /dev/null & wait ${!}
This script will just print that it’s going to sleep, then the magic will begin.
trap
= this basically says, when we receive SIGTERM, execute exit 123
tail & wait
= so, tail
is going to spin (cheaply) until it’s killed, and wait
is going to tell our shell script to wait until tail
exits before exiting (at which point, the container will exit).
Putting the pieces together
Ok, we have our files, so how does one prove this actually works?
First, from the directory containing both the Dockerfile
and the run.sh
file, build your temp image:
docker build -t temp-image .
Now, run your temp container (in detached mode, so we can call stop afterwards):
docker run --name temp-container -d temp-image
Now, show that it’s running and list the PID’s:
docker top temp-container
UID PID PPID C STIME TTY TIME CMD
root 27913 27896 0 02:16 ? 00:00:00 /bin/sh /run.sh
root 27930 27913 0 02:16 ? 00:00:00 tail -f /dev/null
Great, our run.sh
script is PID 1, and the spinning tail -f /dev/null
is running as a child process that, sorun.sh
can watch it.
Now for the moment of truth! Let’s stop the container:
docker stop temp-container && docker ps -a | grep temp-container
The docker ps
command we ran after docker stop
should show that the container was stopped, and this should all have run super quick. e.g.
Exited (143) Less than a second ago
I hope you found this useful! If you like tinkering with Docker containers or have feedback, please tweet me @ ryan0x44