Running Services on Artix Linux: What runit Actually Changes for Daily Operations
Artix Linux's runit init replaces systemd's unit files with executable scripts in /etc/sv/. This blog walks through creating a supervised service, explains dependency handling, and outlines the real trade-offs for engineers considering a non-systemd base.
14 Jan 2026, 08:35 UTC

The Problem: Init System Friction in Daily Engineering Work
Most Linux distributions ship with systemd. For many engineers, that's fine—until it isn't. Debugging a stuck unit file, tracing dependency chains through systemctl list-dependencies, or waiting for a timeout on a service that should have started in milliseconds creates friction. Artix Linux exists because some teams want an init system that stays out of the way. It offers three init options: runit, OpenRC, and s6. This piece focuses on runit, the default variant, and what changes when you manage services with executable scripts instead of declarative unit files.
How runit Organizes Services
runit keeps each service in /etc/sv/<service-name>/ as a directory containing at minimum a run script—an executable that starts the daemon in the foreground. No Type=notify, no ExecStartPre, no After=network.target. The service manager, runsvdir, spawns a runsv process per service. Each runsv monitors its run script: if the script exits, runsv restarts it (unless a down file exists). Supervision is built in, not bolted on.
Enabling a service means creating a symlink from /etc/sv/<name> to /var/service/<name> (or /run/runit/service/ on some setups). Disabling removes the symlink. The sv command talks to runsv over a control socket: sv start nginx, sv stop nginx, sv status nginx. Output is one line, machine-parseable.
Worked Example: Adding a Custom Go API as a runit Service
Assume you've built a small API binary at /opt/myapi/server that listens on :8080 and logs to stdout. You want it supervised, restarted on crash, and started at boot.
- Create the service directory:
sudo mkdir -p /etc/sv/myapi - Write the
runscript (must be executable):cat | sudo tee /etc/sv/myapi/run <<'EOF' #!/bin/sh exec 2>&1 exec /opt/myapi/server EOF sudo chmod +x /etc/sv/myapi/run - Optional: add a
finishscript for cleanup on stop (also executable):cat | sudo tee /etc/sv/myapi/finish <<'EOF' #!/bin/sh # optional: drain connections, write pidfile cleanup echo "myapi stopped at $(date)" >> /var/log/myapi.log EOF sudo chmod +x /etc/sv/myapi/finish - Enable and start:
sudo ln -s /etc/sv/myapi /var/service/ sv status myapi # expected: run: myapi: (pid 12345) 0s
That's it. No systemctl daemon-reload. No enable --now. The symlink is the enable action; runsvdir picks it up within seconds (typically 5 s scan interval). To stop permanently: sudo rm /var/service/myapi then sv stop myapi.
Dependencies and Ordering: Explicit, Not Implicit
systemd builds a dependency graph from After=, Requires=, Wants=. runit has no graph. If myapi needs PostgreSQL, you have two practical patterns:
- Wait in the run script:
exec 2>&1; until pg_isready -q; do sleep 1; done; exec /opt/myapi/server. Simple, visible, testable withsh -n. - Use a wrapper like
s6-svwait(if s6 tools are installed) or a small helper script that blocks until a socket appears.
This shifts responsibility to the service author. The upside: you read the script and know exactly what "depends on PostgreSQL" means. The downside: no parallel startup optimization across unrelated services—runsvdir starts all linked services concurrently, but each service's run script serializes its own prerequisites.
Trade-offs You'll Hit in Production
| Aspect | runit on Artix | systemd (typical) |
|---|---|---|
| Boot parallelism | All services start together; ordering inside scripts | Graph-driven, fine-grained parallelism |
| Logging | stdout/stderr captured by runsv; pipe to svlogd or syslog | journald, structured, indexed |
| Resource limits | Set in run via ulimit, cgroups manual | LimitNOFILE=, MemoryMax= in unit |
| Desktop integration | Some DEs (GNOME, KDE) expect logind, systemd --user | Native |
| Package-provided services | Artix packages ship /etc/sv/ dirs; enable via symlink | Units in /usr/lib/systemd/system/ |
The desktop integration gap is real. If you run a full GNOME or Plasma session, you'll likely need elogind (logind API without systemd) and possibly polkit configured for non-systemd. Artix provides these packages, but expect to spend an evening on first setup. For headless servers, CI runners, or minimal window-manager setups (i3, sway, dwm), the gap rarely appears.
Verification Checklist After First Install
Run these on a fresh Artix VM or bare metal to confirm the baseline works:
ps -p 1 -o comm=→ should printrunit.ls -la /etc/sv/→ shows installed service directories (e.g.,dhcpcd,sshd,cronie).sv status dhcpcd→run: dhcpcd: (pid ...) ....pacman -Sy→ confirms repository connectivity (Artix + Arch repos).sv start/stop/status <test-service>on a throwaway service to feel the workflow.
If any step fails, check dmesg -T and the service's run script for missing binaries or permission issues.
Closing: When to Choose runit on Artix
Pick Artix runit when you want:
- Service definitions you can read and edit in 30 seconds.
- Supervision without a separate daemon (runsv is the supervisor).
- A rolling-release base (Arch packages) without systemd's surface area.
Stick with systemd when you need:
- Deep desktop environment integration out of the box.
- Complex dependency graphs managed declaratively.
- Team familiarity and existing systemd tooling (ansible modules, CI pipelines).
Try it in a VM this weekend. Create one custom service, watch it restart after kill -9, and decide if the mental model fits your workflow. The learning curve is an afternoon; the payoff is services that behave exactly as written.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.