Skip to content

Migrating from supervisord

supervisord keeps your processes alive, and that’s about where it stops. The web UI is an afterthought, the logs are flat files you tail by hand, and finding out why something keeps restarting means SSHing in and reading supervisord.log. RunWisp supervises the same processes and gives you per-run history, streamed logs, and a notification when an instance flaps.

You don’t have to rewrite your config by hand. runwisp import supervisord reads it — [include] files and all — and writes the runwisp.toml.

Point it at your config, or pipe one in — both are first-class:

Terminal window
runwisp import supervisord /etc/supervisor/supervisord.conf -o runwisp.toml
cat /etc/supervisor/supervisord.conf | runwisp import supervisord
runwisp validate

Given a file, it follows any [include] directives relative to it, so a typical conf.d/-style layout comes across in one shot. Each [program] becomes a RunWisp service. Given:

[program:web]
command=/usr/bin/gunicorn app:app
directory=/srv/app
user=www-data
autostart=true
startsecs=10
startretries=5
stopsignal=INT
environment=DJANGO_SETTINGS="prod",LOG_LEVEL=info
stdout_logfile=/var/log/web.log
[program:worker]
command=/srv/app/worker --idx %(process_num)s
numprocs=4

you get:

[services.web]
run = "/usr/bin/gunicorn app:app"
working_dir = "/srv/app"
user = "www-data"
healthy_after = "10s"
restart_attempts = 5
stop_signal = "SIGINT"
[services.web.env]
DJANGO_SETTINGS = "prod"
LOG_LEVEL = "info"
[services.worker]
run = "/srv/app/worker --idx %(process_num)s"
instances = 4

On stderr you get an account of every program, so you know which ones came across untouched and which ones want a look:

Imported supervisord config → 2 services
~ web service
/usr/bin/gunicorn app:app
• log file settings were dropped — RunWisp captures stdout and stderr
per run automatically (see log_max_size / keep_runs to tune).
! worker service
/srv/app/worker --idx %(process_num)s
! command uses supervisord %(...)s expansions RunWisp doesn't fill in
(e.g. %(ENV_x)s, %(process_num)s). Review the run line.
• numprocs=4 became instances. Each instance gets a distinct
RUNWISP_INSTANCE_INDEX (0-based) in place of %(process_num)s.
1 item carries a # TODO — fix it before you rely on it.
Review the TOML above, save it as runwisp.toml, then run `runwisp validate`.
! supervisord still manages these programs. Stop them there before starting
RunWisp, or each one runs twice.

~ means it’s imported but something changed, ! that it needs you before you rely on it, that it mapped cleanly, and - that your runwisp.toml already had it. Every [program] in the file gets a row either way — including the ones that produced no TOML at all, which is the case worth seeing.

Save it and run runwisp validate. Then stop the programs in supervisord — see nothing has retired supervisord yet — and runwisp daemon brings them up under RunWisp, where you can watch them from the dashboard.

-o writes one flat file, which is what you want when you’re starting fresh. When you already have a runwisp.toml of your own, use --write instead:

Terminal window
runwisp import supervisord /etc/supervisor/supervisord.conf --write --dry-run
runwisp import supervisord /etc/supervisor/supervisord.conf --write

--dry-run first is worth the extra command: you get the same summary plus the exact files a real run would create or change, and it changes none of them.

The imported services land in a machine-owned runwisp.d/imported.toml and your root config is wired to load it, so nothing you hand-wrote is touched. They show up as staged — imported, not native yet — and run exactly like any other service in the meantime. When you’re happy with one:

Terminal window
runwisp promote web # move it into your runwisp.toml
runwisp promote --all # or move all of them

The block moves verbatim, comments and # TODO notes included, and the service keeps running while it happens. Re-running the import later skips programs you’ve already promoted rather than tripping over the duplicate name. The cron recipe has the full picture of the two-tier layout, which works identically here.

An import copies your programs into runwisp.toml. It doesn’t stop supervisord, and it doesn’t edit its config — that’s yours. So if you start the daemon while supervisord is still running the same programs, you get two of every process, each with its own supervisor determined to restart it.

That’s not a subtle problem for services the way it is for cron jobs: two gunicorn instances fight over a port, two workers double-consume a queue, and whichever one loses the race gets restarted by its supervisor, forever. Worth doing first, then:

Terminal window
sudo supervisorctl stop web worker # just the ones you imported
sudo supervisorctl stop all # or everything

Then take them out of supervisord’s config for good, so a supervisorctl reload or a host reboot doesn’t quietly bring the second copy back:

Terminal window
sudo cp /etc/supervisor/supervisord.conf ~/supervisord.conf.backup
sudo $EDITOR /etc/supervisor/supervisord.conf # or the conf.d/ file
sudo supervisorctl reread && sudo supervisorctl update

Once nothing is left in there, sudo systemctl disable --now supervisor retires the daemon itself. Migrating a few programs at a time works fine too — just never leave one enabled in both places.

A [program] is a long-running, auto-restarted process — which is exactly a RunWisp service. Most keys carry straight over:

supervisord runwisp.toml Notes
[program:web] [services.web] One program → one service.
command run %(program_name)s is expanded.
directory working_dir
user user Needs the daemon running as root to switch users.
umask umask
numprocs instances Each gets a RUNWISP_INSTANCE_INDEX (0-based).
startsecs healthy_after Uptime that marks an instance healthy.
startretries restart_attempts Fast failures tolerated before FATAL.
stopsignal stop_signal INTSIGINT.
stopwaitsecs graceful_stop Grace window before SIGKILL.
exitcodes exit_codes Which codes count as success.
priority priority Boot start order.
autostart autostart Whether instances start at boot.
environment [services.NAME.env] Quoted values are parsed correctly.
[group:site] programs=web,… group = "site" on each RunWisp has no program groups; members are tagged.
[include] files=conf.d/*.conf (followed and merged) Resolved relative to the config file.

The importer flags everything it can’t map cleanly with a # TODO in the TOML and a note on that program’s row. A key it doesn’t recognise at all is dropped — but the row says which keys, by name, so you can decide whether any of them mattered. The ones to know about:

  • autorestart. RunWisp services are always-on — they restart whenever they exit. autorestart=true (and supervisord’s default, unexpected) map to a service directly. autorestart=false is different: that’s a run-once process, so it’s imported as a task with run_on_start and restart = "never" instead of a service. If you relied on unexpected to not restart on a clean exit, set exit_codes so RunWisp knows which codes are success.
  • %(...)s expansions. %(program_name)s is filled in. Anything else — %(process_num)s, %(ENV_x)s, %(host_node_name)s — is left in place with a TODO. For numprocs, the per-instance index is RUNWISP_INSTANCE_INDEX in the environment; swap it into your command.
  • Log files. stdout_logfile / stderr_logfile are dropped — RunWisp captures both streams for every run automatically, no paths to manage. Tune size and retention with log_max_size and keep_runs instead.
  • Daemon sections. [supervisord], [supervisorctl], [unix_http_server], [inet_http_server], and [rpcinterface] are supervisord’s own plumbing and have no RunWisp equivalent — RunWisp’s daemon is configured in [daemon] and served over HTTP out of the box. They’re skipped, with a note at the bottom of the summary.
  • [eventlistener] / [fcgi-program]. Not supported — RunWisp has no event listener bus or FastCGI process manager. Nothing is generated for them, but they still get a ! row: they’re not “nothing to do”, they’re “go do this another way”.
  • Logs you don’t have to tail. Per-run stdout/stderr, streamed live to the Web UI and TUI and indexed for search — not a flat file you rotate by hand.
  • Restart visibility. Every restart is recorded with timing, so a flapping instance is obvious instead of buried in supervisord.log. Healthy uptime (healthy_after) resets the backoff; too many fast failures mark it FATAL.
  • Failure alerts. Notify on failure through Slack, Telegram, email, or a webhook.
  • Start ordering. priority controls boot order, and depends_on gates a service on another becoming healthy first — both honored at startup.
  • Trigger / stop from anywhere. Start and stop services from the dashboard, TUI, or REST API; stops are graceful, honoring your stop_signal and graceful_stop.

With supervisord out of the picture, runwisp.toml is the single place your processes are defined.