relife2
v1.0.3
Published
Process manager for Node.js & Bun apps — a predictable PM2 alternative: exactly one locked daemon, no silent self-duplication, Bun as a first-class citizen.
Readme
relife2 — your apps stay up. Period.
The PM2 you wish you had from day one — without the gotchas.
You run 5–10 apps on one VPS. PM2 was great — until
pm2 updatesilently spawned a second daemon,pm2 savewas forgotten after a reboot, and bun apps ran asnodewith extra overhead. relife2 is the fix.
Why teams switch from PM2
| PM2 pain you know | relife2 answer |
|---|---|
| pm2 update duplicates the God daemon → 2→4→8 daemons, OOM | Exactly one daemon, always. Socket bind + flock. Second daemon can't start. daemon-upgrade is explicit and locked. |
| Reboot wipes everything if you forgot pm2 save | Autosave on by default. Every change hits snapshot.json + journal. Reboot → systemd + linger → all apps back. |
| interpreter: bun actually runs as node | Bun is first-class. bun <script> directly, auto-detect via bun.lockb/bun.lock, bun --hot in dev. |
| Log files grow forever (needs pm2-logrotate) | Built-in rotation: rotate --max-size --retain with gzip, no plugin. |
| Unknown config fields silently ignored | Honest errors: warnings for unknown fields, errors for incompatible combos. |
| Daemon is heavy (God ~60–90 MB) | Light: ~20–30 MB RSS daemon, no God overhead. |
One command to prove it:
relife2 doctor— checks snapshot, lock, orphans, transport, uptime. PM2 has no equivalent.
Who is it for
- Solo dev / small team with 5–15 apps on a single Oracle Linux 9 / Debian / Ubuntu VPS (the primary target — developed on Windows, but Linux-first)
- You use Caddy for TLS/reverse proxy and want real supervision for the upstreams
- You ship Bun + Node mix (Nuxt, Astro, bots, Java services, Rust binaries)
- You are tired of babysitting processes
If you run Kubernetes for everything, you don't need this. If you run a VPS and want systemd-grade reliability without writing unit files by hand — you do.
30-second demo
npm i -g relife2 # or: curl single binary → /usr/local/bin/relife2
relife2 start ecosystem.config.cjs
relife2 list
relife2 logs web -f # follow
relife2 doctor # 5 checks, exit 1 on error
relife2 startup # systemd user unit + loginctl enable-linger
# reboot → everything is back. No pm2 save dance.Migrate from PM2 in one line:
relife2 import-pm2 ~/.pm2/dump.pm2 && relife2 list # reads PM2 dump, starts via relife2
relife2 from-caddy /etc/caddy/Caddyfile --dry-run # Caddyfile → relife2.config.cjsFind forgotten configs (the PM2 paper trail):
relife2 find # scans $HOME depth 6
relife2 find --root /opt --depth 8 --json | jq .
relife2 find start # starts every stopped config it foundFeatures that sell themselves
- Single binary deploy:
bun build --compile→relife2-linux-x64(~40–60 MB). No Node/Bun on the VPS.tar.gz/.rpm/.debready. - Forgotten-path healing: moved from
/home/debianto/opt? Configs with stalecwd/scriptauto-fix (tries~/tail,sourceDir, basename search) and logsnot found → auto-fixedinstead of crash-looping. - Caddy-native:
from-caddyparsesreverse_proxy/php_fastcgiloopback upstreams, generates one app per port withHOST/PORT/SITE_URL, finds entry (Nuxt .output/server/index.mjsetc.),--dry-runplan before writing. - Dev that doesn't fight you:
relife2 dev ./server.ts --watch ./src --hot→bun --hotfor.ts,fs.watchotherwise.qto quit. - Upgrade without drama:
relife2 daemon-upgrade—save → shutdown old → spawn new → resurrectunder lock, protocol version checked. No silent second daemon. - Observability out of the box:
list/describe/metrics(daemon + per-app RSS) /monitTUI /logs --lines -f. - PM2-compatible config surface:
ecosystem.config.{js,cjs,mjs,json}+relife2.config.ts, all 8example/configs work without edits (golden tests).
Installation
npm (any OS, Node ≥20):
npm i -g relife2
relife2 --version # relife2 1.0.1 (node)
rl2 --version # aliasSingle binary (VPS — recommended, no runtime on target):
npm run build:single:linux # → dist/relife2-linux-x64
scp dist/relife2-linux-x64 vps:/usr/local/bin/relife2
ssh vps 'chmod +x /usr/local/bin/relife2 && relife2 --version'
# or from GitHub Releases:
curl -L https://github.com/<org>/relife2/releases/latest/download/relife2-linux-x64 -o /usr/local/bin/relife2
chmod +x /usr/local/bin/relife2tar.gz / rpm / deb:
npm run dist:tar # → dist/relife2-1.0.1-linux-x64.tar.gz (binary + README + LICENSE)
npm run dist:rpm # → dist/relife2-1.0.1-1.x86_64.rpm (needs fpm)
npm run dist:deb # → dist/relife2_1.0.1_amd64.deb (uses dpkg-deb or fpm)
sudo dnf install ./dist/relife2-*.rpm # Oracle Linux / RHEL / Fedora
sudo apt install ./dist/relife2_*_amd64.deb # Debian / UbuntuAll packages install /usr/bin/relife2 + /usr/bin/rl2; autostart is still relife2 startup (user unit) so it respects XDG_RUNTIME_DIR.
Autostart (Linux):
relife2 startup # writes ~/.config/systemd/user/relife2.service + loginctl enable-linger
systemctl --user daemon-reload && systemctl --user enable --now relife2
systemctl --user status relife2
relife2 doctorQuick Start
# 1. From a config (PM2-compatible)
relife2 start ecosystem.config.cjs
relife2 list
relife2 logs myapp --lines 100
relife2 doctor
# 2. Single script
relife2 start ./server.js --name myapp -- --port 3000
# 3. Auto-discover relife2.config.* / ecosystem.config.* in cwd
relife2 start
# 4. Zero-downtime reload / scale
relife2 reload myapp
relife2 start --instances max --exec-mode cluster --env production
# 5. Find & rescue forgotten configs
relife2 find --root /opt --depth 8
relife2 find start
# 6. Caddyfile → relife2
relife2 from-caddy /etc/caddy/Caddyfile --dry-run
relife2 from-caddy /etc/caddy/Caddyfile -o relife2.config.cjs && relife2 start relife2.config.cjsMore → docs/CLI.md · docs/CONFIG.md · docs/INSTALL.md
Config at a glance
// ecosystem.config.cjs — same file you use for PM2
module.exports = {
apps: [{
name: "web",
script: "./dist/server/entry.mjs",
cwd: __dirname,
interpreter: "bun", // "bun" | "node" | "none" | "/usr/bin/python"
args: ["--port", "3000"], // string or string[]
instances: "max", // number | "max" (= cpu count)
exec_mode: "cluster",
env: { NODE_ENV: "production", PORT: "3000" },
env_production: { PORT: "4000" }, // merged with --env production
env_file: "./.env", // dotenv, does NOT override real env
out_file: "./logs/web-out.log",
error_file: "./logs/web-err.log",
merge_logs: true,
time: true,
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
autorestart: true,
max_restarts: 15,
restart_delay: 3000,
min_uptime: "10s",
kill_timeout: 5000,
max_memory_restart: "512M",
cron_restart: "0 4 * * *",
watch: false, // true | false | ["./src", ".env"]
watch_delay: 1000,
wait_ready: false, // wait for process.send('ready')
}]
};Full matrix → docs/CONFIG.md. CLI overrides: relife2 start --env production --instances max.
Bun, Caddy, Linux — first-class
- Bun:
interpreter: "bun"spawnsbundirectly; auto-detect viabun.lockb/bun.lockor.tsscript;relife2 dev ./app.tsusesbun --hot(HMR);bun relife2 ...works (CI matrixnode+bun). - Caddy: Caddy does TLS/static/proxy, relife2 does supervision.
from-caddy+# relife2: dir=… name=…hints. - Linux: XDG-first data (
$XDG_DATA_HOME/relife2) + runtime ($XDG_RUNTIME_DIR/relife2),RELIFE2_DIRoverride for tests,/procRSS,setsidprocess groups, systemdRestart=always+KillMode=process. SELinux enforcing = experimental (default contexts, no custom policy — author has it disabled).
Development & benchmark
npm install
npm run build # → dist/cli.js (esbuild, single ESM, shebang)
npm run typecheck && npm run lint && npm test # 67 tests (m7 integrity inside)
bun dist/cli.js --version
npm run bench # startup latency + daemon RSS
npm run bench -- --pm2 # compare with PM2 (if installed)See docs/BENCHMARK.md — numbers are host-dependent, run on your VPS for truth.
License
MIT — see LICENSE. Roadmap → TODO.md, decisions → docs/DECISIONS.md.
