8.5 KiB
dumb-init
dumb-init
is a simple process supervisor and init system designed to run as
PID 1 inside minimal container environments (such as Docker). It is a
deployed as a small, statically-linked binary written in C.
Lightweight containers have popularized the idea of running a single process or service without normal init systems like systemd or sysvinit. However, omitting an init system often leads to incorrect handling of processes and signals, and can result in problems such as containers which can't be gracefully stopped, or leaking containers which should have been destroyed.
dumb-init
enables you to simply prefix your command with dumb-init
. It acts
as PID 1 and immediately spawns your command as a child process, taking care to
properly handle and forward signals as they are received.
Why you need an init system
Normally, when you launch a Docker container, the process you're executing becomes PID 1, giving it the quirks and responsibilities that come with being the init system for the container.
There are two common issues this presents:
-
In most cases, signals won't be handled properly.
The Linux kernel applies special signal handling to processes which run as PID 1.
When processes are sent a signal on a normal Linux system, the kernel will first check for any custom handlers the process has registered for that signal, and otherwise fall back to default behavior (for example, killing the process on
SIGTERM
).However, if the process receiving the signal is PID 1, it gets special treatment by the kernel; if it hasn't registered a handler for the signal, the kernel won't fall back to default behavior, and nothing happens. In other words, if your process doesn't explicitly handle these signals, sending it
SIGTERM
will have no effect at all.A common example is CI jobs that do
docker run my-container script
: sendingSIGTERM
to thedocker run
process will typically kill thedocker run
command, but leave the container running in the background. -
Orphaned zombie processes aren't properly reaped.
A process becomes a zombie when it exits, and remains a zombie until its parent calls some variation of the
wait()
system call on it. It remains in the process table as a "defunct" process. Typically, a parent process will callwait()
immediately and avoid long-living zombies.If a parent exits before its child, the child is "orphaned", and is re-parented under PID 1. The init system is thus responsible for
wait()
-ing on orphaned zombie processes.Of course, most processes won't
wait()
on random processes that happen to become attached to them, so containers often end with dozens of zombies rooted at PID 1.
What dumb-init
does
dumb-init
runs as PID 1, acting like a simple init system. It launches a
single process and then proxies all received signals to a session rooted at
that child process.
Since your actual process is no longer PID 1, when it receives signals from
dumb-init
, the default signal handlers will be applied, and your process will
behave as you would expect. If your process dies, dumb-init
will also die,
taking care to clean up any other processes that might still remain.
Session behavior
In its default mode, dumb-init
establishes a
session rooted at the
child, and sends signals to the entire process group. This is useful if you
have a poorly-behaving child (such as a shell script) which won't normally
signal its children before dying.
This can actually be useful outside of Docker containers in regular process
supervisors like daemontools or supervisord for
supervising shell scripts. Normally, a signal like SIGTERM
received by a
shell isn't forwarded to subprocesses; instead, only the shell process dies.
With dumb-init, you can just write shell scripts with dumb-init in the shebang:
#!/usr/bin/dumb-init /bin/sh
my-web-server & # launch a process in the background
my-other-server # launch another process in the foreground
Ordinarily, a SIGTERM
sent to the shell would kill the shell but leave those
processes running (both the background and foreground!). With dumb-init, your
subprocesses will receive the same signals your shell does.
If you'd like for signals to only be sent to the direct child, you can run with
the --single-child
argument, or set the environment variable
DUMB_INIT_SETSID=0
when running dumb-init
. In this mode, dumb-init is
completely transparent; you can even string multiple together (like dumb-init dumb-init echo 'oh, hi'
).
Installing inside Docker containers
You have a few options for using dumb-init
:
Option 1: Installing via an internal apt server (Debian/Ubuntu)
If you have an internal apt server, uploading the .deb
to your server is the
recommended way to use dumb-init
. In your Dockerfiles, you can simply
apt-get install dumb-init
and it will be available.
Debian packages are available from the GitHub Releases tab, or
you can run make builddeb
yourself.
Option 2: Installing the .deb
package manually (Debian/Ubuntu)
If you don't have an internal apt server, you can use dpkg -i
to install the
.deb
package. You can choose how you get the .deb
onto your container
(mounting a directory or wget
-ing it are some options).
One possibility is with the following commands in your Dockerfile:
RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.0.2/dumb-init_1.0.2_amd64.deb
RUN dpkg -i dumb-init_*.deb
Option 3: Downloading the binary directly
Since dumb-init is released as a statically-linked binary, you can usually just plop it into your images. Here's an example of doing that in a Dockerfile:
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.0.2/dumb-init_1.0.2_amd64
RUN chmod +x /usr/local/bin/dumb-init
Option 4: Installing from PyPI
Though dumb-init
is written entirely in C, we also provide a Python package
which compiles and installs the binary. It can be installed from
PyPI using pip. You'll want to first
install a C compiler (on Debian/Ubuntu, apt-get install gcc
is sufficient),
then just pip install dumb-init
.
Usage
Once installed inside your Docker container, simply prefix your commands with
dumb-init
. For example:
$ docker run my_container dumb-init python -c 'while True: pass'
Running this same command without dumb-init
would result in being unable to
stop the container without SIGKILL
, but with dumb-init
, you can send it
more humane signals like SIGTERM
.
Building dumb-init
Building the dumb-init binary requires a working compiler and libc headers and defaults to glibc.
$ make
Building with musl
Statically compiled dumb-init is over 700KB due to glibc, but musl is now an
option. On Debian/Ubuntu apt-get install musl-tools
to install the source and
wrappers, then just:
$ CC=musl-gcc make
When statically compiled with musl the binary size is around 20KB.
Building the Debian package
We use the standard Debian conventions for specifying build dependencies (look
in debian/control
). An easy way to get started is to apt-get install build-essential devscripts equivs
, and then sudo mk-build-deps -i --remove
to install all of the missing build dependencies automatically. You can then
use make builddeb
to build dumb-init Debian packages.
If you prefer an automated Debian package build using Docker, just run make builddeb-docker
. This is easier, but requires you to have Docker running on
your machine.