Basic setup for s6 and s6-overlay in Docker

Posted on May 11, 2017

Nearly all of our containers are running supervisor as the main process. While it’s been solid and reliable, I’d like to move past it to something with less dependencies. In this case, we’re not using Python at all, so having to have both Python 2 and 3 installed seems superfluous. Enter s6, skarnet’s supervisor suite, and s6-overlay, an implementation of s6 that adds some functionality for containers. Here’s how I implemented it.

Unlike supervisor, s6 uses a folder structure to control services. In a pure s6 installation you would put the files wherever, but s6-overlay asks that you put them in /etc/services.d before running init (it then copies them over to /var/run/s6/services). So here’s what I’ve got as my structure:

services
    └ fpm
        └ run
    └ nginx
        └ run

Now, each of those run files is an executable that s6 executes to start the process. I’ve found that you MUST use exec as part of the script to get the process hierarchy right. If you don’t use exec, then you end up with a structure where s6 monitors the executable, and the actual process is a child of the executable. Not good.

Here’s my script for nginx:

#!/usr/bin/with-contenv sh
exec nginx

with-contenv is a script specific to s6-overlay that brings in the current environment variables. You could probably use /usr/bin/env in its place for regular s6.

For entrypoint, you have a few options. You could do all the above setup in Dockerfile and then just have your entrypoint set to /init. For us, though, we have a lot of stuff to set up first, so we have an entrypoint.sh that runs. It’s pretty straightforward:

# Dockerfile
RUN curl -sL -o /tmp/s6-overlay-amd64.tar.gz https://github.com/just-containers/s6-overlay/releases/download/v1.19.1.1/s6-overlay-amd64.tar.gz && \
    tar zxf /tmp/s6-overlay-amd64.tar.gz -C /

ADD entrypoint /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
# /entrypoint.sh
#!/bin/bash
# .. do other stuff
exec /init

Note the exec, which swaps /init in as pid, a necessary action to make sure that Docker controls s6. If this were pure s6, you’d use s6-svscan /path/to/services as your init.

To control the services themselves, you use the s6-svc binary. It has a lot of controls and abilities, but the basic idea is that you give it the directory of the service. So for example, if I wanted to send SIGHUP to nginx, I would do s6-svc -h /var/run/s6/services/nginx. A few points here: first, this is for s6-overlay; second, note that it’s /var/run and not /etc/services.d; and third, the -h is for SIGHUP. If this were pure s6, it would be the same, but the folder you point to would be where you put the services. That’s probably obvious, but it seems worth stating.

Oh, one other big caveat. s6-overlay comes with a number of built versions, so you can download the one that matches your Linux setup. If you want to use s6 directly, users of Alpine and a few other flavors of Linux can just install it from their package manager. We’re running Debian and there’s no PPA for it, so we would have to compile s6 on our own. Not particularly difficult, but I don’t really want that in my Dockerfile, so I’d recommend having a different Docker setup to build s6 and export the executables, and then add them into my normal container.

Hopefully this helps someone so they don’t have to rack their brain trying to figure this out.

Leave a Reply

Your email address will not be published. Required fields are marked *