systemd vs. OpenRC vs. runit: Comparing Init Systems for Servers and Containers
AI generated
$
/etc
Linux
systemd, OpenRC, runit
Comparing init systems for servers and containers

PID 1 decides far more than just the boot sequence: service supervision, dependency resolution, and logging architecture all hang directly off the chosen init system. systemd dominates on classic servers, while OpenRC and runit hold their ground in lean container base images like Alpine and Void Linux, for solid technical reasons.

10 min read Linux systemd Init System

1. What PID 1 actually does

Once the kernel finishes its own initialization, it starts exactly one userspace process with process ID 1, which from that moment on forms the root of the entire process tree. That process is handed a special responsibility: it has to reap orphaned child processes, avoid so called zombie processes, start system services in the correct order, and ideally also watch whether a service exits unexpectedly.

Historically, SysVinit filled this role on most Unix like systems, running sequentially executed shell scripts inside numbered runlevel directories. Modern init systems like systemd, OpenRC, and runit differ mainly in how much of that responsibility actually lives inside PID 1 itself, and how much service supervision, logging, and dependency handling are implemented as distinct, well defined building blocks rather than a loose collection of shell scripts.

2. systemd architecture: units and parallel startup

systemd describes services, mount points, devices, timers, and sockets uniformly as units in declarative configuration files, rather than as executable scripts. A service unit file defines its dependencies on other units through directives like After, Requires, and Wants, from which systemd builds a full dependency graph at boot and starts services without a hard dependency on each other in parallel rather than sequentially.

This parallel startup strategy noticeably shortens boot time compared to a strictly sequential SysVinit approach, but it also brings a considerably larger attack surface and code base, since systemd bundles logging through journald, network configuration through systemd-networkd, device management through udev, and numerous other components alongside pure process management in the same project.


# Inspect the dependencies of a service unit
systemctl show -p After -p Requires -p Wants nginx.service

# Print the full dependency graph as a text tree
systemd-analyze critical-chain nginx.service

# Boot time breakdown per unit
systemd-analyze blame

3. OpenRC architecture: shell scripts with a dependency system

OpenRC still relies on classic shell scripts for individual services, but adds its own dependency system, described through the need, use, and after directives in each script, which enables correct ordering and partial parallelism at startup without fully giving up the simplicity of plain shell scripts.

Since OpenRC is not a big bang redesign the way systemd is, but deliberately builds on a lean, POSIX close foundation, resource usage of PID 1 itself stays minimal, while actual process supervision runs optionally through the separate s6 tool or OpenRC's own supervise-daemon command, rather than being hardwired into PID 1.


# OpenRC service script, trimmed example (/etc/init.d/myapp)
#!/sbin/openrc-run
name="myapp"
command="/usr/bin/myapp"
command_args="--config /etc/myapp/config.yml"
command_background=true
pidfile="/run/myapp.pid"

depend() {
    need net
    after firewall
}

# Enable the service and check its status
rc-update add myapp default
rc-service myapp status

4. runit architecture: supervision trees as the core principle

runit takes a radically minimalist approach modeled on daemontools: every supervised service gets its own directory with a simple run script that starts the service in the foreground, while the supervisor process runsv keeps running alongside it and immediately restarts the service on an unexpected crash, without needing a complex configuration language.

This structure, known as supervision trees, makes runit especially predictable: there is practically no hidden global state, every service can be controlled in isolation with sv start, sv stop, or sv status, and the entire init system consists of only a few thousand lines of C code, which makes audits and a full understanding of its behavior considerably easier.


# runit service directory, minimal example
mkdir -p /etc/sv/myapp
cat <<'EOF' > /etc/sv/myapp/run
#!/bin/sh
exec 2>&1
exec /usr/bin/myapp --config /etc/myapp/config.yml
EOF
chmod +x /etc/sv/myapp/run
ln -s /etc/sv/myapp /var/service/myapp

# Query service status and restart it
sv status myapp
sv restart myapp

5. Service supervision compared directly

With systemd, supervision is built directly into PID 1: Restart directives in the unit file let systemd itself decide whether and how often a crashed service gets restarted automatically, including a configurable backoff strategy through StartLimitIntervalSec and StartLimitBurst, without needing an extra process for it.

runit achieves the same goal through a dedicated runsv process per service, structurally independent from PID 1 itself, while OpenRC delegates actual supervision optionally to supervise-daemon and, without that explicit configuration, does not automatically restart a crashed service, but merely reports it as stopped the next time it is checked manually or on a schedule.

6. Why systemd became the standard on servers

systemd established itself as the standard mainly because it goes far beyond pure process management, delivering a coherent ecosystem with journald, systemd-networkd, systemd-resolved, systemd timers, and cgroup based resource control that covers many tasks classic init systems needed separate, loosely coupled tools for, such as cron, syslog, or NetworkManager.

For ops teams, that means a unified command line through systemctl and journalctl for practically every system aspect, solid documentation, broad distribution support from Debian through RHEL to SUSE, and a huge ecosystem of ready made unit files, which significantly reduces practical effort compared to the theoretically leaner but more fragmented toolbox of OpenRC or runit.

7. When Alpine and Void choose lean container images

Alpine Linux deliberately relies on OpenRC instead of systemd, because musl libc and BusyBox as its foundation are already optimized for minimal size, and a full blown systemd with its cgroup, DBus, and journal underpinnings would fundamentally work against that goal. For container base images, where every extra megabyte of image size and every extra bit of attack surface matters, OpenRC's lean footprint fits noticeably better.

Void Linux goes a step further toward minimalism and predictability by using runit as its default init system, while in practice many containers do not need a full init system at all: a single process per container with a lean signal handler like tini is already enough for most containerized applications, so the init system debate in container environments often ends up limited to side processes such as cron jobs running inside a container.


# Alpine container: set up an OpenRC service inside a Docker image
FROM alpine:3.20
RUN apk add --no-cache openrc myapp
RUN rc-update add myapp default
CMD ["/sbin/openrc-run"]

# Alternative: a minimal signal handler instead of a full init system
CMD ["tini", "--", "/usr/bin/myapp"]

8. Practical consequences for day to day ops

Logging differs noticeably: systemd collects structured logs in binary form through journald, with journalctl as the query tool, while OpenRC and runit rely on classic text files or external loggers like syslog-ng, which are less conveniently searchable but avoid additional journal rotation and binary format dependencies, and feed more easily into existing log aggregation pipelines.

Debugging commands differ accordingly: under systemd, systemctl status immediately shows exit code, the latest log lines, and dependency status in a single output, while under OpenRC rc-service status and under runit sv status each only show plain runtime status, requiring a separate look at the relevant log file for details.

9. Recommendation by use case

For classic virtual or physical servers with mixed services, network configuration, and complex dependencies, systemd remains the pragmatic default choice, if only because of broad distribution support and a huge ecosystem of ready made unit files for practically any common server software.

For lean container base images, minimalist embedded systems, or environments where predictability and a small audit surface matter more than a rich feature set, OpenRC and runit offer a deliberate, well reasoned alternative that has worked reliably for years in its respective niche and should by no means be considered outdated there.

Trait systemd OpenRC runit
Configuration form Declarative unit files Shell scripts with dependency directives Simple run scripts per service
Service supervision Built directly into PID 1 Optional through supervise-daemon Dedicated runsv process per service
Logging Binary through journald Text files or external logger Text files or external logger
Typical use Classic servers, major distributions Alpine Linux, Gentoo Void Linux, minimalist setups
Code size Very large, many extra components Small to medium Very small, a few thousand lines

Mironsoft

Server administration, Docker hosts, and performance tuning

Linux servers nobody on the team really understands anymore?

We handle setup, hardening, and performance tuning of Linux servers and Docker hosts for Magento deployments, documented and traceable instead of grown and unclear.

Server Audit

Review the existing server configuration for security gaps and performance bottlenecks.

Docker Host Setup

Set up and secure production-ready Docker environments for Magento cleanly.

Monitoring & Tuning

Measure resource usage and tune systemd, kernel, and services with purpose.

10. Summary

Init Systems

systemd

Feature rich standard for classic servers running many services

OpenRC

Shell scripts plus a dependency system, the default on Alpine Linux

runit

Minimalist supervision trees, the default on Void Linux

Rule of thumb

Servers: systemd, lean container images: OpenRC or runit

11. FAQ: Init Systems

1What exactly is PID 1, and why does the choice of init system matter?
PID 1 is the first userspace process after the kernel starts, forming the root of the entire process tree. It is responsible for reaping orphaned child processes, starting system services, and, depending on the init system, also supervising them, which gives its architecture far reaching practical consequences.
2Why does systemd dominate most Linux servers?
systemd bundles process management, logging through journald, network configuration, and timers into one coherent ecosystem with a unified command line, broad distribution support, and a huge range of ready made unit files, which significantly reduces practical effort for ops teams.
3Why does Alpine Linux rely on OpenRC instead of systemd?
Alpine is optimized for minimal image size and uses musl libc and BusyBox as a lean foundation. A full blown systemd with its cgroup, DBus, and journal underpinnings would work against that goal, while OpenRC's smaller footprint fits container base images much better.
4What fundamentally sets runit apart from systemd?
runit relies on minimalist supervision trees with a dedicated runsv process per service and a simple run script, while systemd uses declarative unit files with an extensive dependency graph and numerous integrated extras like journald.
5How does service supervision work under OpenRC?
OpenRC delegates actual supervision optionally to the supervise-daemon command. Without that explicit configuration, OpenRC does not automatically restart a crashed service, but only reports it as stopped the next time an rc-service call checks it manually or on a schedule.
6Do Docker containers even need a full init system?
Usually not: a single process per container with a lean signal handler like tini is enough for most containerized applications. A full init system like OpenRC or runit only pays off once several processes need to be managed inside the same container.
7How does logging differ between systemd and OpenRC or runit?
systemd collects structured logs in binary form through journald with journalctl as the query tool, while OpenRC and runit classically rely on text files or external loggers like syslog-ng, which fit more easily into existing log aggregation pipelines but are less conveniently searchable.
8Is runit suitable for production servers, not just containers?
Yes, Void Linux uses runit as a full init system on regular servers, not only in containers. For complex server landscapes with many dependencies, though, it lacks convenience features such as the automatic dependency graph systemd provides.
9How do I start and supervise a service under runit?
A service gets its own directory under /etc/sv with an executable run script, activated through a symlink in /var/service. Status and restarts happen through the sv status and sv restart commands against that directory.
10Can I run systemd and OpenRC in parallel on the same system?
Not sensibly as PID 1, since only one init system can hold that role at a time. Inside containers, though, an OpenRC or runit setup can run within an image independently of the host's systemd based init system on the underlying server.