Trifork Blog

Using Supervisor with Docker to manage processes (supporting image inheritance)

March 11th, 2014 by


In August I wrote a blog post on the creation of tomcat images. Since then, Docker has evolved much, and so has my own knowledge of it. I’d like to update you on what I found to be a good way to manage container processes. After reading this article, I hope you will find good use of the Supervisor image that you can find in my repository on github.

The Docker command

One of the points that came up in aforementioned post was that Docker (only) supports running a single foreground process. We are generally accustomed to something like upstart to have services be initialized at start up, but Docker does not run anything by default, which may be somewhat unexpected if you start out with Docker. You have to specify the proces you want to run. This behavior is contrary to that of a virtual machine an the advantage is that this keeps the container as lightweight as possible. You can start the container specifying the command option at the end of the run command, e.g,

docker run ubuntu echo "hello world"

Alternatively, you can use the CMD directive in a Dockerfile to have Docker run a command by default. E.g. if you build the image hello_world_printer using

docker build -t "hello_world_printer" .

from the directory containing the Dockerfile

FROM ubuntu
CMD echo "hello world"

you would achieve  exactly the same by running

docker run hello_world_printer

Note that, since you can override the CMD directive command line, this really is a run time directive. Fun fact is that on a Linux container you could just invoke upstart and get much of the same behavior as a regular virtual machine.

Running multiple commands

A common wish is to run multiple processes. E.g. an ssh server (to be able to connect to the running container) and your actual application. You could run the container with something like

docker run ... /usr/sbin/sshd && run_your_app_in_foreground

which might come in handy during development. Now, when the application process quits, the container is automatically shut down since that was our only foreground process. While you can fix that using /usr/bin/sshd -D the important point here is that setting up the initialization in the run command is not very neat. And, when your container becomes more complicated, the run command tends to grow.

So, to run more complicated containers, many people use complicated bash scripts. The bash script typically runs a foreground process and often spawns one or more (renegade) daemon processes. A major improvement of this approach over only using the Docker CLI is that the bash scripts are source controlled: the start-up scripts live in your Docker image, which is the new deliverable of you software project. Still, managing processes in bash scripts is simply no fun at all and prone to errors.

… using Supervisor

A better way is to use Supervisor. Supervisor allows for better control of our processes: it is very simple and clear code; it can restart processes after a crash; it allows for restartable process groups and a command line or web interface to manage processes. Of course, with great power comes great responsibility: extensively using Supervisor features is a code smell indicating that you might be better off chopping up your image into several smaller ones.

Personally, I like how Supervisor allows me to cleanly code the process start-up. The clearest use case as I see it arises when child images expand the process set. For instance, if you often use SSH, it might be logical to have an SSH base image. In that case, implementing start-up of the SSH daemon in all the extending images is a form of code duplication. I’ll now show you what I found to be a good way to approach this.

The Supervisor base image

First of all, since I am now using Supervisor by default all my images extend from a base image containing only Supervisor and an updated version of Ubuntu. You can find the Dockerfile here. This base image contains a configuration file /etc/supervisor.conf:


files = /etc/supervisor/conf.d/*.conf

This configuration makes Supervisor itself run as a foreground process, which will keep our containers up and running. Secondly, it includes all conf files in the /etc/supervisor/conf.d/ directory, starting whatever programs are defined in there.

Extending the base image


So, the idea is simple. All child containers add their service to the supervisor configuration by adding their specific to the specified directory. Then, starting the container using

docker run child_image_name "supervisor -c /etc/supervisor.conf"

automatically starts all the specified processes. You can extend the image with multiple layers, each time having the option of adding one or more services to the configuration directory. Effectively, the Supervisor start command replaces upstart in a Docker compliant fashion.

As an example, let’s take a look at the Tomcat stack from aforementioned blog post, which is now updated.

  1. Firstly, as discussed, we have the Supervisor base image extending from Ubuntu
  2. Then, we have a JDK image, which installs Java on top of Supervisor. Java is just a library for other services, so we do not define a service in this layer. Common tasks as setting the JAVA_HOME environment variable are the responsibility of this layer.
  3. The Tomcat image install Tomcat onto the stack and exposes port 8080. This layer does include a service, namely Tomcat, which is defined in
    command=/bin/bash -c "env > /tmp/tomcat.env && cat /etc/default/tomcat7 >> /tmp/tomcat.env && mv /tmp/tomcat.env /etc/default/tomcat7 && service tomcat7 start"

    The run command for the Tomcat service is not as clean as I’d like it to be and could very well be put in a dedicated script. What it does is prepending the environment variables, such as container linking parameters, to /etc/default/tomcat7, so we can use those in following configuration lines as in the following example. It might be more neat to use an available key-value store such as etcd, but that is beyond the scope of this article.
    Of course, we only have the default installation files in here, and no actual web application yet.

  4. Your web application would extend Tomcat with an actual application installation. When starting Supervisor, it will automatically start Tomcat.

Example Tomcat webapp Dockerfile

An actual web application installation is beyond the scope of this article, but as a finishing touch, let me give a partial example Dockerfile of how to use this stack. This example is pretty Java Tomcat specific, so if you’re not interested in that, stop reading now and have fun with other stuff 🙂

Suppose, we have a web app that uses Elasticsearch:

FROM quintenk/tomcat:7

# Install dependencies for project that do not change per check-in
# RUN apt-get -y install ...

RUN rm -rf /var/lib/tomcat7/webapps/*

# append configuration to /etc/default/tomcat7, such as:
RUN echo 'CATALINA_OPTS="... ${DOCKER_OPTS}"' >> /etc/default/tomcat7

# add configuration files such as and chown root:tomcat7 them

# Assume the project has been built and that ROOT.war is in our Docker build directory (containing the Dockerfile). For caching purposes, add this as one of the last steps
ADD ROOT.war /var/lib/tomcat7/webapps/
RUN chown root:tomcat7 /var/lib/tomcat7/webapps/ROOT.war

CMD supervisord -c /etc/supervisor.conf

In this code, the variables for elasticsearch (a search index), are set because the Supervisor configuration for Tomcat prepends all variables to the /etc/default/tomcat7 file at start-up time. Of course, we would need to start the webapp with a link to the elasticsearch container: e.g.

docker run -link name_of_elasticsearch_instance:elasticsearch -d name_of_webapp_image "supervisor -c /etc/supervisor.conf"

You now end up with a web application that has access to the ELASTICSEARCH_SERVER_URL. You can use this in a properties file like


which exposes the property to your application. If you are a Java developer and have read through the last section as well, this is the time where I wish you happy coding!

7 Responses

  1. June 7, 2014 at 21:17 by Stefan Schöffmann

    Man – you really made my day!!! I was struggling with docker, boot2docker and vagrant for a bunch of days now. Every single tutorial I’ve found in the web led me to a dead end. Your explanation of the supervisord-hierarchie along with your github repo (and the code in there) really saved me.

    Thx for sharing !!!

  2. July 2, 2014 at 11:46 by Paul

    great article, but one question, can i use supervisor to launch a daemon process?, i cant seem to get this to work correctly, supervisor still exits and doesn’t allow the daemon process to continue, is there any way of getting supervisor to be “aware” of the daemon process and thus not terminate?.

    • July 15, 2014 at 15:42 by Quinten Krijger

      Hi Paul,
      not according to my knowledge. I also searched for this, but the supervisor documentation clearly states that supervisor manages front processes – it uses that to monitor the processes. Most processes can be started either way. Sometimes, you need to jump some hoops. If you really can’t get it to work, you might want to ask the supervisor project itself. I also remember there is a list with examples of getting popular services to run as front processes, but I cannot find it any more – maybe you have better luck.

  3. July 10, 2014 at 10:48 by Fredrik Wendt

    Supervisor don’t want processes to daemonize – it won’t be able to, with same accuracy, control the job it’s configued to supervise.

  4. July 11, 2014 at 19:02 by montells

    Ok, very well, with this strategy all process you want or you need will be working thanks to supervisor. But what do you suggest for keep working the container it self.

    I mean your container is runnning because supervisor process is running and it keep running your process inside. But if something (i dont know what) stop the container ex, (docker stop ) what can we do for it start automatically. see

  5. July 16, 2014 at 11:08 by Quinten Krijger

    Hi Montells,
    I see the answer on your stackoverflow post looks good 🙂

  6. December 12, 2014 at 13:55 by Tarun

    Cann’t start supervisor with Docker run command!