Supervisor vs Oxmgr vs systemd
In the Python world, “how do I keep my app running?” has had the same answer for over a decade: Supervisor (supervisord). It’s reliable and everywhere. But it was designed in a different era, and both systemd and modern process managers have moved the baseline. Here’s an honest comparison for anyone running Python — Django, FastAPI, Flask, or Celery — in production.
The three contenders
- Supervisord — a Python daemon that supervises processes, configured via INI files. The long-time default for Python apps.
- systemd — the init system already running on your Linux box; it can supervise services via unit files.
- Oxmgr — a modern process manager written in Rust, configured via a single TOML file, with health checks and an event bus built in.
Feature comparison
| Supervisord | systemd | Oxmgr | |
|---|---|---|---|
| Config format | INI files | Unit files | One TOML file |
| Runtime dependency | Python | None (built into OS) | None (single binary) |
| Daemon memory | ~30–50 MB (Python) | Shared with init | ~4 MB |
| HTTP health checks | No (process-alive only) | No (needs helpers) | Yes, built in |
| Health-gated reload | No | No | Yes |
| Real-time events | No (poll supervisorctl) | journalctl follow | Event bus |
| Resource limits | No | Yes (cgroups) | Yes |
| Config in git | Yes | Awkward (scattered) | Yes (one file) |
| Cross-platform | Linux/Unix | Linux only | Linux/macOS/Windows |
Where Supervisord shows its age
Supervisord still works, but three things date it:
- It’s a Python daemon. Ironically, the tool keeping your Python app alive is itself a Python app with its own interpreter and memory. On a small box that’s 30–50 MB of supervision overhead — and it means Supervisor’s health depends on a Python runtime.
- “Alive” is the only health signal. Supervisord knows whether the process exists, not whether it’s serving requests. An app that’s deadlocked but not crashed looks perfectly healthy to Supervisord. There’s no built-in HTTP health check and no readiness-gated reload.
- You poll it. Status comes from running
supervisorctl status; there’s no push stream of lifecycle events to build alerting or dashboards on. Compare that with a real-time event bus.
None of these make it bad — they make it a 2010-era tool. If it’s running your app today, there’s no fire.
Where systemd is the right answer
systemd is genuinely great for system-level services, and it’s already installed:
- Zero extra dependency — it’s PID 1; nothing to install.
- cgroup resource control — real memory/CPU limits enforced by the kernel.
- Boot integration is native — it’s literally the init system.
systemd’s weaknesses are ergonomic: config lives in scattered unit files (awkward to review as one thing in git), it has no HTTP health checks or health-gated reloads, and managing a fleet of app processes through it means a lot of .service files and systemctl incantations. It’s excellent for one service; it’s tedious for an app made of many processes you deploy constantly.
Where a modern manager fits
Oxmgr targets the application layer specifically: many related processes, deployed frequently, that you want described in one reviewable file with real health checks. The Python-relevant wins:
- One TOML file describes your web tier, Celery workers, and beat scheduler together — versioned, reviewed in PRs.
- HTTP health checks know whether Django is actually serving, not just whether the process exists — and gate zero-downtime reloads on it.
- ~4 MB of overhead vs Supervisord’s Python daemon, with no interpreter dependency.
- An event bus for real-time crash alerts and dashboards, instead of polling
supervisorctl.
[processes.web]
command = "gunicorn app.wsgi -w 4 -b 127.0.0.1:8000"
restart_on_exit = true
[processes.web.health_check]
endpoint = "http://localhost:8000/healthz"
interval_secs = 30
[processes.worker]
command = "celery -A app worker --pool=gevent --concurrency=100"
restart_on_exit = true How to choose
- A single system service (a daemon, a database) → systemd. It’s already there and it’s the right tool.
- A legacy Python stack already on Supervisord that’s happy → leave it, or migrate at your leisure. No emergency.
- An app of several processes you deploy often and want health checks, one-file config, and real-time visibility → a modern process manager.
The honest summary: Supervisord walked so the current generation could run. It’s still dependable, but if you’re standing up a Python stack today — especially one with a web tier and workers and a scheduler that you deploy multiple times a week — the health checks, single-file config, and event stream of a modern manager save you the helper scripts you’d otherwise bolt onto Supervisord or systemd.