dumb-init

A minimal init system that runs as PID 1 in containers, correctly forwarding signals and reaping zombie processes.

Tool
PyPI
v1.2.5.post1
7,304stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
52/100Fair
Development Activity28
Maintenance20
Community60
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
61/100Good
Architecture70
Code Quality68
Innovation55
Learning Curve50

dumb-init is a lightweight, statically-linked init system written in C, designed to run as PID 1 inside minimal container environments like Docker. It solves two problems that plague single-process containers: signals sent to PID 1 are silently ignored unless explicitly handled, and orphaned child processes become unreaped zombies. By prefixing a container’s entrypoint with dumb-init, the target process runs as a proper child instead of PID 1, so it receives default signal behavior while dumb-init forwards signals and reaps zombies on its behalf.

Distributed as a small C binary, dumb-init is available via apt packages, direct .deb/binary download, or a PyPI wheel that compiles and installs the same executable. It supports two signal-forwarding modes — a default session-rooted mode that proxies signals to the whole process group (useful for shell scripts that don’t forward signals to their own children) and a —single-child mode for fully transparent, chainable behavior — plus configurable signal rewriting for supervisors that only ever send one standard stop signal.

What You Get

  • Proper signal handling for containerized processes, so SIGTERM actually stops your app instead of being silently dropped
  • Automatic reaping of orphaned zombie processes re-parented to PID 1
  • A statically-linked binary with no runtime dependencies, as small as ~20KB when built with musl
  • Configurable signal rewriting via —rewrite for apps that expect a nonstandard stop signal
  • Multiple installation paths: distro apt packages, a downloadable .deb, a raw static binary, or a PyPI wheel

Common Use Cases

  • Docker ENTRYPOINT wrapping for any containerized application
  • Kubernetes or Mesos deployments that need consistent, graceful SIGTERM handling
  • Shell script entrypoints that spawn background processes needing shared signal propagation
  • CI jobs using docker run that must remain interruptible with Ctrl+C or SIGTERM

Under The Hood

Architecture dumb-init is a single ~340-line C file implementing a small, flat state machine rather than an object model. parse_command() handles CLI flags and builds a signal-rewrite table from globals like signal_rewrite[]; main() blocks all signals with sigprocmask(), installs a dummy handler for signals 1-31, forks the wrapped command as a child, and loops on sigwait() dispatching into handle_signal(), which either reaps exited children via waitpid() on SIGCHLD or translates and forwards the signal via translate_signal()/forward_signal() and kill(). There’s no layering or dependency injection — the entire behavior lives in a handful of top-level functions operating on shared global state (child_pid, use_setsid, signal_rewrite[]), which is a deliberate, appropriate tradeoff for a program whose whole job is proxying signals to one child, though it means any change to signal semantics touches both main() and handle_signal() directly.

Tech Stack The project is pure C, buildable against either glibc (the default) or musl (CC=musl-gcc) for a dramatically smaller static binary — roughly 700KB with glibc versus around 20KB with musl. Building uses a plain Makefile, and the same source is packaged three ways: a Debian package built from the debian/ control files via dpkg-buildpackage, static binaries attached to GitHub Releases, and a PyPI wheel produced by a setup.py that layers custom bdist_wheel/build_cexe/install_cexe distutils commands on top of setuptools to compile and bundle the C binary rather than expose any Python API. CI runs through both circle.yml and GitHub Actions.

Code Quality Tests live under tests/ (roughly 720 lines across files like cli_test.py, proxies_signals_test.py, exit_status_test.py, and tty_test.py) and run with pytest (pytest.ini sets a 20-second timeout per test), exercising the compiled binary end-to-end via subprocess rather than unit-testing individual C functions — an appropriate black-box style for a signal-handling program. A pre-commit config enforces autopep8 formatting plus hygiene hooks (trailing whitespace, merge-conflict markers, private-key detection) on the Python side; the C source itself has no linter or sanitizer configured beyond whatever warnings the Makefile’s compiler flags produce. Error handling in the C code favors defensive assert() calls over graceful degradation, which is reasonable for a small init process where a bug should fail loudly rather than limp along.

What Makes It Unique dumb-init doesn’t invent a new mechanism — it applies the well-understood Unix pattern of session leader plus signal proxy plus zombie reaper to the specific problem Docker’s flat process model creates. Its most distinguishing design choice is the signal-rewrite table (--rewrite s:r), which lets one generic init binary adapt to processes expecting nonstandard stop signals without wrapping them in bespoke shell shims, alongside the deliberate rewrite of SIGTSTP/SIGTTIN/SIGTTOU to SIGSTOP that restores normal shell job-control behavior inside a session that would otherwise silently drop those signals. It plays the same role as tini, its closest alternative, but the rewrite mechanism is unusual among comparable tools.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search