What PID 1 does
Once the kernel has booted, it starts one process with PID 1. On modern Linux that process is systemd. Its jobs:
- Start the services the system needs at boot
- Watch their state and restart the ones that crash
- Manage dependencies (start docker after network)
- Adopt orphaned processes (when a parent dies, PID 1 becomes the new parent)
- Log everything through journald
- Manage cgroups for limits and isolation
Unit files
Everything systemd can do is a unit. The kinds:
.service: an ordinary daemon (nginx, ssh, postgres).socket: listen on a socket and start the service on the first connection.timer: a cron replacement that runs a unit on a schedule.mount/.automount: filesystem mounts.target: a group of units (like a runlevel:multi-user.target,graphical.target).path: react when a file appears or changes.slice: a node in the cgroup hierarchy
Where they live:
/lib/systemd/system/: shipped by packages/etc/systemd/system/: your local files and drop-ins (these take priority)
A minimal service unit
[Unit]
Description=My App
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp --config=/etc/myapp.yml
Restart=on-failure
RestartSec=5
User=myapp
Group=myapp
[Install]
WantedBy=multi-user.target
Put it in /etc/systemd/system/myapp.service, then:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
Type: the startup model
simple: ExecStart is a binary that does NOT fork; the defaultforking: the old-school style (the binary forks a child and exits); needsPIDFile=notify: the app sendsREADY=1throughsd_notifyonce it is readyoneshot: run and exit (for scripts and setup tasks)idle: like simple, but starts only after other units have settled
Dependencies
Wants=: a soft dependency (we want it, but it is not critical)Requires=: a hard one (if the dependency fails, this unit fails too)After=/Before=: ordering (not a dependency, just the sequence)Conflicts=: mutual exclusion
Isolation through cgroups
systemd puts each service in its own cgroup and supports sandboxing:
[Service]
MemoryMax=512M
CPUQuota=50%
PrivateTmp=yes # private /tmp
ProtectSystem=strict # /usr, /etc read-only
ProtectHome=yes # /home, /root inaccessible
NoNewPrivileges=yes # block setuid escalation
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
This gives you most of the "containerization" without Docker.
Targets instead of runlevels
Old vs new:
| runlevel | systemd target |
|---|---|
| 0 | poweroff.target |
| 1 | rescue.target |
| 3 | multi-user.target (CLI) |
| 5 | graphical.target (GUI) |
| 6 | reboot.target |
systemctl get-default # current default target
sudo systemctl set-default multi-user.target
systemctl isolate rescue.target # switch to a target right now
Debugging
systemctl status myapp # state + recent logs
journalctl -u myapp -f # tail the logs
systemd-analyze blame # who was slow to start
systemd-analyze critical-chain # the critical path of the boot
systemctl list-dependencies myapp