What is S6-Overlay?

S6-overlay is a container-focused process manager that offers end-to-end management of the container's lifecycle, from initialization to graceful shutdown. Its innovative design and feature set make it a compelling alternative to other process managers, such as supervisord. But what sets it apart from the rest? Let's explore its key features.

Key Features of S6-Overlay

Versatile Process Management: S6-overlay efficiently handles both one-time tasks and long-running processes, making it versatile for containerized tasks.

Dependency Control: Establish dependencies between processes to ensure orderly execution in complex application stacks.

Sequence Management: Control the start and stop sequence of processes, streamlining container operations.

Environment Variable Templating: Easily customize process behavior with environment variables, adapting to different environments.

Easy Integration: Seamlessly integrate S6-overlay into Docker images with a straightforward installation process.

Log Management: Built-in log rotation simplifies log file management within container environments.

Graceful Shutdown: Ensure data integrity with graceful process shutdown and the ability to execute custom scripts before container shutdown.

Multi-Arch Support: S6-overlay accommodates the diverse landscape of container platforms with support for multi-architecture container images.

Directory Structure of S6-Overlay

S6-overlay's directory structure sets it apart from other process managers, providing flexibility for process management in container environments. The base directory, /etc/s6-overlay/s6-rc, serves as the starting point. To manage processes effectively, create a directory for each process you wish to run. Within these directories, several key files play essential roles:

Type File:

Define the nature of your process by writing either "oneshot" or "longrun." Use "oneshot" for tasks that run as initializations before primary processes. For continuous daemon processes supervised by S6, choose "longrun."

Up File:

In this file, specify the file path of your script. This path guides S6 to run the script. If left blank, S6 will consider the "run" file as your script.

Run File:

Here, you write the script or process that your container executes. If necessary, you can utilize environment variables and the execlineb command for added flexibility.

Dependencies Directory:

The dependencies.d directory plays a crucial role when your process relies on another. To establish dependencies, create empty files named after the processes your task depends on. Multiple files can be created when a process depends on more than one other process.

This directory structure simplifies the organization and execution of processes within your containerized environment, enhancing control and flexibility.

Finally, it will look like below

s6-overlay
└── s6-rc.d
    ├── init-nginx
    │   ├── dependencies.d
    │   │   └── base
    │   ├── run
    │   ├── type
    │   └── up
    ├── init-php82-fpm
    │   ├── dependencies.d
    │   │   └── init-nginx
    │   ├── run
    │   ├── type
    │   └── up
    ├── init-usermod
    │   ├── dependencies.d
    │   │   └── init-nginx
    │   ├── run
    │   ├── type
    │   └── up
    ├── svc-nginx
    │   ├── dependencies.d
    │   │   ├── init-nginx
    │   │   └── svc-php82-fpm
    │   ├── run
    │   └── type
    ├── svc-php82-fpm
    │   ├── dependencies.d
    │   ├── run
    │   └── type
    ├── svc-stdout
    │   ├── dependencies.d
    │   │   ├── svc-nginx
    │   │   └── svc-php82-fpm
    │   ├── run
    │   └── type
    └── user
        └── contents.d
            ├── init-nginx
            ├── init-php82-fpm
            ├── init-usermod
            ├── svc-nginx
            ├── svc-php82-fpm
            └── svc-stdout

Installing S6

S6-overlay is distributed in several tar files, each with a detailed description. The choice of which tar file to install depends on your specific requirements:

To utilize scripts with environment variables, you should install the symlinks tar files.

If you are running daemons that cannot log to stderr to take advantage of the s6 logging infrastructure, and rely on the old syslog() mechanism, consider installing the syslog-overlay-noarch tar file.

Here is a sample Dockerfile code snippet to install S6-overlay in your Docker image,

You can find the full image here.

FROM alpine:3.18

ARG S6_OVERLAY_VERSION="3.1.5.0"
ARG S6_OVERLAY_ARCH="x86_64"

# add s6 overlay
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_OVERLAY_ARCH}.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-${S6_OVERLAY_ARCH}.tar.xz

# add s6 optional symlinks
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-noarch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-symlinks-noarch.tar.xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-arch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-symlinks-arch.tar.xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/syslogd-overlay-noarch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/syslogd-overlay-noarch.tar.xz

In a production environment, when running PHP applications, it becomes essential to employ PHP-FPM. This is due to PHP's single-threaded nature, which can limit its ability to efficiently handle heavy traffic while conserving memory and CPU resources. In such scenarios, S6-overlay proves to be an ideal choice for managing these applications. In this example, we will demonstrate how to set up PHP-FPM alongside Nginx using S6-overlay. 

Installing PHP and Nginx

To install PHP and Nginx, you can use a package manager. In our case, we are utilizing an Alpine base image, making it convenient to use the apk package manager.

# Install OS Dependencies
RUN set -ex \
  && apk add --no-cache --virtual .build-deps \
    autoconf automake build-base python3 gmp-dev \
    curl \
    tar \
  && apk add --no-cache --virtual .run-deps \
    nodejs npm \
    # PHP and extensions
    php82 php82-bcmath php82-ctype php82-curl php82-dom php82-exif php82-fileinfo \
    php82-fpm php82-gd php82-gmp php82-iconv php82-intl php82-mbstring \
    php82-mysqlnd php82-mysqli php82-opcache php82-openssl php82-pcntl php82-pecl-apcu php82-pdo php82-pdo_mysql \
    php82-phar php82-posix php82-session php82-simplexml php82-sockets php82-sqlite3 php82-tidy \
    php82-tokenizer php82-xml php82-xmlreader php82-xmlwriter php82-zip php82-pecl-xdebug php82-pecl-redis php82-soap php82-sodium php82-pdo_sqlite php82-pdo_pgsql php82-pgsql \
    # Other dependencies
    mariadb-client sudo shadow \
    # Miscellaneous packages
    bash ca-certificates dialog git libjpeg libpng-dev openssh-client vim wget shadow \
    # Nginx
    nginx \
    # Create directories
  && mkdir -p /etc/nginx \
    && mkdir -p /run/nginx \
    && mkdir -p /etc/nginx/sites-available \
    && mkdir -p /etc/nginx/sites-enabled \
    && rm -Rf /var/www/* \
    && rm -Rf /etc/nginx/nginx.conf \
  # Composer
  && wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig \
    && php82 -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
    && php82 -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
    && php82 composer-setup.php --install-dir=/usr/bin --filename=composer \
    && php82 -r "unlink('composer-setup.php'); unlink('installer.sig');" \
  # Cleanup
  && apk del .build-deps

Here we also installed Nodejs and npm as we are using this image for the Laravel app in which frontend files will be built using vite.

Now we need PHP-FPM and Nginx config.  Generally, we kept in base image repo in the following directory structure

└── rootfs
    └── etc
        ├── nginx
        │   ├── nginx.conf
        │   ├── sites-available
        │   │   └── default.conf
        │   └── sites-enabled
        ├── php82
        │   ├── conf.d
        │   ├── php-fpm.conf
        │   ├── php-fpm.d
        │   │   └── www.conf
        │   └── php.ini
        ├── s6-overlay
        │   └── s6-rc.d

So we added that using the below line in the Dockerfile

ADD rootfs /

Setting Users & Permissions in Your Image

By default, Docker runs containers with root privileges. While this is suitable for local development, where developers may need to install new packages or edit configs, it is not recommended for production environments. Allowing anyone with access to the container to add packages or change permissions can pose security risks.

To address this, it's advisable to run your processes as non-root users. To achieve this, we utilize the following script to set the User ID (UID) and Group ID (GID) of the "nginx" user and group. This script is included in the S6-overlay, ensuring that it runs as a one-shot task before the start of PHP-FPM and Nginx.

#!/usr/bin/with-contenv bash
# shellcheck shell=bash

UID=${UID:-911}
GID=${GID:-911}

usermod -u "$UID" nginx && groupmod -g "$GID" nginx

In that script, you can use the environment variables that you passed to your container. i.e

docker run --name s6-app -e UID -e GID -p 8888:80 -v .:/var/www

Process Sequences

When you are running multiple processes, it becomes essential to ensure they run in a specific sequence. S6-overlay simplifies the process of defining and controlling the sequence in which these processes operate.

For instance, if your Nginx process depends on an initialization process for Nginx, it's straightforward to establish the sequence, ensuring that the necessary processes start in the correct order. 

cd svc-nginx
mkdir dependencies.d
touch init-nginx svc-php82-fpm

Now, svc-nginx will start only after init-nginx will be completed(as it is oneshot) and svc-php82-fpm will be in a running state.

Graceful Shutdown

A graceful shutdown is crucial for preserving the proper state of data and responses when a container exits. S6-overlay simplifies this process by supporting the use of variables like S6_SERVICES_GRACETIME and S6_KILL_GRACETIME to set a graceful exit time frame. Additionally, S6-overlay allows you to customize the exit code that the container returns upon termination. You can achieve this by creating a "finish" script in your process directory.

For instance, in cases where you are running a non-critical process, you can use the finish script to specify a return status of 0. The inclusion of a finish script is optional but valuable when you need to modify the exit code or execute specific cleanup tasks during the container's shutdown.

#!/bin/sh

echo "0" > /run/s6-linux-init-container-results/exitcode

Extending Image

S6-overlay's directory-based structure offers a straightforward method for extending this image from another base image. You can effortlessly create multiple images by adding additional processes without overwriting existing configurations in S6-overlay.

COPY svc-crond /etc/s6-overlay/s6-rc.d/svc-crond

Summary

For many years, we relied on Supervisor in our production environments. However, it became evident that the Supervisor could not address all the challenges we encountered.

This is where S6-overlay emerged as a modern alternative, offering a comprehensive solution to our process management needs.

S6-overlay's versatility and ease of use make it a perfect choice, particularly for container images that require the execution of multiple processes.