- Docs
- Run in Production
- Running the Server
One server API. One or many brokers.
Run bunqueue as a standalone service so multiple apps can share a queue. Keep one memory/SQLite broker, or point several brokers at PostgreSQL 15–18; producers and workers use the same TCP API either way.
Embedded mode ties the queue to one Bun process. Server mode runs bunqueue standalone: your API adds jobs from one service, workers process them from another, and all six external SDKs join over the wire. Every broker listens on two ports: 6789 (TCP, the fast binary protocol clients use) and 6790 (HTTP, REST API and metrics).
Start the server
Section titled “Start the server”# Defaults: TCP 6789, HTTP 6790, in-memory storagebunqueue
# With persistence and custom portsbunqueue start \ --tcp-port 6789 \ --http-port 6790 \ --data-path ./data/queue.db
# PostgreSQL: use a unique BUNQUEUE_BROKER_ID in every active processBUNQUEUE_POSTGRES_URL='postgres://bunqueue:secret@postgres:5432/bunqueue' \BUNQUEUE_POSTGRES_NAMESPACE=production \BUNQUEUE_BROKER_ID=broker-a \bunqueue startAlways select durable storage in production: set a SQLite --data-path, or configure BUNQUEUE_POSTGRES_URL for the server-only multi-broker backend. Without either, jobs live in memory and are lost on restart. PostgreSQL 15–18 is tested in CI and 18.6 is recommended; see Storage backends.
For bounded completed-job history on SQLite, add
--completed-retention-ms <age> (or the equivalent config/environment
setting). --max-completed-jobs only sizes the in-memory hot window and does
not reclaim disk space.
The server validates every setting before it opens a port. A flag, config-file
key or environment variable with a value the server cannot use, such as
--tcp-port 1e4, timeouts.stats: 0 or STATS_INTERVAL_MS=1e12 (which earlier
releases misread as port 1 and 1 ms), stops startup with an error that names it.
Values that earlier releases accepted keep working: forms such as 6789.5 or
5000ms are read as they were, and a value they replaced with a default (for
example --tcp-port abc, which starts on 6789) is logged as a warning and keeps
that default. See Configuration File and
Environment Variables.
Connect from your app
Section titled “Connect from your app”Drop the embedded option and clients connect to localhost:6789 automatically:
import { Queue, Worker } from 'bunqueue/client';
const queue = new Queue('tasks');const worker = new Worker('tasks', async (job) => { console.log('Processing:', job.data); return { success: true };});
await queue.add('my-job', { foo: 'bar' });For a remote server, pass a connection:
const queue = new Queue('tasks', { connection: { host: '192.168.1.100', port: 6789, token: 'my-secret-token', // Required if the server sets AUTH_TOKENS },});Not on Bun? Use the client SDKs for TypeScript on Node.js/Deno/Cloudflare Workers, Python, PHP, Go, Rust, or Elixir.
Add authentication
Section titled “Add authentication”Without auth, anyone who can reach the port can control your queues. Set one or more tokens on the server:
AUTH_TOKENS=secret1,secret2 bunqueue start --data-path ./data/queue.dbEvery client then needs a matching token in its connection options. More hardening tips in Security.
Configure it
Section titled “Configure it”The recommended way is a typed bunqueue.config.ts file in your project root, auto-discovered by bunqueue start:
import { defineConfig } from 'bunqueue';
export default defineConfig({ server: { tcpPort: 6789, httpPort: 6790 }, auth: { tokens: ['my-secret-token'] }, storage: { dataPath: './data/queue.db' },});See Configuration File for every option. Environment variables work too, as a fallback:
| Variable | Default | Description |
|---|---|---|
TCP_PORT | 6789 | TCP server port |
HTTP_PORT | 6790 | HTTP server port |
HOST | 0.0.0.0 | Bind address |
BUNQUEUE_STORAGE_DRIVER | inferred | memory, sqlite, or postgres |
BUNQUEUE_DATA_PATH | (memory) | SQLite database path |
BUNQUEUE_POSTGRES_URL | (none) | PostgreSQL URL; selects postgres when no driver is set |
BUNQUEUE_POSTGRES_NAMESPACE | default | Isolates a bunqueue installation in one database |
BUNQUEUE_BROKER_ID | generated | Unique stable identity for each active PostgreSQL broker |
AUTH_TOKENS | (none) | Comma-separated auth tokens |
LOG_FORMAT | text | Log format (text / json) |
Priority when the same option is set in more than one place: CLI flags > config file > environment variables > defaults. Full list in Environment Variables.
Run it in Docker
Section titled “Run it in Docker”Every completed release publishes the multi-arch image with the exact version
tag alongside latest and the distribution variants. Pin the version
tag when the server and client must move together:
docker run -d -p 6789:6789 -p 6790:6790 \ -v bunqueue-data:/app/data \ ghcr.io/egeominotti/bunqueue:2.9.22PostgreSQL storage needs a 2.9 image or newer: a 2.8.x image ignores the
variables above and starts in memory or SQLite mode without an error. Confirm
the tag you pin exists — a release whose pipeline did not complete pushes no
image, and 2.9.0 is one such gap. docker buildx imagetools inspect ghcr.io/egeominotti/bunqueue:<tag> prints the digest a rollout should pin.
To build an application-specific image instead:
FROM oven/bun:1.4.4-alpineWORKDIR /appCOPY package.json bun.lock* ./RUN bun install --productionCOPY . .EXPOSE 6789 6790CMD ["bun", "run", "src/main.ts"]docker build -t bunqueue .docker run -p 6789:6789 -p 6790:6790 \ -v ./data:/app/data \ -e BUNQUEUE_DATA_PATH=/app/data/queue.db \ bunqueueMore deployment recipes (systemd, Kubernetes, Fly.io) in the deployment guide.
Graceful shutdown
Section titled “Graceful shutdown”On SIGINT or SIGTERM the server:
- Stops accepting new connections and stops handing out jobs (a pull delivers nothing; one with a timeout waits it out before answering), while open connections keep working so workers can still acknowledge, fail and heartbeat the jobs they hold
- Waits for active jobs to finish, and moves on as soon as none is active (30s timeout, configurable via
SHUTDOWN_TIMEOUT_MS; a job still active at the timeout is recovered by the next start as interrupted work) - Closes the TCP and HTTP servers
- Flushes SQLite writes or drains admitted PostgreSQL operations and maintenance
- Exits cleanly
With TLS enabled, SIGHUP does not stop the server: it reloads the certificate and key from disk without dropping connections (see Renewing certificates). Without TLS the server does not handle SIGHUP, as in earlier releases: the default action ends the process without this sequence (under nohup the signal is ignored).
Connect AI agents (MCP)
Section titled “Connect AI agents (MCP)”AI agents can drive a running server through the bundled MCP server, which talks to bunqueue over TCP:
bunqueue start --data-path ./data/queue.db
# In another terminalbun add bunqueue @modelcontextprotocol/sdkclaude mcp add bunqueue -- bunx --package=bunqueue bunqueue-mcpPoint the MCP server at your instance with BUNQUEUE_MODE=tcp, BUNQUEUE_HOST, BUNQUEUE_PORT, and BUNQUEUE_TOKEN (when auth is on). Agents get 79 tools to add jobs, manage queues, schedule crons, and monitor everything. Full setup, including Claude Desktop, Cursor, and Windsurf config, in the MCP guide.