local dev process manager
- Rust 89.3%
- HTML 8.9%
- Procfile 1.8%
| src | ||
| static | ||
| test_fixtures | ||
| .clippy.toml | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
| rustfmt.toml | ||
backfin
local dev process manager. reads a Procfile, runs everything concurrently.
cargo install backfin
why
single Rust binary — daemon, CLI client, and web dashboard all in one.
quick start
cat > Procfile << 'EOF'
web: echo "starting web" && while true; do echo "request GET /"; sleep 3; done
worker: echo "starting worker" && while true; do echo "job done"; sleep 5; done
EOF
backfin start
commands
| command | what it does |
|---|---|
backfin start |
start daemon + all Procfile processes, print status, exit |
backfin start <name> |
start a specific process |
backfin status |
table: name, status, pid, uptime, restart count |
backfin stop <name> |
stop a process (resolves short names like web → project/web) |
backfin stop-all |
stop every managed process |
backfin restart <name> |
restart a process |
backfin logs <name> |
tail logs for a process via socket |
backfin web |
start the web dashboard on port 9999 |
procfile formats
backfin supports two formats. Procfile.toml is the preferred format — it's more descriptive and supports per-process configuration.
TOML (recommended)
# Procfile.toml
[[processes]]
name = "web"
cmd = "cargo run"
cwd = "./web"
env = { RUST_LOG = "debug", PORT = "3000" }
links = ["http://localhost:3000"]
lifecycle = "always" # always | on-failure | never
backoff = true # exponential restart delay
[[processes]]
name = "worker"
cmd = "cargo run --bin worker"
env_file = ".env.worker" # load env vars from a file
lifecycle = "on-failure" # only restart on non-zero exit
[[processes]]
name = "oneoff"
cmd = "./scripts/migrate"
lifecycle = "never" # never restart
backoff = false
Fields:
name— process name (required)cmd— shell command to run (required)cwd— working directory (optional, defaults to project root)env— environment variables overrideenv_file— load environment from a filelinks— URLs shown in the web dashboardlifecycle— restart behavior:always(default),on-failure,neverbackoff— use exponential backoff on restart (default: true)
Classic Procfile
# Procfile or Procfile.dev
web: cargo run
worker: cargo run --bin worker
Inline env vars are supported:
api: PORT=8080 DEBUG=true go run .
File priority: Procfile.toml > Procfile.dev > Procfile > procfile
web dashboard
backfin web # → http://localhost:9999
built-in SPA with:
- process table (status, pid, uptime, restarts)
- live streaming logs over SSE
- per-process start / stop / restart / clear buttons
- no external dependencies — single HTML file embedded in the binary
how it works
backfin start
├── spawns daemon process (background)
├── daemon listens on ~/.backfin/daemon.sock for CLI commands
├── daemon serves web dashboard on port 9999
├── CLI parses Procfile, sends process map to daemon via socket
├── daemon spawns each process, reads stdout/stderr line-by-line
├── output is ring-buffered (last 1000 lines per process)
├── broadcast to subscribers (web SSE, CLI logs)
└── CLI prints status table + web URL and exits
crashed processes restart automatically with exponential backoff: 1s → 2s → 4s → … → 60s max.
process definitions and PIDs survive daemon restarts — state is persisted to ~/.backfin/state.json. if the daemon dies, the next start detects surviving PIDs and re-attaches to them.
architecture
src/
main.rs CLI parsing + socket client
procfile.rs Procfile + .env parser
manager.rs ProcessManager (spawn, stop, restart, ring buffers, broadcast)
daemon.rs daemon (process I/O, exit monitor w/ backoff, Unix socket, PID reattach)
web.rs axum HTTP server (SSE, REST API, static HTML)
static/
index.html web dashboard (vanilla JS, no frameworks)
- tokio async runtime
- axum for the web server
- owo-colors for terminal output
- Unix socket IPC between CLI client and daemon
- broadcast channels for log distribution
- single binary —
backfinis the daemon, the CLI, and the web server
license
AGPL-3.0