GUIDE · PREVIEW
GUIDE / CON.20
source: docs/guide/concepts/Init Systems Compared.md
Concepts

Init Systems Compared

What It Is

An init system is PID 1 -- the first userspace process started by the Linux kernel. It starts and supervises all other services. If PID 1 dies, the kernel panics. Every Linux system has one.

This page compares the four init systems most relevant to immutable/embedded Linux: systemd, runit, OpenRC, and s6 + s6-rc.

Why It Matters

The init system choice affects:

  • Boot speed: How fast services start (parallel vs sequential)
  • Reliability: What happens when a service crashes
  • Image size: How much space the init system takes
  • Complexity: How much you need to understand to operate it
  • Compatibility: Which C libraries and platforms it supports

For an immutable OS like FortrOS, the init system is baked into the image. Changing it requires rebuilding. The choice is permanent until you decide to rebuild with something else.

The Contenders

systemd

What: A comprehensive system and service manager. PID 1, service supervisor, logging daemon, network manager, container runtime, device manager, and more.

Service definition: Declarative INI-style unit files.

Dependency model: Declarative (After=, Requires=, Wants=). Parallel startup by default. Socket activation (service starts on first connection to its socket).

Readiness: sd_notify protocol. Service sends READY=1 on a Unix socket.

Supervision: Restarts crashed services per policy (Restart=on-failure).

Size: ~10MB+ (binaries), requires glibc.

Used by: Ubuntu, Fedora, Arch, Debian, RHEL, SUSE -- most mainstream distros.

Best for: General-purpose Linux distributions where the broad feature set is an asset and glibc is already required.

runit

What: Minimal process supervisor. Three stages: one-time setup, service supervision, shutdown.

Service definition: A directory with a run shell script.

Dependency model: None. All services start simultaneously. If service A needs service B, A's run script must poll/wait.

Readiness: None built-in. Services are "ready" when their process starts, which may not mean they're accepting connections.

Supervision: Restarts crashed services immediately.

Size: ~50KB (binaries). Works with musl.

Used by: Void Linux, some Docker containers.

Best for: Simple systems where services have no ordering requirements and you want maximum simplicity.

OpenRC

What: Dependency-based init system using shell scripts. Started as Gentoo's init system.

Service definition: Shell scripts with a depend() function declaring dependencies.

Dependency model: need (hard dependency), use (soft dependency), after (ordering without dependency). Resolves graph, starts in levels.

Readiness: Limited. start-stop-daemon checks that the process is running. No application-level readiness signal.

Supervision: Limited. Can restart on crash but not as robust as dedicated supervisors.

Size: ~500KB (binaries). Works with musl.

Used by: Gentoo, Alpine Linux (alongside busybox init).

Best for: Systems that need dependency ordering without systemd's complexity. Shell-based service definitions are easy to write and debug.

s6 + s6-rc

What: Two complementary tools. s6 is a process supervisor (like runit with readiness signaling). s6-rc is a service manager that adds dependency resolution on top of s6.

Service definition: Directories with files: type (oneshot/longrun), run (the executable), dependencies (list of dependencies), notification-fd (readiness FD number).

Dependency model: Full dependency graph with oneshots (run-once scripts) and longruns (supervised daemons). Atomic transitions between service states.

Readiness: notification-fd protocol. The supervised process writes a newline to a specific file descriptor when ready. s6-rc waits for this before starting dependent services.

Supervision: Restarts crashed longruns. Configurable restart policies.

Size: ~200KB total (all s6 + s6-rc binaries). Works with musl.

Used by: Embedded systems, container images, FortrOS.

Best for: Minimal images that need proper dependency ordering and readiness signaling without systemd's weight or glibc requirement.

Comparison Table

Feature systemd runit OpenRC s6 + s6-rc
Dependency ordering Yes No Yes Yes
Readiness signaling sd_notify No Limited notification-fd
Parallel startup Yes Yes (unordered) Partial Yes (ordered)
Total binary size ~10MB+ ~50KB ~500KB ~200KB
musl compatible No Yes Yes Yes
Oneshot services Yes No Yes Yes
Socket activation Yes No No Yes (s6-ipcserver)
Logging journald svlogd syslog s6-log
Cgroup management Yes No No No
Timer/cron systemd.timer No No No
Network management networkd No Yes No
Learning curve Moderate Low Low Moderate
Documentation Extensive Minimal Good Technical

Why FortrOS Chose s6 + s6-rc

Hard constraint: musl. FortrOS images are built with Buildroot using musl libc (not glibc). This eliminates systemd. Everything else is secondary to this constraint.

Need: dependency ordering. Services have real dependencies (WireGuard must be up before the maintainer joins the gossip mesh). runit's "start everything, let services figure it out" model would require retry loops in every service. s6-rc handles this cleanly.

Need: readiness signaling. The maintainer takes time to initialize (WireGuard setup, initial TreeSync pull). The reconciler must not start until the maintainer is truly ready. s6-rc's notification-fd gives real "ready" signals, not "process started" guesses.

Bonus: tiny footprint. ~200KB for the entire init system fits the immutable image philosophy. Less code = less attack surface = less to audit.

Links