buncargo
v3.0.0
Published
A Bun-powered development environment CLI for managing Docker Compose services, dev servers, and environment variables
Downloads
1,325
Maintainers
Readme
Buncargo
A Bun-first development environment toolkit that eliminates the friction of local dev setup. Define your entire dev stack in a single typed config file—Docker services, app servers, environment variables, migrations, and more.
Why Buncargo?
The problem: Local development environments are fragile. Teams maintain separate docker-compose.yml files, scatter port assignments across .env files, manually manage container lifecycles, and struggle with port conflicts when working on multiple branches.
The solution: Buncargo provides a single source of truth for your dev environment. One dev.config.ts file defines everything. Type-safe. Auto-generated Docker Compose. Worktree-aware port isolation. Zero configuration drift.
Key Features
- Single config file — Define services, apps, ports, URLs, migrations, and hooks in one typed
dev.config.ts - Auto-generated Docker Compose — No manual compose files; buncargo generates them from your config
- Worktree isolation — Each git worktree gets unique ports and isolated containers automatically
- Built-in service presets — One-liner setup for Postgres, Redis, ClickHouse with health checks and URL templates
- Custom service support — Full Docker Compose escape hatch for any service
- Dev server orchestration — Start and monitor multiple app servers with health checks
- Public tunnels — Expose services via Cloudflare Quick Tunnels for webhook testing and mobile dev
- Prisma integration — Run Prisma commands with auto-injected
DATABASE_URL - Lifecycle hooks — Run migrations, seeders, or custom scripts at the right time
- Programmatic API — Access ports/URLs in tests or scripts
- Watchdog auto-shutdown — Containers stop automatically after inactivity
Quick Start
1. Install
bun add -d buncargo2. Create dev.config.ts
import { defineDevConfig, service } from 'buncargo'
export default defineDevConfig({
projectPrefix: 'myapp',
services: {
postgres: service.postgres({ database: 'mydb' }),
redis: service.redis(),
},
apps: {
api: {
port: 3000,
devCommand: 'bun run dev',
cwd: 'apps/backend',
},
web: {
port: 5173,
devCommand: 'bun run dev',
cwd: 'apps/frontend',
},
},
envVars: (ports, urls) => ({
DATABASE_URL: urls.postgres,
REDIS_URL: urls.redis,
API_PORT: ports.api,
}),
})3. Add scripts to package.json
{
"scripts": {
"dev": "bunx buncargo dev",
"dev:up": "bunx buncargo dev --up-only",
"dev:down": "bunx buncargo dev --down",
"dev:reset": "bunx buncargo dev --reset",
"dev:expose": "bunx buncargo dev --expose",
"prisma": "bunx buncargo prisma"
}
}4. Run
bun run devBuncargo will:
- Generate a Docker Compose file from your config
- Start all containers and wait for health checks
- Run any configured migrations
- Start your dev servers
- Print all ports and URLs
CLI Commands
bunx buncargo dev # Start containers + dev servers
bunx buncargo dev --up-only # Start containers only (no dev servers)
bunx buncargo dev --down # Stop containers
bunx buncargo dev --reset # Stop containers and remove volumes
bunx buncargo dev --expose # Start with public tunnels for expose:true targets
bunx buncargo dev --expose=api # Expose specific targets
bunx buncargo dev --migrate # Run migrations only
bunx buncargo dev --seed # Run migrations and seeders
bunx buncargo prisma <args> # Run Prisma CLI with correct DATABASE_URL
bunx buncargo typecheck # Run TypeScript typecheck across workspaces
bunx buncargo env # Print ports/URLs as JSON
bunx buncargo help # Show help
bunx buncargo version # Show versionServices
Built-in Presets
Use service.* helpers for common databases with sensible defaults:
services: {
postgres: service.postgres({ database: 'mydb' }),
redis: service.redis(),
clickhouse: service.clickhouse({ database: 'analytics' }),
}Each preset includes:
- Default Docker image
- Health check configuration
- URL template (e.g.,
postgresql://postgres:postgres@localhost:5432/mydb) - Volume for data persistence
Custom Services
Use service.custom() for any Docker service:
services: {
rabbitmq: service.custom({
port: 5672,
healthCheck: false,
docker: {
image: 'rabbitmq:3-management-alpine',
ports: ['${RABBITMQ_PORT:-5672}:5672', '15672:15672'],
environment: {
RABBITMQ_DEFAULT_USER: 'guest',
RABBITMQ_DEFAULT_PASS: 'guest',
},
},
}),
nats: service.custom({
port: 4222,
docker: {
image: 'nats:2-alpine',
ports: ['${NATS_PORT:-4222}:4222'],
},
}),
}Apps
Define dev servers to run alongside containers:
apps: {
api: {
port: 3000,
devCommand: 'bun run dev',
cwd: 'apps/backend',
healthEndpoint: '/health',
},
web: {
port: 5173,
devCommand: 'bun run dev',
cwd: 'apps/frontend',
healthEndpoint: '/',
},
}Environment Variables
The envVars function builds all env vars from computed ports and URLs:
envVars: (ports, urls, { localIp, publicUrls }) => ({
DATABASE_URL: urls.postgres,
REDIS_URL: urls.redis,
API_PORT: ports.api,
EXPO_API_URL: `http://${localIp}:${ports.api}`,
WEBHOOK_URL: publicUrls.api ?? urls.api,
})These are injected into:
- Docker Compose services
- Dev server processes
- Hook
exec()calls - Prisma commands
Worktree Isolation
When working in git worktrees, buncargo automatically assigns unique port offsets (10-99) so each worktree has isolated:
- Ports (e.g., postgres on 5442 instead of 5432)
- Docker Compose project names
- Containers, networks, and volumes
This means you can run multiple branches simultaneously without conflicts.
To disable isolation and share state across worktrees:
options: {
worktreeIsolation: false
}Public Tunnels
Expose local services to the internet using Cloudflare Quick Tunnels:
services: {
postgres: service.postgres({ database: 'mydb' }),
},
apps: {
api: {
port: 3000,
devCommand: 'bun run dev',
expose: true, // Mark as exposable
},
}bun run dev --expose # Expose all targets with expose: true
bun run dev --expose=api # Expose specific targetsPublic URLs are printed in the console and available via publicUrls in envVars:
envVars: (_ports, urls, { publicUrls }) => ({
WEBHOOK_URL: publicUrls.api ?? urls.api,
})Lifecycle Hooks
Run code at specific points in the startup/shutdown cycle:
hooks: {
afterContainersReady: async (ctx) => {
await ctx.exec('bunx prisma migrate deploy', { cwd: 'packages/prisma' })
},
beforeServers: async (ctx) => {
await ctx.exec('bun run seed')
},
afterServers: async (ctx) => {
console.log(`API running at ${ctx.urls.api}`)
},
beforeStop: async (ctx) => {
await ctx.exec('bun run cleanup', { throwOnError: false })
},
}Hook context provides:
interface HookContext {
projectName: string
ports: { postgres: number, api: number, ... }
urls: { postgres: string, api: string, ... }
publicUrls: { api?: string, ... }
root: string
isCI: boolean
portOffset: number
localIp: string
exec(cmd: string, opts?): Promise<ExecResult>
}Migrations and Seeding
Migrations
Run migration commands after containers are healthy:
migrations: [
{ name: 'prisma', command: 'bunx prisma migrate deploy', cwd: 'packages/prisma' },
{ name: 'clickhouse', command: 'bun run migrate:clickhouse' },
]Seeding
Seed the database with a check to avoid re-seeding:
seed: {
command: 'bun run seed',
check: ({ checkTable }) => checkTable('User', 'postgres'),
}Prisma Integration
Configure Prisma to use the correct database URL:
prisma: {
cwd: 'packages/prisma',
service: 'postgres', // Default: 'postgres'
urlEnvVar: 'DATABASE_URL', // Default: 'DATABASE_URL'
}Then run Prisma commands through buncargo:
bun run prisma migrate dev
bun run prisma studio
bun run prisma db pushBuncargo ensures the database container is running and injects the correct DATABASE_URL with worktree-aware ports.
Programmatic API
Access the dev environment from code (useful for tests):
import { loadDevEnv } from 'buncargo'
const env = await loadDevEnv()
console.log(env.ports.postgres) // 5432 (or offset port)
console.log(env.urls.api) // http://localhost:3000
console.log(env.urls.postgres) // postgresql://postgres:postgres@localhost:5432/mydb
// Start/stop programmatically
await env.start()
await env.stop({ removeVolumes: true })
// Build env vars for subprocess
const envVars = env.buildEnvVars()Docker Compose Generation
Buncargo generates Docker Compose from your config. No external docker-compose.yml is read.
docker: {
generatedFile: '.buncargo/docker-compose.generated.yml',
writeStrategy: 'always', // or 'if-missing'
volumes: {
shared_cache: {},
},
}Health Checks
Built-in health check types:
| Type | Description |
|------|-------------|
| pg_isready | PostgreSQL readiness check |
| redis-cli | Redis PING check |
| http | HTTP endpoint check |
| tcp | TCP port check |
Or provide a custom health check function:
healthCheck: async (port) => {
const res = await fetch(`http://localhost:${port}/health`)
return res.ok
}Watchdog Auto-Shutdown
When running via CLI, containers automatically stop after 10 minutes of inactivity. The watchdog monitors heartbeats and shuts down orphaned environments.
Full Example
import { defineDevConfig, service } from 'buncargo'
export default defineDevConfig({
projectPrefix: 'platform',
services: {
postgres: service.postgres({ database: 'platform' }),
redis: service.redis(),
clickhouse: service.clickhouse({ database: 'platform' }),
},
apps: {
api: {
port: 3000,
expose: true,
devCommand: 'bun run dev',
cwd: 'apps/backend',
healthEndpoint: '/health',
},
web: {
port: 5173,
devCommand: 'bun run dev',
cwd: 'apps/frontend',
},
},
envVars: (ports, urls, { localIp, publicUrls }) => ({
DATABASE_URL: urls.postgres,
REDIS_URL: urls.redis,
CLICKHOUSE_URL: urls.clickhouse,
API_URL: urls.api,
VITE_API_URL: urls.api,
EXPO_API_URL: `http://${localIp}:${ports.api}`,
WEBHOOK_URL: publicUrls.api ?? urls.api,
}),
migrations: [
{ name: 'prisma', command: 'bunx prisma migrate deploy', cwd: 'packages/prisma' },
],
seed: {
command: 'bun run seed',
check: ({ checkTable }) => checkTable('User', 'postgres'),
},
prisma: {
cwd: 'packages/prisma',
},
hooks: {
afterContainersReady: async (ctx) => {
console.log(`Containers ready on port offset ${ctx.portOffset}`)
},
},
})License
MIT
