Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Comparing launchd to systemd and init

If you’re coming from Linux, you’re probably familiar with systemd or traditional init scripts. This chapter maps those concepts to launchd, helping you translate your knowledge to macOS.

Architecture Comparison

FeaturelaunchdsystemdSysVinit
PID 1YesYesYes
On-demand activationYesYesNo
Socket activationYesYesVia inetd
Timer unitsVia plistTimer unitsVia cron
ConfigurationXML plistsINI-like unitsShell scripts
Dependency managementImplicitExplicitManual
Cgroup integrationNoYesNo
Container supportNoYesNo

Concept Mapping

Service Files

systemd (.service file):

[Unit]
Description=My Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/myservice
Restart=always

[Install]
WantedBy=multi-user.target

launchd (.plist file):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.example.myservice</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/myservice</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>

Command Translation

Tasksystemdlaunchd
Start servicesystemctl start foolaunchctl start foo
Stop servicesystemctl stop foolaunchctl stop foo
Enable at bootsystemctl enable foolaunchctl load -w
Disablesystemctl disable foolaunchctl unload -w
Check statussystemctl status foolaunchctl list foo
List servicessystemctl list-unitslaunchctl list
View logsjournalctl -u foolog show --predicate 'process=="foo"'
Reload configsystemctl daemon-reloadAutomatic

Service Locations

Typesystemdlaunchd
System services/etc/systemd/system//Library/LaunchDaemons/
User services~/.config/systemd/user/~/Library/LaunchAgents/
Vendor services/usr/lib/systemd/system//System/Library/LaunchDaemons/

Key Differences

Dependencies

systemd: Explicit dependencies with After=, Requires=, Wants=

[Unit]
After=network.target postgresql.service
Requires=postgresql.service

launchd: Implicit dependencies via on-demand activation

<!-- launchd handles dependencies via on-demand loading -->
<!-- No explicit dependency declaration -->

Service Types

systemd has multiple service types:

  • simple: Default, main process
  • forking: Forks and exits
  • oneshot: Runs once
  • notify: Uses sd_notify
  • dbus: D-Bus activated

launchd determines type implicitly:

  • RunAtLoad: Starts immediately
  • KeepAlive: Respawns if exits
  • StartInterval: Periodic
  • WatchPaths: Event-triggered

Restart Policies

systemd:

Restart=always
RestartSec=10
StartLimitBurst=5

launchd:

<key>KeepAlive</key>
<true/>
<key>ThrottleInterval</key>
<integer>10</integer>

Environment Variables

systemd:

Environment="VAR1=value1" "VAR2=value2"
EnvironmentFile=/etc/default/myservice

launchd:

<key>EnvironmentVariables</key>
<dict>
    <key>VAR1</key>
    <string>value1</string>
    <key>VAR2</key>
    <string>value2</string>
</dict>

Logging

systemd: Integrated with journald

$ journalctl -u myservice -f

launchd: Uses unified logging or file redirection

$ log show --predicate 'process == "myservice"' --last 1h
# Or configure StandardOutPath/StandardErrorPath

Migration Guide

From systemd to launchd

  1. Service name: Use reverse-domain notation (com.example.service)
  2. ExecStart: Use ProgramArguments array
  3. Restart=always: Use KeepAlive
  4. After=network.target: Remove (launchd handles implicitly)
  5. Environment: Use EnvironmentVariables dict
  6. Install section: Use RunAtLoad or explicit loading

Common Patterns

Simple daemon:

<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>

Periodic task (like systemd timer):

<key>StartCalendarInterval</key>
<dict>
    <key>Hour</key>
    <integer>3</integer>
</dict>

Socket activated:

<key>Sockets</key>
<dict>
    <key>Listeners</key>
    <dict>
        <key>SockServiceName</key>
        <string>8080</string>
    </dict>
</dict>

Traditional init Comparison

For users familiar with SysVinit:

SysVinitlaunchd Equivalent
/etc/init.d/foo startlaunchctl start foo
/etc/init.d/foo stoplaunchctl stop foo
chkconfig foo onlaunchctl load -w
update-rc.d foo defaultsPlace plist in LaunchDaemons
/etc/rc.d/rc.localUse Launch Agent/Daemon

What launchd Doesn’t Have

Features in systemd without direct launchd equivalents:

  • Cgroups: No container-style resource isolation
  • Slice/scope units: No hierarchical resource management
  • Socket/path/timer as separate units: All in one plist
  • Templates: No parameterized service files
  • Drop-in directories: No .d override directories
  • Portable services: No standardized service images

What launchd Has That Others Don’t

  • Tight GUI integration: Agents can interact with user session
  • Power management awareness: Respects sleep/wake
  • Application bundle integration: Services in app bundles
  • On-demand loading: More aggressive than systemd
  • XPC integration: Modern IPC framework

Summary

Translation guide:

Conceptsystemdlaunchd
Service definition.service.plist
Service managersystemctllaunchctl
LoggingjournaldUnified log
Timers.timer unitsStartCalendarInterval
Sockets.socket unitsSockets dict in plist
User servicesuser unitsLaunchAgents
System servicessystem unitsLaunchDaemons

Key mindset shifts:

  • launchd prefers on-demand over explicit dependencies
  • Configuration is XML instead of INI
  • Domains (system/user/gui) replace targets
  • Unified logging replaces journald
  • Less explicit control, more automatic management

Both are capable service managers; the differences are largely philosophical and syntactic.