npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bgrun

v3.4.0

Published

bgrun — A lightweight process manager for Bun

Readme

⚡ bgrun

Bun Background Runner — a modern process manager built on Bun

npm bun license

Start, stop, restart, and monitor any process — from dev servers to Docker containers. Zero config. One command. Beautiful dashboard included.

bun install -g bgrun

Why 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 bgrun and bgr (alias). All examples below use bgrun.


Quick Start

# 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 --dashboard

That's it. bgrun tracks the PID, captures stdout/stderr, detects the port, and survives terminal close.


Table of Contents


Core Commands

Starting a process

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 default

Listing processes

bgrun                  # Pretty table
bgrun --json           # Machine-readable JSON
bgrun --filter api     # Filter by group (BGR_GROUP env)

Viewing a process

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 only

Stopping, restarting, deleting

bgrun --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 everything

Force restart

When a process is stuck or its port is orphaned:

bgrun --name my-api --command "bun run server.ts" --force

--force will:

  1. Kill the existing process by PID
  2. Detect all ports it was using (via OS netstat)
  3. Kill any zombie processes still holding those ports
  4. Wait for ports to free up
  5. Start fresh

Dashboard

bgrun ships with a built-in web dashboard for managing all your processes visually.

bgrun --dashboard

The 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

Dashboard port selection

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.


File Watching

For development, bgrun can watch for file changes and auto-restart:

bgrun --name frontend \
    --directory ~/projects/frontend \
    --command "bun run dev" \
    --watch

This 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.toml

Port Handling

bgrun 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 PORT or BUN_PORT
  • Clean restarts--force kills all orphaned port bindings before restarting
  • Accurate display — the port shown in bgrun output is the actual bound port

How it works

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

Port conflict resolution

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

Docker Integration

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"

How bgrun handles Docker

bgrun is Docker-aware — when it detects a docker run command, it:

  1. Checks container status via docker inspect instead of checking the PID
  2. Handles container lifecycle — stops containers with docker stop on bgrun --stop
  3. Reports correct status — shows Running/Stopped based on container state, not process state

Docker Compose alternative

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
bgrun

The advantage over Docker Compose: your app processes and Docker containers are managed in the same place with the same commands.


Caddy Reverse Proxy

bgrun pairs naturally with Caddy for production deployments with automatic HTTPS.

Basic setup

# 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.ts

Caddyfile:

api.example.com {
    reverse_proxy localhost:3000
}

dashboard.example.com {
    reverse_proxy localhost:3001
}

Multi-service setup

# 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 --dashboard

Managing Caddy with bgrun

You can even manage Caddy itself as a bgrun process:

bgrun --name caddy \
    --directory /etc/caddy \
    --command "caddy run --config Caddyfile" \
    --force

Now bgrun shows your entire stack — app servers, databases, and reverse proxy — in one place.


TOML Configuration

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 = 3600

Becomes:

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.


Programmatic API

bgrun exposes its internals as importable TypeScript functions:

bun add bgrun

Process management

import {
  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)

Build a custom dashboard

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)
}

Migrating from PM2

If you're coming from PM2, here's a direct mapping of commands:

Command mapping

| 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) |

ecosystem.config.js → TOML + shell script

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

Key differences

  1. No cluster mode — bgrun manages independent processes. For multi-core, run multiple named instances (api-1, api-2) behind a load balancer.

  2. 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
  3. No log rotation — bgrun writes to plain text files in ~/.bgr/. Use logrotate or similar tools, or specify custom log paths with --stdout and --stderr.

  4. Bun required — bgrun runs on Bun, but the processes it manages can be anything: Node.js, Python, Ruby, Go, Docker, shell scripts.


Edge Cases & Behaviors

What happens when a process crashes?

bgrun records the process as Stopped. The PID and log files are preserved so you can inspect what happened:

bgrun my-api --logs --log-stderr

For auto-restart on crash, use the guard script:

bun run guard.ts my-api 30  # Check every 30 seconds, restart if dead

What happens on bgrun --force if the port is stuck?

bgrun 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).

What happens if I start two processes with the same name?

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.

What happens if bgrun itself is killed?

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.

What about Windows?

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.

Can I manage processes on a remote server?

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).


Custom Log Paths

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.log

Process Groups

Tag 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 dev

Git Integration

Pull 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

File Structure

~/.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.


Full CLI Reference

| 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 | - |

Environment Variables

| Variable | Description | Default | |----------|-------------|---------| | DB_NAME | Custom database file name | bgr | | BGR_GROUP | Assign process to a group | (none) | | BUN_PORT | Dashboard port (explicit, no fallback) | (auto: 3000+) |


Requirements


License

MIT


Built by Mements with ⚡ Bun