Stopping Docker Containers in a hurry May 20, 2015

I’ve been working with docker-compose a lot, and it’s a really great tool. I can’t wait to see what they do with it!

However, I found myself doing a lot of docker compose stop, docker compose start. Shutting down each container was taking approximately 10 seconds each, which, with my setup, meant waiting about a minute for a shutdown. This was certainly manageable, but also added up quickly.

So what happens when you call docker stop? The container’s main process is sent a signal, SIGTERM. It’s then given 10 seconds to do any cleanup it wants/needs to, and then it’s sent SIGKILL and forcibly killed.

By default, python does nothing when it gets a SIGTERM, it has the default signal handler installed (SIG_DFL). So, for a simple application, you can turn SIGTERM into an immediate quit by just doing:

import sys
import signal

def handler(signum, frame):
    sys.exit(1)

def main():
    signal.signal(signal.SIGTERM, handler)
    ... your special logic here

And that’s it! Or so I thought. I started the container:

$ docker run -d --name shutdown_test shutdown_test
$ time docker stop shutdown test
real    0m10.367s
user    0m0.136s
sys     0m0.007s

Not awesome. Checking docker ps showed the problem, though:

$ docker ps                                                               
CONTAINER ID        IMAGE                           COMMAND               
272c68f3206e        shutdown_test:latest            "/bin/sh -c ./test.py... 

Docker is actually running sh as the primary process, which is launching my python script. This is because of the CMD entry I was using in Dockerfile.

CMD ./test.py

This version of CMD, as you can see, runs the process under a shell.

Converting to the argument list format fixes this:

CMD ["./test.py"]

There we go:

$ docker ps                                                            
CONTAINER ID        IMAGE                           COMMAND            
8e5ef05d5389        shutdown_test:latest            "./test.py"        

and finally, we get the shutdown times we deserve.

$ time docker stop shutdown_test 
real    0m0.341s                 
user    0m0.132s                 
sys     0m0.007s                 

It took a bit more work to get all the containers in my docker-compose working. Some containers specified different commands in the docker-compose.yml itself, so you can do the same thing there.

badcontainer:
  command: "thescript"
goodcontainer:
  command: ["thescript"]

Also, if you’re using cherrypy, their documentation reveals an important step for handling signals:

if hasattr(cherrypy.engine, 'signal_handler'):
    cherrypy.engine.signal_handler.subscribe()

There are a few containers left which have slow shutdowns, but they are ones I’m grabbing from upstream sources. I’m sure some pull requests will be pending!