bm2: A 5.5 MB MoonBit Process Manager That Runs Bun and Node.js with 1 ms Command Latency
A process manager that compiles to a 5.5 MB static binary with no runtime dependency cuts deployment surface area and memory overhead sharply compared to pm2's Node.js-based 23 MB install. For teams running Bun in production, bm2 is one of the first purpose-built managers that treats Bun as a first-class runtime alongside Node.
bm2 is a Linux process manager written in MoonBit, shipping as two native binaries totaling about 5.5 MB — roughly a quarter of pm2's size and with none of its Node.js runtime dependency. It manages Bun and Node.js applications through a per-user daemon controlled over a Unix socket, supporting multi-instance deployment, crash-auto-restart with a configurable budget, per-instance memory limits, graceful stop, and state persistence across daemon restarts.
A single `bm2.toml` file defines one project: runtime, script path, instance count, port range, memory cap, and restart policy. The CLI's `start` command always performs a full restart, so changing any config field takes effect immediately. Reserved environment variables — `BM2_APP_PORT`, `BM2_APP_INSTANCE`, and `NODE_ENV=production` — let each instance bind its own port and let the primary instance run migrations or cron jobs without extra coordination.
bm2 deliberately does not handle reverse proxying, load balancing, certificates, or hot reload. It leaves those to Nginx or Caddy, keeping its scope narrow: process hosting with built-in 10 MB × 10-generation log rotation, JSONL event streams for diagnostics, and a stale-socket recovery mechanism that auto-restarts a crashed daemon on the next CLI request.
Shipping a process manager as a native static binary eliminates the circular dependency of using a Node.js tool to manage Node.js processes — a single `bm2d` crash doesn't cascade through a runtime it itself depends on.
The 1 ms command latency versus pm2's 200–400 ms suggests the Unix-socket control path and MoonBit runtime avoid the event-loop serialization overhead that Node.js CLI tools pay on every invocation.
Deliberately scoping out reverse proxying, certificates, and hot reload keeps the codebase small and the failure domain narrow; the design bets that Nginx/Caddy already handle those concerns better than any process manager can.
Persisting instance state per project and fully deregistering killed projects means a daemon restart won't resurrect intentionally stopped apps — a common operational annoyance with pm2's dump/resurrect workflow.
The `min_uptime_ms` restart-budget gating prevents a flapping process from burning through its restart allowance on near-instant crashes, distinguishing genuine startup failures from runtime crashes.