跪拜 Guibai
← All articles
Frontend · Backend · Developer

bm2: A 5.5 MB MoonBit Process Manager That Runs Bun and Node.js with 1 ms Command Latency

By 前端之虎陈随易 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

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.

Summary

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.

Takeaways
bm2 compiles to two native MoonBit binaries totaling ~5.5 MB, with zero runtime dependencies beyond the Linux kernel.
Idle daemon memory sits around 2.6 MB versus pm2's ~50 MB; CLI commands respond in ~1 ms versus pm2's 200–400 ms.
One `bm2.toml` configures a single project with instance count, port range, memory limit, restart budget, and stop timeout.
`bm2 start` always performs a full restart, so any config change — instances, port, script — applies immediately on the next start.
Reserved env vars (`BM2_APP_PORT`, `BM2_APP_INSTANCE`, `NODE_ENV=production`) let each instance bind its own port and let the primary instance gate one-off tasks.
bm2 does not manage Nginx, domains, certificates, hot reload, or boot auto-start; load balancing is delegated to a separate gateway.
Log rotation is built-in at 10 MB per file with 10 generations; application logs and daemon management logs are stored separately.
A stale Unix socket left by a crashed daemon is detected, removed, and replaced by a fresh daemon on the next CLI request, with one automatic retry.
`bm2 reload` swaps the daemon binary without stopping managed processes; the new daemon adopts running instances under the same PID.
Kernel >= 5.3 is required for pidfd-based process tracking; non-Linux builds explicitly refuse to run.
Conclusions

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.

Concepts & terms
pidfd
A Linux kernel interface (>= 5.3) that provides a file-descriptor-based handle to a process, allowing race-free process tracking even if the PID is reused. bm2 uses it to reliably monitor child processes.
MoonBit
A modern systems programming language that compiles to native code via WebAssembly and native backends. bm2 is written in MoonBit, producing small, self-contained binaries with no garbage-collection runtime dependency.
Restart budget
A configurable limit on consecutive abnormal restarts. When exceeded, the instance enters an `errored` state and stops restarting, preventing infinite crash loops. A clean exit before `min_uptime_ms` also counts against the budget.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗