Overview of macOS Process Automation Methods
Explore macOS process automation methods including launchd, cron, PM2, supervisord, systemd (via Linux emulation), and Docker for reliable background service management.
Overview of macOS Process Automation Methods

macOS provides multiple approaches to process automation and service management, each suited for different scenarios. From the native launchd system to language-specific process managers like PM2, understanding these tools is essential for running background services, scheduled tasks, and production applications on macOS.
Introduction
Process automation on macOS goes beyond simple scriptingβitβs about keeping services alive, scheduling tasks, and managing applications that run independently of user sessions. Whether youβre a developer running a Node.js API, a sysadmin managing system services, or a data scientist scheduling Python scripts, macOS offers tools tailored to your needs.
This guide covers the primary process automation methods on macOS:
- launchd - macOSβs native init system
- brew services - Simple Homebrew service management
- PM2 - Node.js production process manager
- Supervisord/Circus - Python process control
- Overmind - Development environment with Procfiles
- cron - Legacy Unix scheduling
Quick Comparison Table
| Tool | Type | Use Case | Complexity |
|---|---|---|---|
| launchd | Native system | Production services, scheduled tasks | High |
| brew services | Wrapper | Homebrew services | Low |
| PM2 | Node.js | Node.js production | Medium |
| Forever | Node.js | Simple Node.js | Low |
| Supervisord | Python | Python/any language | Medium |
| Circus | Python | Modern Python | Medium |
| Overmind | Procfile | Development | Low |
| cron | Unix scheduler | Legacy tasks | Low |
Native macOS: launchd
What is launchd
launchd is macOSβs init system and service manager, equivalent to systemd on Linux. Introduced in Mac OS X 10.4 (Tiger), it manages daemons, agents, and scheduled tasks. Every process on macOS (except the kernel) is ultimately a child of launchd.
Key Components:
- launchd - The system service manager
- launchctl - Command-line interface for managing services
- LaunchAgents - User-level services (run when user logs in)
- LaunchDaemons - System-level services (run at boot, as root)
launchctl Command Reference
# List all loaded serviceslaunchctl list
# Load a servicelaunchctl load ~/Library/LaunchAgents/com.example.service.plist
# Unload a servicelaunchctl unload ~/Library/LaunchAgents/com.example.service.plist
# Start a servicelaunchctl start com.example.service
# Stop a servicelaunchctl stop com.example.service
# View service logslog stream --predicate 'process == "your-process-name"' --level debugLaunchAgents vs LaunchDaemons
The distinction between LaunchAgents and LaunchDaemons is fundamental:
| Aspect | LaunchAgents | LaunchDaemons |
|---|---|---|
| When runs | User login | System boot |
| Run as | Logged-in user | root/system |
| Permissions | User-level | System-level |
| GUI access | Yes | No |
| Location | ~/Library/LaunchAgents/ or /Library/LaunchAgents/ | /Library/LaunchDaemons/ |
LaunchAgents are for user-specific tasks that may need GUI access:
- Menu bar apps
- User automation scripts
- Per-user background services
LaunchDaemons are for system services that run regardless of users:
- Database servers
- Web servers
- System monitoring tools
Creating plist Files
Services are defined in XML property list (plist) files:
<?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.myapp</string>
<key>ProgramArguments</key> <array> <string>/usr/local/bin/node</string> <string>/Users/user/myapp/app.js</string> </array>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>WorkingDirectory</key> <string>/Users/user/myapp</string>
<key>StandardOutPath</key> <string>/Users/user/myapp/logs/stdout.log</string>
<key>StandardErrorPath</key> <string>/Users/user/myapp/logs/stderr.log</string>
<key>EnvironmentVariables</key> <dict> <key>NODE_ENV</key> <string>production</string> </dict></dict></plist>Key plist keys:
Label- Unique service identifier (required)ProgramArguments- Command and arguments to executeRunAtLoad- Start immediately when loadedKeepAlive- Restart if process diesWorkingDirectory- Set working directoryStandardOutPath/StandardErrorPath- Log file locationsEnvironmentVariables- Environment variables for the processStartInterval- Run at specified interval (for scheduled tasks)CalendarInterval- Run at specific times (scheduling alternative to cron)
Scheduled Tasks with launchd
launchd has largely replaced cron for scheduled tasks on macOS. Instead of cron syntax, use calendar intervals:
<key>StartCalendarInterval</key><dict> <key>Hour</key> <integer>2</integer> <key>Minute</key> <integer>0</integer></dict>This runs the task daily at 2:00 AM. More examples:
<!-- Every hour --><key>StartInterval</key><integer>3600</integer>
<!-- Every Monday at 9 AM --><key>StartCalendarInterval</key><dict> <key>Weekday</key> <integer>1</integer> <key>Hour</key> <integer>9</integer> <key>Minute</key> <integer>0</integer></dict>
<!-- First day of every month --><key>StartCalendarInterval</key><dict> <key>Day</key> <integer>1</integer> <key>Hour</key> <integer>3</integer> <key>Minute</key> <integer>0</integer></dict>Debugging launchd Services
When a service fails to start:
- Check the syntax:
plutil -lint ~/Library/LaunchAgents/com.example.service.plist- View system logs:
log show --predicate 'process == "launchd"' --last 1hlog stream --predicate 'eventMessage contains "com.example"'- Check service status:
launchctl list | grep com.example- Manual execution for testing:
# Run the command directly to see errors/usr/local/bin/node /Users/user/myapp/app.jsSources: Understanding macOS LaunchAgents and Login Items, What are launchd agents and daemons on macOS?
Scheduling: launchd vs cron
Cronβs Deprecation Status
Apple has deprecated cron in favor of launchd, though cron remains supported for backwards compatibility. In macOS Sequoia, Apple even added flags in System Settings for legacy cron support.
When to Use launchd Scheduling
launchd offers advantages over cron:
- Better integration - Native macOS service management
- More triggers - File watching, system events, not just time
- Sleep handling - Handles sleep/wake cycles intelligently
- Security contexts - Runs with proper macOS permissions
- Logging - Integrated with Unified Logging
Migrating crontab to launchd
Crontab entry:
0 2 * * * /Users/user/scripts/backup.shEquivalent launchd plist:
<key>Label</key><string>com.user.backup</string>
<key>ProgramArguments</key><array> <string>/Users/user/scripts/backup.sh</string></array>
<key>StartCalendarInterval</key><dict> <key>Hour</key> <integer>2</integer> <key>Minute</key> <integer>0</integer></dict>When cron Still Makes Sense
For simple time-based tasks where youβre already familiar with cron syntax:
# Edit crontabcrontab -e
# List crontabcrontab -l
# Remove crontabcrontab -rSources: Scheduled jobs with launchd rather than cron, Use launchd instead of crontab on your Mac
Homebrew Services
Installation
First, install Homebrew if you havenβt already:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Then verify installation:
brew --versionWhat is brew services
brew services is a Homebrew extension that simplifies managing background services. It wraps launchd to provide a familiar interface for services installed via Homebrew.
# List all servicesbrew services list
# Start a servicebrew services start postgresql
# Stop a servicebrew services stop redis
# Restart a servicebrew services restart nginx
# Run service in foreground (for debugging)brew services run postgresqlHow brew services Uses launchd
brew services automatically creates launchd plist files:
- User services β
~/Library/LaunchAgents/ - System services β
/Library/LaunchDaemons/
For example, brew services start postgresql creates a plist at ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist.
Common Use Cases
Databases:
brew install postgresqlbrew services start postgresql
brew install mysqlbrew services start mysql
brew install redisbrew services start redisWeb Servers:
brew install nginxbrew services start nginxDevelopment Stacks:
# Start multiple servicesbrew services start postgresqlbrew services start redisbrew services start memcachedGUI Tools
For those who prefer graphical interfaces:
- BrewServicesManager - Menu bar app for managing Homebrew services
- brew-services-manage - Service management with log access
Sources: Homebrew Services: How to Use, How It Works, Managing background processes in Ventura
Node.js Process Managers
PM2
PM2 is the de facto standard for Node.js process management in production. It provides clustering, monitoring, and automatic restart capabilities.
Installation:
npm install pm2 -gBasic Commands:
# Start an applicationpm2 start app.js
# Start with namepm2 start app.js --name "api"
# Start multiple instances (cluster mode)pm2 start app.js -i max
# List all processespm2 list
# View logspm2 logs
# Monitor dashboardpm2 monit
# Stop a processpm2 stop api
# Restart a processpm2 restart api
# Delete a processpm2 delete apiPM2 Startup on macOS (launchd integration)
To have PM2 start automatically on boot:
# Generate and save startup scriptpm2 startup darwin
# Save current process listpm2 save
# Now start your appspm2 start app.jspm2 saveThe pm2 startup darwin command generates a launchd plist that loads PM2 on system boot, and pm2 save persists the current process list so PM2 can restore your applications after restart.
Sources: PM2 Documentation, How to use pm2 startup on macOS
Forever
For simpler use cases, Forever provides basic keep-alive functionality:
npm install forever -g
# Start a scriptforever start app.js
# List running scriptsforever list
# Stop a scriptforever stop app.jsBest for: Development environments, simple scripts, when PM2βs features arenβt needed.
Sources: Running Node.js scripts continuously using forever
Other Node.js Options
- nodemon - Auto-restart on file changes (development only)
Terminal window npm install -g nodemonnodemon app.js - concurrently - Run multiple npm scripts simultaneously
Terminal window npm install -g concurrentlyconcurrently "npm run watch" "npm run serve" - nohup - Built-in Unix command for quick background tasks
Python Process Managers
Supervisord
Supervisord is a mature process control system for Unix-like systems, widely used in Python environments.
Installation:
brew install supervisord# or via pip:pip install supervisorConfiguration (/etc/supervisord.conf or ~/.supervisor/supervisord.conf):
[supervisord]nodaemon=falselogfile=/var/log/supervisor/supervisord.log
[program:myapp]command=/usr/bin/python3 /path/to/app.pydirectory=/path/to/appuser=myuserautostart=trueautorestart=truestderr_logfile=/var/log/supervisor/myapp.err.logstdout_logfile=/var/log/supervisor/myapp.out.logCommands:
# Start supervisordsupervisord -c /path/to/supervisord.conf
# Check statussupervisorctl status
# Start a programsupervisorctl start myapp
# Stop a programsupervisorctl stop myapp
# Restart a programsupervisorctl restart myapp
# Reread configurationsupervisorctl rereadsupervisorctl updateSources: Supervisor Official Documentation, Install supervisor on Mac M1
Circus
Circus is a modern Python-based alternative to Supervisord with better performance and active development:
pip install circusCircus configuration (circus.ini):
[watcher:myapp]cmd = python3 /path/to/app.pyuid = myusernumprocesses = 1autostart = truestop_signal = TERMCommands:
circusd circus.inicircusctl statuscircusctl start myappcircusctl stop myappAdvantages over Supervisord:
- Better performance with ZeroMQ
- More responsive to process changes
- Active development
- Cross-platform
Sources: Circus Documentation
Procfile-Based Managers
Overmind
Overmind is a modern process manager using tmux for terminal UI, ideal for development environments with multiple processes.
Installation:
brew install overmindProcfile (Procfile):
web: bundle exec rails serverworker: bundle exec sidekiqredis: redis-serverUsage:
overmind startOvermind creates a tmux session with each process in its own window, accessible via standard tmux commands.
Best for: Development environments, multi-process applications, terminal-based workflows.
Sources: Overmind GitHub, Control Your Dev Processes with Overmind
Honcho (Python) and Foreman (Ruby)
Honcho (Python):
pip install honchohoncho startForeman (Ruby):
gem install foremanforeman startBoth work with the same Procfile format as Overmind but without the tmux TUI.
Other Methods
nohup
For quick background tasks:
nohup node app.js &Best for: Quick tests, simple background tasks, temporary runs.
screen/tmux
For persistent terminal sessions:
screen -S mysession# ortmux new -s mysessionBest for: Interactive sessions, remote work, long-running terminal processes.
Comprehensive Comparison: Pros and Cons
| Tool | Description | Pros | Cons |
|---|---|---|---|
| launchd | macOS native init system | β
Native to macOS β No installation needed β Survives system updates β Most reliable option β Full system integration | β XML configuration is verbose β Steep learning curve β Debugging can be difficult β Limited community tools |
| brew services | Homebrew service wrapper | β
Extremely simple interface β One-command installation β Familiar for Homebrew users β Auto-generates plists | β Requires Homebrew β Limited customization β Not ideal for production β Hidden complexity |
| PM2 | Node.js process manager | β
Built for Node.js β Clustering support β Excellent monitoring β Auto-restart on crash β Zero-downtime reload | β Node.js specific β Additional software dependency β Memory overhead β Learning curve for features |
| Forever | Simple Node.js keep-alive | β
Very lightweight β Simple to use β Low resource usage | β Minimal features β No monitoring dashboard β No clustering β Manual restart only |
| Supervisord | Python process manager | β
Mature and stable β Language-agnostic β Web monitoring interface β Process grouping | β Older architecture β Configuration complexity β Slower than modern alternatives β Less active development |
| Circus | Modern Python process manager | β
Better performance β ZeroMQ for communication β Active development β Cross-platform | β Smaller community β Less mature than Supervisord β Different configuration format β Fewer tutorials available |
| Overmind | Procfile-based dev manager | β
Excellent TUI with tmux β Standard Procfile format β Great for development β Visual process control | β tmux dependency β Not for production β Requires terminal access β macOS-specific quirks |
| Honcho | Python Procfile manager | β
Cross-platform β Simple interface β Pure Python | β No TUI β Less popular than Overmind β Minimal features |
| Foreman | Ruby Procfile manager | β
Original Procfile tool β Battle-tested β Large community | β Ruby dependency β Aging codebase β No TUI β Slower than alternatives |
| cron | Unix job scheduler | β
Familiar syntax β Simple for quick tasks β Works everywhere (Unix) | β Deprecated on macOS β No native macOS integration β Poor error handling β Doesnβt handle sleep/wake well |
| nohup | Unix background command | β
Built into Unix β No installation needed β Quick for testing | β No auto-restart β No monitoring β Lost on terminal close β Manual process management only |
| screen/tmux | Terminal multiplexer | β
Persistent sessions β Great for remote work β Multiple windows | β Not for automation β Requires terminal access β Manual process management β Session-based (not service) |
Decision Framework
Choosing by Use Case
| Scenario | Recommended Tool | Why |
|---|---|---|
| Production system service | launchd | Native, reliable, survives reboot |
| Development with Procfile | Overmind | TUI, easy process management |
| Node.js production | PM2 | Clustering, monitoring, auto-restart |
| Node.js simple/development | Forever | Simple, lightweight |
| Python production | Circus/Supervisord | Python-native, mature |
| Database/Redis | brew services | Simple, Homebrew integration |
| One-off background task | nohup | Built-in, no setup |
| Production scheduled task | launchd | Native, more features |
| Cross-platform dev | Honcho | Works everywhere |
Choosing by Skill Level
| User Profile | Start With | Expand To |
|---|---|---|
| macOS native focus | launchd | brew services |
| Node.js developer | PM2 | launchd for system integration |
| Python developer | Circus | brew services for databases |
| DevOps engineer | launchd | Supervisord/Circus |
| Beginner | brew services | PM2 or launchd |
| Power user | Overmind | All tools as needed |
Integration Strategies
PM2 with launchd:
pm2 startup darwin # Creates launchd plistpm2 save # Saves current process listbrew services with launchd: brew services automatically creates launchd plists.
Overmind with Docker: Use Overmind for local processes, Docker for containers, orchestrate with scripts.
Conclusion
macOS offers a rich toolkit for process automation:
- For most users: Start with
brew servicesfor simplicity - For production: Use
launchddirectly or via tool integration (PM2 startup) - For Node.js: PM2 is the industry standard
- For Python: Supervisord or Circus
- For development: Overmind provides excellent workflow
The key is choosing the right tool for your specific use case and environment. Native tools like launchd provide the most reliable integration with macOS, while language-specific tools like PM2 offer specialized features for their ecosystems.
Cover Image: Technical diagram of macOS automation tools including launchd architecture, brew services interface, PM2 dashboard, and comparison table.
Sources: