Production-ready process manager with dashboard and programmatic API, designed for running your containers, services, and AI agents.
Start, stop, restart, and monitor any process — from dev servers to Docker containers. Zero config. One command. Beautiful dashboard included.
bun install -g bgrun
| Feature | PM2 | bgrun |
|---|---|---|
| Runtime | Node.js | Bun (5× faster startup) |
| Install | npm i -g pm2 (50+ deps) |
bun i -g bgrun (minimal deps) |
| Config format | JSON / JS / YAML | TOML (or none at all) |
| Dashboard | pm2 monit (TUI) |
bgrun --dashboard (full web UI) |
| Language support | Any | Any |
| Docker-aware | ❌ | ✅ detects container status |
| Port management | Manual | Auto-detect & cleanup |
| File watching | Built-in | Built-in |
| Programmatic API | ✅ | ✅ (first-class TypeScript) |
| Process persistence | ✅ | ✅ (SQLite) |
Note: The CLI is available as both
bgrunandbgr(alias). All examples below usebgrun.
# Install globally
bun install -g bgrun
# Start a process
bgrun --name my-api --directory ./my-project --command "bun run server.ts"
# List all processes
bgrun
# Open the web dashboard
bgrun --dashboardThat's it. bgrun tracks the PID, captures stdout/stderr, detects the port, and survives terminal close.
Launch with bgrun --dashboard and open http://localhost:3001. Processes are auto-grouped by working directory.
Expose with Caddy for remote access:
bgrun.yourdomain.com {
reverse_proxy localhost:3001
}
Features:
- Real-time process status via SSE (no polling)
- Start, stop, restart, and delete processes from the UI
- Live stdout/stderr log viewer with search and virtual scrolling
- Memory, PID, port, runtime, and guard restarts at a glance
- Guard toggle per-process (auto-restart on crash)
- Keyboard shortcuts —
↑/↓orj/knavigate,Enteropen,Rrestart,Sstop,Gguard,Ddelete,Nnew,?help - Search with debounce, result count badge, and persistence across SSE updates
- Auto-calibrated virtual scroll for large log files (10K+ lines)
- Dark / light theme toggle
- Responsive mobile layout with cards view
- Collapsible directory groups
- Right-click context menu on process rows
- Core Commands
- Dashboard
- File Watching
- Port Handling
- Docker Integration
- Caddy Reverse Proxy
- TOML Configuration
- Programmatic API
- Process Dependencies
- Log Rotation
- Guard (Auto-Restart)
- Migrating from PM2
- Edge Cases & Behaviors
- Full CLI Reference
bgrun --name my-api \
--directory ~/projects/my-api \
--command "bun run server.ts"Short form — if you're already in the project directory:
bgrun --name my-api --command "bun run server.ts"
# bgrun uses current directory by defaultbgrun # Pretty table
bgrun --json # Machine-readable JSON
bgrun --filter api # Filter by group (BGR_GROUP env)bgrun my-api # Show status, PID, port, runtime, command
bgrun my-api --logs # Show stdout + stderr interleaved
bgrun my-api --logs --log-stdout --lines 50 # Last 50 stdout lines onlybgrun --stop my-api # Graceful stop (SIGTERM → SIGKILL)
bgrun --restart my-api # Stop then start again with same command
bgrun --delete my-api # Stop and remove from database
bgrun --clean # Remove all stopped processes
bgrun --nuke # ☠️ Delete everythingWhen a process is stuck or its port is orphaned:
bgrun --name my-api --command "bun run server.ts" --force--force will:
- Kill the existing process by PID
- Detect all ports it was using (via OS
netstat) - Kill any zombie processes still holding those ports
- Wait for ports to free up
- Start fresh
bgrun ships with a built-in web dashboard for managing all your processes visually.
bgrun --dashboardThe dashboard provides:
- Real-time process table with status, PID, port, runtime
- Start/stop/restart/delete actions with one click
- Log viewer with monospace display and auto-scroll
- Process detail drawer with stdout/stderr tabs
- Auto-refresh every 5 seconds
The dashboard uses Melina.js for serving and follows smart port selection:
| Scenario | Behavior |
|---|---|
bgrun --dashboard |
Starts on port 3000. If busy, auto-falls back to 3001, 3002, etc. |
BUN_PORT=4000 bgrun --dashboard |
Starts on port 4000. Fails with error if port is busy. |
bgrun --dashboard --port 5000 |
Same as BUN_PORT=5000 — explicit, no fallback. |
| Dashboard already running | Prints current URL and PID instead of starting a second instance. |
The actual port is always detected from the running process and displayed correctly in bgrun output.
For development, bgrun can watch for file changes and auto-restart:
bgrun --name frontend \
--directory ~/projects/frontend \
--command "bun run dev" \
--watchThis monitors the working directory for changes and restarts the process when files are modified. Combine with --force to ensure clean restarts:
bgrun --name api \
--command "bun run server.ts" \
--watch \
--force \
--config .dev.tomlbgrun automatically detects which TCP ports a process is listening on by querying the OS. This means:
- No port configuration needed — bgrun discovers ports from
netstat - No environment variable assumptions — bgrun doesn't guess
PORTorBUN_PORT - Clean restarts —
--forcekills all orphaned port bindings before restarting - Accurate display — the port shown in
bgrunoutput is the actual bound port
1. bgrun spawns your process
2. Process starts and binds to a port (however it wants)
3. bgrun queries `netstat -ano` (Windows) or `ss -tlnp` (Linux)
4. bgrun finds all TCP LISTEN ports for the process PID
5. These ports are displayed in the table and used for cleanup
If you --force restart a process and its old port is still held by a zombie:
1. bgrun detects ports held by the old PID
2. Sends SIGTERM to the old process
3. Kills any remaining processes on those ports
4. Waits for ports to become free (up to 5 seconds)
5. Starts the new process
bgrun can manage Docker containers alongside regular processes:
# Start a Postgres container
bgrun --name postgres \
--command "docker run --name bgr-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret postgres:16"
# Start a Redis container
bgrun --name redis \
--command "docker run --name bgr-redis -p 6379:6379 redis:7-alpine"bgrun is Docker-aware — when it detects a docker run command, it:
- Checks container status via
docker inspectinstead of checking the PID - Handles container lifecycle — stops containers with
docker stoponbgrun --stop - Reports correct status — shows Running/Stopped based on container state, not process state
Instead of docker-compose.yml, use bgrun to orchestrate containers alongside your app:
#!/bin/bash
# start-stack.sh
# Database
bgrun --name db \
--command "docker run --name bgr-db -p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_DB=myapp \
-e POSTGRES_PASSWORD=secret \
postgres:16" \
--force
# Cache
bgrun --name cache \
--command "docker run --name bgr-cache -p 6379:6379 redis:7-alpine" \
--force
# Your app (not Docker, just a regular process)
bgrun --name api \
--directory ~/projects/my-api \
--command "bun run server.ts" \
--config production.toml \
--force
# See everything
bgrunThe advantage over Docker Compose: your app processes and Docker containers are managed in the same place with the same commands.
bgrun pairs naturally with Caddy for production deployments with automatic HTTPS.
# Start your app on any port (bgrun detects it)
bgrun --name my-api --command "bun run server.ts" --force
# Check which port it got
bgrun
# → my-api ● Running :3000 bun run server.tsCaddyfile:
api.example.com {
reverse_proxy localhost:3000
}
dashboard.example.com {
reverse_proxy localhost:3001
}# Start services
bgrun --name api --command "bun run api/server.ts" --force
bgrun --name frontend --command "bun run frontend/server.ts" --force
bgrun --name admin --command "bun run admin/server.ts" --force
# Start dashboard
bgrun --dashboardYou can even manage Caddy itself as a bgrun process:
bgrun --name caddy \
--directory /etc/caddy \
--command "caddy run --config Caddyfile" \
--forceNow bgrun shows your entire stack — app servers, databases, and reverse proxy — in one place.
bgrun loads TOML config files and flattens them into environment variables:
bgrun --name api --command "bun run server.ts" --config production.toml# production.toml
[server]
port = 3000
host = "0.0.0.0"
[database]
url = "postgresql://localhost/myapp"
pool_size = 10
[auth]
jwt_secret = "your-secret-here"
session_ttl = 3600Becomes:
SERVER_PORT=3000
SERVER_HOST=0.0.0.0
DATABASE_URL=postgresql://localhost/myapp
DATABASE_POOL_SIZE=10
AUTH_JWT_SECRET=your-secret-here
AUTH_SESSION_TTL=3600
The convention: [section] becomes the prefix, key becomes the suffix, joined with _, uppercased.
If no --config is specified, bgrun looks for .config.toml in the working directory automatically.
bgrun exposes its internals as importable TypeScript functions:
Packaging note: the CLI ships from
dist/index.js, the Bun programmatic API resolves throughdist/api.js, and the dashboard backend uses builtdist/*runtime artifacts viadashboard/lib/runtime.ts. Published packages are nowdist-first; repositorysrc/files remain the development/build source of truth and are not part of the runtime package surface.
bun add bgrunimport {
getAllProcesses,
getProcess,
isProcessRunning,
terminateProcess,
handleRun,
getProcessPorts,
readFileTail,
calculateRuntime,
} from 'bgrun'
// List all processes
const procs = getAllProcesses()
// Start a process programmatically
await handleRun({
action: 'run',
name: 'my-api',
command: 'bun run server.ts',
directory: '/path/to/project',
force: true,
remoteName: '',
})
// Check status
const proc = getProcess('my-api')
if (proc) {
const alive = await isProcessRunning(proc.pid)
const ports = await getProcessPorts(proc.pid)
const runtime = calculateRuntime(proc.timestamp)
console.log({ alive, ports, runtime })
}
// Read logs
const myProc = getProcess('my-api')
if (myProc) {
const stdout = await readFileTail(myProc.stdout_path, 100) // last 100 lines
const stderr = await readFileTail(myProc.stderr_path, 100)
}
// Stop a process
await terminateProcess(proc.pid)import { getAllProcesses, isProcessRunning, calculateRuntime } from 'bgrun'
// Express/Hono/Elysia endpoint
export async function GET() {
const procs = getAllProcesses()
const enriched = await Promise.all(
procs.map(async (p) => ({
name: p.name,
pid: p.pid,
running: await isProcessRunning(p.pid),
runtime: calculateRuntime(p.timestamp),
}))
)
return Response.json(enriched)
}If you're coming from PM2, here's a direct mapping of commands:
| PM2 | bgrun |
|---|---|
pm2 start app.js --name api |
bgrun --name api --command "node app.js" |
pm2 start app.js -i max |
(cluster mode not supported — use multiple named processes) |
pm2 list |
bgrun |
pm2 show api |
bgrun api |
pm2 logs api |
bgrun api --logs |
pm2 logs api --lines 50 |
bgrun api --logs --lines 50 |
pm2 stop api |
bgrun --stop api |
pm2 restart api |
bgrun --restart api |
pm2 delete api |
bgrun --delete api |
pm2 flush |
bgrun --clean |
pm2 kill |
bgrun --nuke |
pm2 monit |
bgrun --dashboard |
pm2 save / pm2 resurrect |
(automatic — processes persist in SQLite) |
PM2 ecosystem file:
// ecosystem.config.js
module.exports = {
apps: [
{
name: 'api',
script: 'server.js',
cwd: './api',
env: { PORT: 3000, NODE_ENV: 'production' },
},
{
name: 'worker',
script: 'worker.js',
cwd: './workers',
env: { QUEUE: 'default' },
},
],
}bgrun equivalent:
# api.toml
[server]
port = 3000
[node]
env = "production"#!/bin/bash
# start.sh
bgrun --name api --directory ./api --command "node server.js" --config api.toml --force
bgrun --name worker --directory ./workers --command "node worker.js" --force-
No cluster mode — bgrun manages independent processes. For multi-core, run multiple named instances (
api-1,api-2) behind a load balancer. -
No
pm2 startup— bgrun doesn't install itself as a system service. Use your OS init system (systemd, launchd, Windows Task Scheduler) to run bgrun at boot:# /etc/systemd/system/bgrun-api.service [Unit] Description=My API via bgrun [Service] ExecStart=/usr/local/bin/bgrun --name api --directory /var/www/api --command "bun run server.ts" --force Restart=always [Install] WantedBy=multi-user.target
-
Built-in log rotation — bgrun automatically rotates logs when they exceed 10MB, keeping the last 5000 lines. Manual rotation is also available via the dashboard API.
-
Bun required — bgrun runs on Bun, but the processes it manages can be anything: Node.js, Python, Ruby, Go, Docker, shell scripts.
bgrun supports declaring process startup dependencies via the BGR_DEPENDS_ON environment variable:
# Start a price listener (no dependencies)
bgrun --name price-feed --command "bun run sqd.ts" --force
# Start a worker that depends on the price feed
BGR_DEPENDS_ON=price-feed bgrun --name mm-worker --command "bun run worker.ts" --forceWhen you start mm-worker, bgrun will automatically start price-feed first if it's not already running.
- Topological sort — processes start in correct dependency order
- Cycle detection — bgrun warns if dependencies form a cycle
- Auto-start — unmet dependencies are started automatically
- API —
GET /api/depsreturns the full dependency graph with startup order
curl -X POST http://localhost:3001/api/deps \
-H 'Content-Type: application/json' \
-d '{"name": "mm-worker", "dependsOn": ["price-feed", "redis"]}'bgrun includes automatic log rotation to prevent unbounded log file growth:
- Size-based rotation: Files exceeding 10MB are truncated, keeping the last 5000 lines
- Automatic checks: Rotation runs every 60 seconds alongside the dashboard
- Rotation header: Rotated files include a timestamp header for auditability
- Manual rotation: Trigger via the dashboard API
# Check log sizes
curl http://localhost:3001/api/logs/rotate
# Force rotation now
curl -X POST http://localhost:3001/api/logs/rotatebgrun includes a standalone guard process that monitors and auto-restarts crashed processes:
# Enable guard for a process
BGR_KEEP_ALIVE=true bgrun --name my-api --command "bun run server.ts" --forceThe guard checks processes every 30 seconds and restarts any that have stopped. Features:
- Exponential backoff — avoids restart storms for processes that keep crashing
- Per-process toggle — enable/disable guard via the dashboard shield icon
- Guard sentinel — dashboard shows a pulsing green dot when the guard is active
- Restart counter — dashboard tracks total guard restarts across all processes
bgrun records the process as Stopped. The PID and log files are preserved so you can inspect what happened:
bgrun my-api --logs --log-stderrFor auto-restart on crash, use the guard script:
bun run guard.ts my-api 30 # Check every 30 seconds, restart if deadbgrun queries the OS for all TCP ports held by the old PID, kills them, and waits up to 5 seconds for cleanup. If ports are still held after that, the new process starts anyway (and will likely pick a different port).
The new process replaces the old one. If the old one is still running, use --force to kill it first. Without --force, bgrun will refuse to start if a process with that name is already running.
The managed processes keep running — they're independent OS processes. When you run bgrun again, it reconnects to the SQLite database and checks which PIDs are still alive. Dead processes are marked as Stopped.
bgrun works on Windows. Process management uses taskkill and wmic instead of Unix signals. Port detection uses netstat -ano. The dashboard runs in your browser, so it works everywhere.
Not directly — bgrun manages processes on the local machine. For remote management, run bgrun on the remote server and expose the dashboard behind a reverse proxy (see Caddy section).
By default, logs go to ~/.bgr/<name>-out.txt and ~/.bgr/<name>-err.txt. Override with:
bgrun --name api \
--command "bun run server.ts" \
--stdout /var/log/api/stdout.log \
--stderr /var/log/api/stderr.logTag processes with BGR_GROUP to organize and filter them:
BGR_GROUP=prod bgrun --name api --command "bun run server.ts" --force
BGR_GROUP=prod bgrun --name worker --command "bun run worker.ts" --force
BGR_GROUP=dev bgrun --name dev-server --command "bun run dev" --force
# Show only production processes
bgrun --filter prod
# Show only dev processes
bgrun --filter devPull the latest changes before starting:
bgrun --name api \
--directory ~/projects/api \
--command "bun run server.ts" \
--fetch \
--force--fetch runs git pull in the working directory before starting the process. Combine with --force for a clean deploy workflow:
# Deploy script
bgrun --name api --directory /var/www/api --command "bun run server.ts" --fetch --force~/.bgr/
├── bgr.sqlite # Process database (SQLite)
├── myapp-out.txt # stdout logs
├── myapp-err.txt # stderr logs
├── bgr-dashboard-out.txt # Dashboard stdout
└── bgr-dashboard-err.txt # Dashboard stderr
All state lives in ~/.bgr/. To reset everything, delete this directory.
| Option | Description | Default |
|---|---|---|
--name <name> |
Process name | (required for start) |
--directory <path> |
Working directory | Current directory |
--command <cmd> |
Command to execute | (required for start) |
--config <path> |
TOML config file for env vars | .config.toml |
--force |
Kill existing process and ports before starting | false |
--fetch |
Git pull before starting | false |
--watch |
Auto-restart on file changes | false |
--stdout <path> |
Custom stdout log path | ~/.bgr/<name>-out.txt |
--stderr <path> |
Custom stderr log path | ~/.bgr/<name>-err.txt |
--db <path> |
Custom SQLite database path | ~/.bgr/bgr.sqlite |
--json |
Output process list as JSON | false |
--filter <group> |
Filter by BGR_GROUP |
(show all) |
--logs |
Show process logs | false |
--log-stdout |
Show only stdout | false |
--log-stderr |
Show only stderr | false |
--lines <n> |
Number of log lines | All |
--stop <name> |
Stop a process | - |
--restart <name> |
Restart a process | - |
--delete <name> |
Delete a process | - |
--clean |
Remove stopped processes | - |
--nuke |
Delete ALL processes | - |
--dashboard |
Launch web dashboard | - |
--port <number> |
Port for dashboard | 3000 |
--version |
Show version | - |
--help |
Show help | - |
| Variable | Description | Default |
|---|---|---|
DB_NAME |
Custom database file name | bgr |
BGR_GROUP |
Assign process to a group | (none) |
BGR_KEEP_ALIVE |
Enable guard auto-restart for this process | false |
BGR_DEPENDS_ON |
Comma-separated list of process dependencies | (none) |
BUN_PORT |
Dashboard port (explicit, no fallback) | (auto: 3000+) |
- Bun v1.0.0+
MIT
Built with ⚡ Bun