Let a Docker container spin & stop too

Photo by Jad Limcaco on Unsplash

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 tailexits 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.shcan 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

#DevOps, #Security, Go (#golang), #Linux, #Docker, #Kubernetes. DevOps Manager @Xero. Previously: DevTools Engineering Manager @Cloudflare. Opinions are my own.

#DevOps, #Security, Go (#golang), #Linux, #Docker, #Kubernetes. DevOps Manager @Xero. Previously: DevTools Engineering Manager @Cloudflare. Opinions are my own.