← all posts

Why one binary

RunWisp embeds SQLite, a web dashboard, and a TUI into a single static Go binary. Here's why, and what it costs.

2 min read

Most process supervisors ask you to bring infrastructure. supervisord needs Python. PM2 needs Node. Airflow needs a metadata database, a message broker, and a weekend. RunWisp ships as a single static binary: no runtime, no interpreter, no container required.

This wasn’t a flex. It was a design constraint that fell out of the problem.

The target machine

RunWisp is built for the box you SSH into: a VPS, a Raspberry Pi, a Docker container, a CI runner. These machines have disk and a shell. They may not have Python 3.11, or Node 20, or a Postgres instance you’d trust with scheduler metadata.

A single binary means curl | sh works on a fresh Alpine container the same way it works on a five-year-old Ubuntu LTS. No package manager, no dependency resolution, no “but it works on my machine.”

Embedding SQLite

Run history (exit code, duration, captured stdout/stderr) needs to live somewhere. A flat file gets unwieldy after a few thousand runs. An external database is another thing to operate.

modernc.org/sqlite is a pure-Go translation of SQLite. No CGo, no system library, no apt install libsqlite3-dev. It compiles into the binary and opens a file on disk. Queries are SQL. Backups are cp. The database sits in the data directory alongside run logs, protected by the same file permissions.

The tradeoff: the binary is larger (~25 MB) than it would be with a flat-file store. And SQLite’s single-writer lock means RunWisp can’t shard writes across cores. Neither matters at the scale this tool targets: dozens of tasks, not thousands.

Embedding the dashboard

The web UI is a Svelte app, built at compile time and embedded via Go’s embed directive. When the daemon starts, it serves the dashboard from memory. No static file directory to deploy, no CDN to configure, no CORS headers to debug.

The TUI is built with Bubble Tea, same data, different surface. SSH into a box, run runwisp tui, and you’re looking at run history and live logs without opening a browser.

Both UIs are read-only plus trigger. They can start a task early or inspect a run, but they can’t mutate runwisp.toml. The config file is the source of truth; the UIs are windows into it.

What it costs

Honest accounting:

  • Binary size. ~25 MB stripped. Larger than a purpose-built cron wrapper. Smaller than a Node runtime.
  • Compile time. Pure-Go SQLite adds ~30 seconds to a clean build. CI caches make this invisible in practice.
  • No plugin system. Everything ships in the binary. You can’t drop in a custom notifier without recompiling. Notifications (Slack, Telegram, email) are built in; if yours isn’t, file an issue.
  • Single-writer SQLite. Fine for one daemon managing dozens of tasks. Not fine if you wanted to run ten daemons writing to the same database (don’t do this).

The bet

The bet is that operators would rather scp one file than debug a dependency graph. That a 25 MB binary that starts in under a second and idles at 25 MB of RAM is cheaper to operate than a lighter binary plus its runtime. That “it just works on a fresh box” is worth the compile-time cost.

Seven releases in, nothing has made us regret it.

  1. Scheduling restic or borg backups with alerts when they fail: systemd timer, cron or borgmatic

    Four ways to run restic or borg on a schedule and hear about it when a backup fails or never runs: a systemd timer with OnFailure=, cron with a Healthchecks ping, resticprofile and borgmatic. Tested with restic 0.18 and 0.19, including exit 3, stale locks and catch-up after downtime.

  2. Django periodic tasks without Celery or Redis: 5 Docker Compose setups

    You need clearsessions every night and a report every 15 minutes, and every tutorial adds Celery, beat and Redis. Five setups without them, each with a Compose file: a management command plus supercronic, APScheduler (and why it runs 3 times under gunicorn), Huey on SQLite, django-q2 on your database, and RunWisp.

Get started
GPL 3.0 · no accounts · free to self-host

Start in 30 seconds. Ship on your terms.

A single Go binary. If it doesn't earn the disk space, rm it. No account, no signup, no catch.

Install in 30 seconds
curl -fsSL https://get.runwisp.com | sh