Convert Figma logo to code with AI

Yelp logodumb-init

A minimal init system for Linux containers

6,826
344
6,826
29

Top Related Projects

9,777

A tiny but valid `init` for containers

1,686

An open, distributed platform as a service

1,634

A fast, straightforward, reliable tool for performing massive, automated code refactoring

Quick Overview

Dumb-init is a simple process supervisor that forwards signals to its children. It is designed to be used as the init system for Docker containers, where it helps to handle signal forwarding and reaping zombie processes.

Pros

  • Signal Forwarding: Dumb-init forwards all received signals to its child processes, ensuring that they are properly terminated when the container is stopped.
  • Zombie Process Reaping: Dumb-init reaps zombie processes, preventing them from accumulating and causing issues in the container.
  • Lightweight: Dumb-init is a small, single-purpose tool that adds minimal overhead to the container.
  • Cross-Platform: Dumb-init is written in C and can be used on various platforms, including Linux, macOS, and Windows.

Cons

  • Limited Functionality: Dumb-init is a very focused tool and does not provide the full functionality of a traditional init system, such as process management or service supervision.
  • Dependency on Dumb-init: Using dumb-init requires modifying the container's entrypoint to use the dumb-init binary, which may not be suitable for all use cases.
  • Potential Compatibility Issues: Dumb-init may not work as expected with some applications or container runtimes, requiring additional configuration or troubleshooting.
  • Lack of Advanced Features: Dumb-init does not offer advanced features like process monitoring, resource management, or service orchestration that are available in more comprehensive init systems.

Code Examples

N/A (Dumb-init is not a code library)

Getting Started

To use dumb-init in a Docker container, you can follow these steps:

  1. Download the dumb-init binary from the GitHub repository and add it to your container image.

  2. Modify your container's entrypoint to use the dumb-init binary. For example, in a Dockerfile:

FROM ubuntu:latest

# Add dumb-init to the container
COPY dumb-init /usr/local/bin/dumb-init
RUN chmod +x /usr/local/bin/dumb-init

# Use dumb-init as the entrypoint
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD ["your", "command", "here"]
  1. When running the container, you can use the dumb-init binary to start your application:
docker run -it your-image dumb-init your-command

Alternatively, you can set dumb-init as the default entrypoint for your container image, as shown in the Dockerfile example above.

Competitor Comparisons

9,777

A tiny but valid `init` for containers

Pros of Tini

  • Tini is a more actively maintained project, with regular updates and bug fixes.
  • Tini has a larger community and more contributors, which can lead to faster issue resolution and feature development.
  • Tini provides better signal handling and process management, which can be important for running containerized applications.

Cons of Tini

  • Tini has a slightly larger binary size compared to dumb-init.
  • Tini may have a steeper learning curve for users who are already familiar with dumb-init.
  • Tini's additional features may not be necessary for all use cases, making dumb-init a simpler and more lightweight option.

Code Comparison

dumb-init:

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <program> [args...]\n", argv[0]);
        return 1;
    }

    return run_child(argv + 1);
}

Tini:

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <program> [args...]\n", argv[0]);
        return 1;
    }

    return tini_main(argv + 1);
}

The main difference in the code is the use of the tini_main() function in Tini, which provides additional functionality for signal handling and process management.

1,686

An open, distributed platform as a service

Pros of PaaSTA

  • PaaSTA provides a comprehensive platform for managing and deploying applications in a containerized environment, making it a powerful tool for large-scale deployments.
  • The platform includes features like service discovery, load balancing, and automated scaling, which can simplify the management of complex application architectures.
  • PaaSTA's integration with Kubernetes and other cloud-native technologies allows for seamless deployment and scaling of applications across different environments.

Cons of PaaSTA

  • PaaSTA has a steeper learning curve compared to dumb-init, as it requires a deeper understanding of containerization and cloud-native technologies.
  • The setup and configuration of PaaSTA can be more complex, especially for smaller projects or teams that may not require the full set of features.
  • The overhead of running the PaaSTA platform may be higher than using a simpler tool like dumb-init, particularly for smaller-scale deployments.

Code Comparison

dumb-init:

import os
import signal
import sys

def _forward_signal(signum, frame):
    os.kill(os.getpid(), signum)

signal.signal(signal.SIGTERM, _forward_signal)
signal.signal(signal.SIGINT, _forward_signal)

os.execvp(sys.argv[1], sys.argv[1:])

PaaSTA:

import argparse
import os
import sys

from paasta_tools.cli.cmds.status import paasta_status
from paasta_tools.cli.cmds.start import paasta_start
from paasta_tools.cli.cmds.stop import paasta_stop
from paasta_tools.cli.cmds.deploy import paasta_deploy

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='PaaSTA command-line interface')
    subparsers = parser.add_subparsers(dest='command')
    # Add subcommands for paasta_status, paasta_start, paasta_stop, and paasta_deploy
    # ...
    args = parser.parse_args()
    args.func(args)
1,634

A fast, straightforward, reliable tool for performing massive, automated code refactoring

Pros of undebt

  • Designed for large-scale code refactoring and debt reduction
  • Supports multiple programming languages
  • Highly customizable with pattern-based transformations

Cons of undebt

  • More complex setup and usage compared to dumb-init
  • Requires understanding of grammar files and patterns
  • Limited to code refactoring tasks, not a general-purpose tool

Code comparison

undebt:

from undebt.pattern.util import tokens_as_dict
from undebt.pattern.python import ATOM

grammar = tokens_as_dict(ATOM, ATOM)
pattern = grammar.keyword("old_function") + grammar.name
replace = "new_function({name})"

dumb-init:

int main(int argc, char *argv[]) {
    pid_t child_pid = fork();
    if (child_pid == 0) {
        execvp(argv[1], &argv[1]);
    }
}

Summary

undebt is a powerful tool for large-scale code refactoring across multiple languages, while dumb-init is a lightweight init system for Docker containers. undebt offers more flexibility for code transformations but requires more setup and knowledge. dumb-init is simpler and focused on process management in containerized environments.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

dumb-init

PyPI version

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 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:

  1. 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: sending SIGTERM to the docker run process will typically kill the docker run command, but leave the container running in the background.

  2. 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 call wait() 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').

Signal rewriting

dumb-init allows rewriting incoming signals before proxying them. This is useful in cases where you have a Docker supervisor (like Mesos or Kubernetes) which always sends a standard signal (e.g. SIGTERM). Some apps require a different stop signal in order to do graceful cleanup.

For example, to rewrite the signal SIGTERM (number 15) to SIGQUIT (number 3), just add --rewrite 15:3 on the command line.

To drop a signal entirely, you can rewrite it to the special number 0.

Signal rewriting special case

When running in setsid mode, it is not sufficient to forward SIGTSTP/SIGTTIN/SIGTTOU in most cases, since if the process has not added a custom signal handler for these signals, then the kernel will not apply default signal handling behavior (which would be suspending the process) since it is a member of an orphaned process group. For this reason, we set default rewrites to SIGSTOP from those three signals. You can opt out of this behavior by rewriting the signals back to their original values, if desired.

One caveat with this feature: for job control signals (SIGTSTP, SIGTTIN, SIGTTOU), dumb-init will always suspend itself after receiving the signal, even if you rewrite it to something else.

Installing inside Docker containers

You have a few options for using dumb-init:

Option 1: Installing from your distro's package repositories (Debian, Ubuntu, etc.)

Many popular Linux distributions (including Debian (since stretch) and Debian derivatives such as Ubuntu (since bionic)) now contain dumb-init packages in their official repositories.

On Debian-based distributions, you can run apt install dumb-init to install dumb-init, just like you'd install any other package.

Note: Most distro-provided versions of dumb-init are not statically-linked, unlike the versions we provide (see the other options below). This is normally perfectly fine, but means that these versions of dumb-init generally won't work when copied to other Linux distros, unlike the statically-linked versions we provide.

Option 2: 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 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 3: 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.2.5/dumb-init_1.2.5_amd64.deb
RUN dpkg -i dumb-init_*.deb

Option 4: 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.2.5/dumb-init_1.2.5_x86_64
RUN chmod +x /usr/local/bin/dumb-init

Option 5: 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.

As of 1.2.0, the package at PyPI is available as a pre-built wheel archive and does not need to be compiled on common Linux distributions.

Usage

Once installed inside your Docker container, simply prefix your commands with dumb-init (and make sure that you're using the recommended JSON syntax).

Within a Dockerfile, it's a good practice to use dumb-init as your container's entrypoint. An "entrypoint" is a partial command that gets prepended to your CMD instruction, making it a great fit for dumb-init:

# Runs "/usr/bin/dumb-init -- /my/script --with --args"
ENTRYPOINT ["/usr/bin/dumb-init", "--"]

# or if you use --rewrite or other cli flags
# ENTRYPOINT ["dumb-init", "--rewrite", "2:3", "--"]

CMD ["/my/script", "--with", "--args"]

If you declare an entrypoint in a base image, any images that descend from it don't need to also declare dumb-init. They can just set a CMD as usual.

For interactive one-off usage, you can just prepend it manually:

$ 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.

It's important that you use the JSON syntax for CMD and ENTRYPOINT. Otherwise, Docker invokes a shell to run your command, resulting in the shell as PID 1 instead of dumb-init.

Using a shell for pre-start hooks

Often containers want to do some pre-start work which can't be done during build time. For example, you might want to template out some config files based on environment variables.

The best way to integrate that with dumb-init is like this:

ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["bash", "-c", "do-some-pre-start-thing && exec my-server"]

By still using dumb-init as the entrypoint, you always have a proper init system in place.

The exec portion of the bash command is important because it replaces the bash process with your server, so that the shell only exists momentarily at start.

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.

See also