In this blog post, I’m going to look at some init systems, but not very thoroughly:

What Makes it a “Container” Init System?

A tool can be considered “container” (PID1) ready if it can do two things:

  1. Handle signals as PID1: In Linux, any PID1 process won’t automatically get signal handlers it hasn’t registered for.

If you don’t do this, your process will not respond to signals as expected, and you will have long and non-graceful shutdown time.

  1. Zombie process reaping: Most programs are not ready to wait() on random forked processes, but this is a PID1 responsibility.

If you don’t do this, forked processes end up piling up in your container and fill up the Linux process table.

What? I thought Containers Didn’t Need An Init System?

Even if you run a “single process container”, you probably need something to be PID1 for the above two reasons.

You might also want something fancier if you want process supervision.

You almost certainly want something fancier if you are running multiple things in the same container.

You Are Wrong! I Only Run (one thing) in a Container and its Fine!

In 2025, quite a few apps have added patches to behave better in a container as PID1.

If you are using a k8s pod with shareProcessNamespace enabled (not default), then you are also covered because the k8s pause process handles signals and child reaping.

Here are a few industry examples I found:

So yea. By now in 2025, your app may be PID1-ready already.

Crash-only Software Versus Supervision

The next thing you should consider when evaluating init systems is process supervision.

This is another thing that k8s has implemented for you with container restart policies.

But sometimes the cost of crashing and restarting something is high. There is a reason we don’t just do apache || reboot.

Sometimes doing process supervision in a container is the right tool for the job. I’ll leave it as an exercise to the reader to figure out if they need it.

Comparison

Feature Chart

Tool Supervision Multi Process Configuration AKA
tini CLI docker’s --init
dumb-init CLI
pid1 CLI
daemontools /service style dirs svscanboot
nitro /service style dirs
runit /service style dirs runsvdir
s6 /service style dirs s6-svscan
supervisord INI Files
systemd Unit files

There are a couple of families:

  1. The “pid1 wrappers”: tini, dumb-init, and pid1. The are specifically targeting containers and only solve the PID1 problems.

  2. The “daemontools”-inspired: daemontools, nitro, runit, and s6. These use the “directory for each service” paradigm.

  3. The “application healthcheckers”?: supervisord but also tools like monit or god. These focus on more “application” than system process supervision? It is a blurry line, but nobody ever intended for these tools to monitor sshd, contrast to the daemontools family.

  4. systemd: It is in a class of its own. It is concerned with everything on a system. System service supervision is just one of its many functions.

Opinions

I really wish we didn’t need the pid1 wrappers at all. On all the container platforms I’ve built, I’ve made sure that none of our users have to worry about that. In other words, I strongly believe this is a platform problem, not something to be solved by apps.

These daemontools tools I think will slowly go out of style in the container world and be replaced by k8s pods and restart policies.

Likewise with supervisord and similar tools, k8s as a product has a full suite of probes to pick from to make sure that the app stays healthy, and when it isn’t, the control-plane can take action. The control-plane connection is what will make these “userspace” healthcheck tools slowly fade.

As for systemd, if you are running systemd in your containers, more power to you! :)

I do think there was a missed opportunity for systemd to have a container-native systemd-lite thingy, that could be a pid1-wrapper and supervise a single unit.

Conclusion

At this point, after working with containers for a long time, the industry has kinda converged on the pod concept for running processes together in supervised way.

This does obsolete a lot of the responsibility of classic init/supervision helpers.

It does not obsolete the need for the process to behave like a proper PID1.

I wish that k8s pause would have solved that problem once and for all for everyone, but in the end the use of shared pid namespaces by default was reverted in k8s 1.8, so now every container entrypoint is PID1.

I hope that over time, more language runtimes become “PID1-aware”, obsoleting the pid1 wrappers.

Till then, I just recommend tini.


Comment via email