One concept people often overlook about Docker is that there can only be one process running at a time in Docker, and when this process dies, the container stops. What that means is you can not simply start daemons in your container using the usual /etc/init.d/nginx start for example, because while nginx will effectively start, it’s going to start in the background and your primary process will exit, stopping the container by the same occasion.

So how do you get multiple services to run in a Docker container? There are several ways, but it helps to see your initial process as an init process since after all, that is what it is. By leveraging this concept and using a process manager, we can run all our services and make sure they keep running, restarting them if necessary.

To make things simpler, we are going to use Docker’s Ubuntu base image, more precisely, the precise one (pun intended, sorry for that), so let’s pull it from the public index and open up a shell in a new container:

$ docker pull ubuntu:precise
$ docker run -i -t run ubuntu:precise /bin/bash

Everything should work exactly the same with more recent versions of ubuntu, but precise is the version I tested, so that will be the one I talk about.

Installing and configuring services

First things first, install OpenSSH:

$ apt-get install -y openssh-server

We are going to use daemontools in this article. The great thing about daemontools is that it follows the UNIX tooling philosophy by not doing too much at once. Instead, it provides a set of tools that can be used together to achieve great control on how things run on your system.

There are a few other process managers to chose from and you could use anything from supervisord to monit, but debating the pros and cons of them is beyond the scope of this tutorial.

There is an ubuntu package for daemontools, but it’s in the universe package repository, so you will need to add it and update apt-get first:

$ echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
$ apt-get update

You can now install daemontools:

$ apt-get install -y daemontools

Daemontools expects services to be defined each in their own directory. Each service directory must contain an executable file named run. This executable can be anything that actually is executable, a binary compiled executable, a shell script, a ruby/python/whatever executable script, you name it. The only requirement is that it is executable (using chmod +x).

We will store our services definitions in /etc/services, so create that directory if it does not already exist…

$ mkdir /etc/services

…and since we want to monitor an sshd process, create an sshd directory in it…

$ mkdir /etc/services/sshd

…and finally create a run script in this directory with this content:

#!/bin/bash
exec /usr/bin/sshd

You might want to install an editor to create the file, or you could just as well use bash’s heredoc syntax with cat:

$ cat > /etc/services/sshd/run <<EOF
> #!/bin/bash
> exec /usr/sbin/sshd
> EOF

The first line, #!/bin/bash is called a shebang and tells the system what interpreter to use when executing this file. exec is a bash builtin that replaces the current shell with the supplied command.

Do not forget to make this script executable:

$ chmod +x /etc/services/sshd/run

We are now ready to run svscan, which is daemontools’ utility to scan a services directory and start processes:

$ svscan /etc/services

You should have the following output:

Missing privilege separation directory: /var/run/sshd

As the message states, sshd requires a /var/run/sshd directory for something related to privilege separation. That seems important, so let’s stop svscan if not already done (hit ^C) and create the directory:

$ mkdir /var/run/sshd

You can now restart svscan and admire the lack of any output, which means that everything works fine!

Making a reusable image out of it

It’s all well and good that we are able to run an sshd inside our container, but as soon as we will exit the container all this work will be lost. Fortunately, Docker has a couple options for us to persist this work.

First, we can commit the container and obtain a brand new reusable image out of it. The prompt in your container contained your container’s id. For example, if your prompt was something like root@44c1ee41ae70, the container id is 44c1ee41ae70. Another way of finding the id of a stopped container is docker ps -a (the -a option stands for all), used in conjunction with the -n option to limit results (output truncated for clarity):

$ docker ps -a -n 1
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS  
44c1ee41ae70        ubuntu:precise      /bin/bash           9 minutes ago       Exit 130

Commit this container to a new ssh image by using docker commit:

$ docker commit 44c1ee41ae70 ssh

You can now reuse your newly created image with docker run:

$ docker run -i -t ssh /usr/bin/svscan /etc/services

And check that everything works fine from another terminal:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS       
4aa184718760        ssh:latest          /usr/bin/svscan /etc   16 seconds ago      Up 16 seconds
$ docker top 4aa184718760
PID                 TTY                 TIME                CMD
11264               pts/9               00:00:00            svscan
11309               pts/9               00:00:00            supervise
11311               ?                   00:00:00            sshd

Automating things with a Dockerfile

Cherry on the cake, you can easily automate creation of such images with the use of a Dockerfile. A Dockerfile is a Docker configuration file that tells how to build an image in a predictable and repeatable way. Our Dockerfile will look like that:

FROM ubuntu:precise
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y openssh-server daemontools
RUN mkdir -p /etc/services/sshd /var/run/sshd
RUN echo "#!/bin/bash\nexec /usr/sbin/sshd" > /etc/services/sshd/run
RUN chmod +x /etc/services/sshd/run
EXPOSE 22
ENTRYPOINT ["/usr/bin/svscan", "/etc/services/"]

The ENTRYPOINT directive allows to define a command to be run when the container is created, and the EXPOSE tells Docker that we will want access to tcp port 22 of this container.

Instead of an ENTRYPOINT you could define a default CMD for the container. Docker’s official documentation on Dockerfiles explains the difference between both and when you’d want to use one over the other.

Put all that in a Dockerfile and run docker build:

$ docker build - ssh < Dockerfile

And you can now run your container:

$ docker run -P ssh

The -P option tells Docker to publish all EXPOSEd ports, so we get a nicely mapped port to ssh to that we can retrieve using docker port:

$ docker port e56733a0ceaf 22

Where e56733a0ceaf, of course, is the id of your newly created container.

Going the extra mile

As you can see, using daemontools inside a container is fairly easy, and it would be trivial to add more service definitions inside /etc/services/. daemontools can also do a lot more than what we saw in this article, so I can only encourage you to go read its documentation, even if you’re not going to use it, it’s a good example of the UNIX philosophy in action.