Self-hosted service
Yelp/dumb-init avatar
Yelp/dumb-init

dumb-init: a PID 1 shim for Docker containers that do not want an init system

A minimal init system for Linux containers

7,311 stars354 forksPythonMIT

At a glance

What is it?
dumb-init is a small C binary that runs as PID 1, spawns your command as a child, forwards signals to the process group, and reaps zombies. It solves two specific container problems and nothing else.
Who is it for?
Adopt dumb-init when your container entrypoint is a shell script, a JVM, or any process that does not handle SIGTERM itself, and you want a single static binary you can copy into a scratch image. Skip it when your process already handles signals correctly and reaps its own children, or when you want a full supervisor with health checks and restart policies, which dumb-init does not provide.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The two container problems dumb-init exists to fix

The Linux kernel treats PID 1 differently. If a process running as PID 1 has not registered a handler for a signal, the kernel does not fall back to the default action. The README states that sending SIGTERM to such a process has no effect at all. In a container, the command in your ENTRYPOINT or CMD is PID 1, so a process that does not explicitly handle SIGTERM will ignore docker stop until the timeout expires and the runtime sends SIGKILL.

The second problem is zombie reaping. When a process exits and its parent never calls wait(), it stays in the process table as defunct. If a parent exits first, the child is re-parented under PID 1, and PID 1 is then responsible for waiting on it. Most application processes do not wait on arbitrary children, so containers accumulate zombies. The README describes containers ending up with dozens of zombies rooted at PID 1.

dumb-init is for teams running one process per container who want the kernel's default signal behavior back without pulling in systemd or sysvinit. It is also useful outside Docker: the README notes it can supervise shell scripts under daemontools or supervisord, because a shell that receives SIGTERM normally dies without forwarding it to subprocesses running in the background or foreground.

How dumb-init sits between the kernel and your command

dumb-init runs as PID 1 and immediately spawns your command as a child. From then on it is a proxy: every signal it receives is forwarded, and when the child dies dumb-init dies too, cleaning up remaining processes.

In the default mode it calls setsid to establish a new session rooted at the child, then sends signals to the entire process group. That is what makes the shell-script case work. A shell script that launches a background server and a foreground server will have both killed by the same SIGTERM, because dumb-init targets the group rather than the shell alone.

If you want signals delivered only to the direct child, run with --single-child or set DUMB_INIT_SETSID=0. The README calls this mode completely transparent, to the point that you can nest it: dumb-init dumb-init echo 'oh, hi'.

Signal rewriting is the third mechanism. --rewrite 15:3 turns an incoming SIGTERM into SIGQUIT before forwarding, which matters when an orchestrator such as Mesos or Kubernetes always sends SIGTERM but your application needs a different stop signal for graceful cleanup. Rewriting a signal to 0 drops it entirely.

There is a special case worth reading twice. In setsid mode, forwarding SIGTSTP, SIGTTIN or SIGTTOU is not enough, because a process in an orphaned process group will not get the kernel's default suspend behavior. dumb-init therefore rewrites those three to SIGSTOP by default. You can opt out by rewriting them back. The README also warns that dumb-init will always suspend itself after receiving a job control signal, even if you rewrote it to something else.

Installing dumb-init and a first Dockerfile

There are several install paths. On Debian and Debian derivatives such as Ubuntu, dumb-init is in the official repositories since stretch and bionic respectively, so `apt install dumb-init` works like any other package. The README warns that most distro-provided builds are not statically linked, unlike the versions Yelp publishes, which means a distro binary generally will not work when copied into another Linux distribution.

For a container image, the common pattern is to copy the static binary in. The README's install section covers distro packages and an internal apt server; the published static builds and Python wheels are produced by the repository's Makefile, which compiles dumb-init.c with -static and also builds manylinux wheels for x86_64, aarch64, ppc64le and s390x.

A minimal Dockerfile that uses the Debian package looks like this:

dockerfile
FROM debian:buster
RUN apt-get update \
    && apt-get install -y --no-install-recommends dumb-init \
    && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["dumb-init", "--"]
CMD ["my-web-server"]

The ENTRYPOINT with the trailing -- means whatever you put in CMD is executed as a child of dumb-init rather than being interpreted by dumb-init itself. You should see your process running under PID 1 in the container, and `docker stop` should reach it as SIGTERM instead of timing out.

For a shell script entrypoint, the README shows dumb-init in the shebang:

bash
#!/usr/bin/dumb-init /bin/sh
my-web-server &  # launch a process in the background
my-other-server  # launch another process in the foreground

With that shebang, a SIGTERM sent to the script reaches both servers, because dumb-init forwards to the process group. Without it, the shell dies and both children keep running.

To rewrite signals, add the flag to the entrypoint:

bash
dumb-init --rewrite 15:3 my-web-server

This turns SIGTERM into SIGQUIT for the child. Run `dumb-init --help` for the full usage, as the README points readers there.

Where dumb-init stops being the right answer

dumb-init supervises one command. It does not restart a crashed process, it does not run health checks, it does not manage multiple services, and it does not provide a configuration file. If your container needs any of that, you are looking at a different class of tool.

The default setsid behavior is also a trade-off rather than a free win. Sending signals to the whole process group is what makes shell scripts behave, but it is broader than sending to the direct child. If your application forks children that should outlive a signal, or if you run two unrelated processes that should be stopped independently, group delivery will hit both. The escape hatch is --single-child, but then you are back to the shell problem the default mode was designed to solve.

The job control caveat is the sharpest edge. Even if you rewrite SIGTSTP, SIGTTIN or SIGTTOU to something else, dumb-init still suspends itself on receipt. Anyone building interactive or job-control-dependent workloads on top of dumb-init should read that paragraph in the README before assuming rewriting gives full control.

Finally, dumb-init does not fix an application that handles signals incorrectly by design. It restores the kernel's default behavior for the child; if the child ignores SIGTERM on its own, the signal is still ignored. The README's framing is that your process is no longer PID 1, so default handlers apply. That is a real improvement, not a guarantee.

dumb-init compared with tini and Docker run --init

tini is the closest alternative and the one people search for alongside dumb-init. Both are small init shims that run as PID 1, spawn a child, forward signals and reap zombies. The difference is in the details of signal handling. dumb-init's default mode creates a session with setsid and delivers signals to the process group, and it ships signal rewriting via --rewrite plus the built-in SIGTSTP, SIGTTIN, SIGTTOU to SIGSTOP rewrites. tini's approach centers on forwarding to the child and zombie reaping, and it is bundled with Docker itself: `docker run --init` inserts tini as PID 1 for you. If your only need is zombie reaping and basic signal forwarding inside a container you launch with docker run, --init is a one-flag solution and you never touch the Dockerfile. If you need signal rewriting, a shebang-usable binary, or the process-group behavior for shell scripts, dumb-init is the more configurable of the two. The README does not compare itself to tini, so treat that distinction as a design observation rather than a claim from the project.

S6 is a different category. It is a process supervision suite with service directories and dependency management, not a single-binary shim. Choosing S6 means adopting a supervision model, not prefixing a command.

Maintenance, packaging and the MIT licence

The repository is not archived, and its last push was on 2026-07-13, so there is ongoing commit activity even though the most recent tagged release listed is v1.2.5 from 2021-02-02. That gap matters for planning: the code is small and stable, but if you are pinning to a release tag rather than tracking master, you are pinning to something from early 2021. Verify which artefact you are consuming, because the repository produces several: a statically linked binary built by the Makefile, Debian packages built with musl-gcc, and Python wheels built through manylinux2014 images for four architectures. The setup.py explicitly marks the wheel as not pure Python and sets the wheel tag to py2.py3 / none, which reflects that the package ships a C executable rather than Python source.

Architecture coverage is worth checking against your fleet. The release target in the Makefile sha256sums amd64, x86_64, ppc64el, ppc64le, s390x, arm64 and aarch64 artefacts, and the wheel targets cover x86_64, aarch64, ppc64le and s390x. If you run something outside that list, you are compiling from source.

The project is MIT licensed, which is permissive and imposes no source-disclosure obligation on your container images. This is not legal advice; read the LICENSE file in the repository and your own organisation's policy. The practical point is that MIT is compatible with the way dumb-init is normally used, which is copied into a proprietary image as a static binary.

Editorial conclusion

Adopt dumb-init when your container entrypoint is a shell script, a JVM, or any process that does not handle SIGTERM itself, and you want a single static binary you can copy into a scratch image. Skip it when your process already handles signals correctly and reaps its own children, or when you want a full supervisor with health checks and restart policies, which dumb-init does not provide. Before rolling it out, verify your base image architecture matches the release artifact you pull, check whether your distro package is statically linked, and confirm that any --rewrite rules you add do not collide with the built-in SIGTSTP, SIGTTIN and SIGTTOU to SIGSTOP rewrites.

Frequently asked questions

What is dumb-init?

It is a process supervisor and init system for minimal Linux containers, deployed as a small statically linked binary written in C. It runs as PID 1, spawns your command as a child, and proxies received signals to a session rooted at that child.

What does dumb-init actually do?

It gives your container a real init process so that signals reach your application with normal kernel default behavior, and so that orphaned zombie processes are reaped by PID 1. It also lets you rewrite incoming signals, for example turning SIGTERM into SIGQUIT with --rewrite 15:3.

What is /usr/bin/dumb-init?

It is the installed path of the dumb-init binary on Debian-based systems, which is where `apt install dumb-init` places it. Distro-provided builds are usually not statically linked, unlike the versions published by the project, so they generally will not work when copied to a different Linux distribution.

How does dumb-init compare with tini?

Both run as PID 1, forward signals and reap zombies. dumb-init's default mode creates a session rooted at the child and signals the whole process group, and it supports --rewrite plus built-in rewrites of SIGTSTP, SIGTTIN and SIGTTOU to SIGSTOP; tini is the init that Docker's `docker run --init` flag inserts for you.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. Yelp/dumb-init on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yelp-dumb-init.svg)](https://hysenlabs.com/projects/yelp-dumb-init)