by zbentley | 2026-01-29 03:14:17+00:00
In daemontools' happy path, you're 100% right.

However, the happy path is very, very narrow. It breaks down as soon as you have to deal with:

- Set(e)uid daemons or any background service that needs to run as a different user than the one launching the service.

- Programs that report readiness somehow (the equivalent of the "notify" systemd type). Plenty of services won't crash when they should, so a standard for "it's all the way up" is useful.

- Start timeouts and failures. If daemontools is your only authority of the "status" result, recognizing a started-but-hung service is hard. Even if your service has some other "yes, the binary exists, but is it working?" check, getting daemontools+init to probe that and appropriately react to start timeouts can be fussy.

- Services that themselves spawn subprocesses (either forking or running separate child programs). Parent-process crashes are a recipe for orphaned things running and consuming resources/doing who knows what. You can solve this with a cgroup/namespacing/prctls to control orphan reparenting, but that's super hard to get right. Systemd has a one-liner for "on stop, kill this service and any program that was ever spawned in its tree". Having implemented the equivalent code to KillMode=control-group a in a few different "this has to be really reliable and bug-free" contexts now, having to manually manage this when managing services is a recipe for disaster. You'd think you know when your services will/won't have child processes, but you'd be wrong a lot (e.g. oddball JVM tracing sidecars, Pythons that always run a multiprocessing helper even if they don't use multiprocessing, Redis sometimes forking and doing bgsave, Erlang with its DNS-management child procs, literally anything ever written in Perl, Postgres, and so on. You would think that most managed child processes in big, mature software projects will know to kill themselves if their parent crashes for whatever reason. You would be wrong).

- Inter-service dependencies. Yes, many init systems take that on, but now you have the daemonization system and the init system--separate tools to configure, with overlap and expectations about each other.

- Race conditions around setting up pidfiles/locks when first deploying a service. Sure, once it's installed flock(2) will work fine, but what if you're installing a new distribution of the service and the installer picks a different lock directory from the one in use by the running service?

There are a lot more things I could list, but I'd rather not go back there. Having to manage this stuff by hand was a pretty low point for our field--as fully clued-in as sysadmins felt, we made a lot of mistakes, and made an environment that was deeply hostile to non-expert operators who just wanted to run a service and forget about it.

Systemd's far from perfect, but the challenges of doing this stuff with daemontools and friends weren't just "this is a worse implementation than systemd"; they were "this is existentially harder than using systemd precisely because it follows the 'unix philosophy' and maximizes control of (and thus responsibility for) an area where simple cases are often ... not".