egeominotti/bunqueue

⚡ High-performance job queue for Bun. SQLite by default, PostgreSQL multi-broker when you scale. DLQ, cron, SQLite S3 backups, and native MCP. No Redis.

TypeScript

587

873 commits

updated Oct 15, 2026

See the code

See what people are saying

README

bunqueue

npm version npm downloads CI GitHub Stars License

High-performance job queue for Bun. Memory or one-file SQLite; PostgreSQL 15–18 multi-broker when you scale.
DLQ, cron, SQLite S3 backups, and native MCP. Built for AI agents and automation. No Redis.

Documentation · Quick Start · Video course · Benchmarks · npm


Quickstart

Your first job in about a minute. On Bun the queue runs inside your app, with no server to start:

bun add bunqueue
// jobs.ts
import { Queue, Worker } from 'bunqueue/client';

const queue = new Queue<{ to: string }>('emails', { embedded: true });

const worker = new Worker<{ to: string }>(
  'emails',
  async (job) => {
    console.log('Processing:', job.data.to);
    return { sent: true };
  },
  { embedded: true }
);

worker.on('error', (error) => console.error(error));
await queue.add('welcome', { to: '[email protected]' });
bun jobs.ts
# Processing: [email protected]

That's it: no Redis, no config, no setup. The worker keeps waiting for more jobs until you stop it with Ctrl+C. The example keeps jobs in memory; pass dataPath: './jobs.db' to the queue and the worker to persist them in one SQLite file. msgpackr is the only runtime dependency; cron, SQLite, S3, HTTP and WebSocket use Bun's built-ins.

Not on Bun? Run the server, connect from anywhere

Start the server with Docker (no Bun needed) or, if Bun is installed, with bunx bunqueue start --host 127.0.0.1 --data-path ./bunqueue.db:

docker run -d --name bunqueue \
  --restart unless-stopped \
  -p 127.0.0.1:6789:6789 \
  -p 127.0.0.1:6790:6790 \
  -v bunqueue-data:/app/data \
  egeominotti/bunqueue:alpine

curl --fail http://127.0.0.1:6790/health

Then, in another terminal, add a job and process it from Node.js:

npm install bunqueue-client
// jobs.mjs
import { Queue, Worker } from 'bunqueue-client';

const options = {
  embedded: false,
  connection: { host: '127.0.0.1', port: 6789 },
};
const queue = new Queue('emails', options);

const worker = new Worker(
  'emails',
  async (job) => {
    console.log('Processing:', job.data.to);
    return { sent: true };
  },
  options
);

worker.on('error', (error) => console.error(error));
await queue.add('welcome', { to: '[email protected]' });
node jobs.mjs
# Processing: [email protected]

Deno, Python, PHP, Go, Rust and Elixir take the same three steps: see the Quick Start for each language.

For multiple active brokers, the repository includes a topology pinned to PostgreSQL 18.6:

POSTGRES_PASSWORD='replace-me' \
  BUNQUEUE_POSTGRES_URL='postgres://bunqueue:replace-me@postgres:5432/bunqueue' \
  docker compose -f docker-compose.postgres.yml up --build -d

It starts two brokers against one database/namespace. PostgreSQL is server-only; embedded mode keeps using memory/SQLite. Supply both Compose values when the password changes, percent-encoding reserved characters in the URL only. MySQL is not supported. CI validates PostgreSQL 15, 16, 17, and the pinned/recommended 18.6 release. See the storage guide.

Docker images

All four images run the same bunqueue server with the same queue features, protocols, and persistence options. The difference is the Linux base and the tools available inside the container.

Starting with 2.9.5, completed releases publish to both Docker Hub (egeominotti/bunqueue) and GHCR (ghcr.io/egeominotti/bunqueue), with matching version, latest, and variant tags. Build references are kept on GHCR only. Each image supports linux/amd64 and linux/arm64; Docker selects the matching architecture automatically. You choose the distribution, not the CPU tag. Pin a version or digest for reproducible deployments. Confirm a tag exists with docker buildx imagetools inspect egeominotti/bunqueue:<tag> before using it.

Choose a Linux distribution with the same tags on either registry:

VariantVersion tagMoving tagRuntime base
Alpine (default)2.9.16-alpinealpine, latestAlpine 3.22, musl
Debian2.9.16-debiandebianDebian 13
Debian slim2.9.16-slimslimDebian 13 slim
Distroless2.9.16-distrolessdistrolessDebian 13, no shell or package manager

Which variant should I use?

  • Alpine is the default for a compact deployment. It uses musl and includes a shell and the apk package manager. Choose it when you do not need Debian tooling or glibc compatibility for additional software.
  • Debian uses glibc and the standard Debian base, with a shell and apt. Choose it when familiar Debian tools and a fuller base matter more than image size. Additional troubleshooting tools may still need to be installed.
  • Debian slim keeps glibc, a shell, and apt in a reduced Debian base. Choose it when you want Debian compatibility with fewer bundled utilities.
  • Distroless contains the compiled server and its required runtime libraries, without a shell or package manager. Choose it when you operate through logs, health checks, and external debugging tools; docker exec ... sh is unavailable.

The distribution does not select a faster queue engine or unlock extra features. All images run as a non-root user; having a package manager does not grant that user permission to install packages at runtime.

Try a specific variant by changing only the image tag:

docker run -d --name bunqueue --restart unless-stopped \
  -p 127.0.0.1:6789:6789 -p 127.0.0.1:6790:6790 \
  -v bunqueue-data:/app/data \
  egeominotti/bunqueue:2.9.16-alpine

curl --fail http://127.0.0.1:6790/health

This example exposes the APIs only on your machine. Replace 2.9.16-alpine with 2.9.16-debian, 2.9.16-slim, or 2.9.16-distroless to choose another base. Moving tags such as alpine follow newer releases; version tags identify a release, while a digest pins the exact image even across base-image rebuilds.

Every variant supports both architectures, runs as UID/GID 1001:1001, and stores SQLite data in /app/data. Unsuffixed tags such as 2.9.16 stay on Alpine. Production images contain the compiled server and required system libraries; development dependencies and a separate Bun installation stay out of the image. The built-in health check uses /app/bunqueue healthcheck, including on distroless. See the deployment guide for custom probes.

Standalone executables and network clients

Prefer a standalone executable? GitHub releases include the Bun runtime, so no Bun or Node.js installation is needed. From 2.9.5, the eight downloads are:

SystemArchitectureArchive
Linux (glibc)x64bunqueue-linux-x64.tar.gz
Linux (glibc)arm64bunqueue-linux-arm64.tar.gz
Linux (musl / Alpine)x64bunqueue-linux-x64-musl.tar.gz
Linux (musl / Alpine)arm64bunqueue-linux-arm64-musl.tar.gz
macOSx64 / Intelbunqueue-darwin-x64.tar.gz
macOSarm64 / Apple Siliconbunqueue-darwin-arm64.tar.gz
Windowsx64bunqueue-windows-x64.zip
Windowsarm64bunqueue-windows-arm64.zip

Extract the archive for your operating system and architecture, and verify it against the release's SHA256SUMS. See the installation guide.

Then produce and process from the language you already use:

npm install bunqueue-client    # Node.js ≥ 20, Deno ≥ 2, Bun, Cloudflare Workers
import { Queue, Worker } from 'bunqueue-client';

const queue = new Queue('emails'); // localhost:6789 by default
await queue.add('welcome', { to: '[email protected]' });

new Worker('emails', async (job) => ({ sent: true }), { concurrency: 10 });

Python, PHP, Go, Rust and Elixir clients speak the same protocol — see One Queue, Any Language.

Only the server and embedded mode are Bun-only (bun >= 1.4.0, bun.sh); producers and workers can run anywhere.

Quick Start guide →

Learn with bunqueue Academy

A free video course on YouTube, from your first queue to production. Each episode follows a guide on bunqueue.dev, with chapters and English subtitles.

Watch the 60-second bunqueue overview on YouTube

Coming next: 07 Workflow Engine · 08 SDKs, CLI & MCP · 09 Production · 10 Framework Integrations.

Full playlist → · All episodes on bunqueue.dev →

Why bunqueue?

LibraryRequiresAI-native
BullMQRedisNo
AgendaMongoDBNo
pg-bossPostgreSQLNo
bunqueueNothing (memory/SQLite) · PostgreSQL optionalYes
  • Zero external infrastructure by default — memory by default; one SQLite file when local persistence is configured
  • PostgreSQL 15–18 multi-broker mode — PostgreSQL 18.6 is recommended; database-authoritative claims, fenced leases, shared limits, cron, workers, job-state/lifecycle metrics, and failover state (tested three-broker Docker example)
  • BullMQ and BullMQ Pro-compatible API — Queue, Worker, QueueEvents, FlowProducer, plus QueuePro/WorkerPro aliases, fair job groups, native processor batches, cooperative cancellation, and Observable results; migrating takes minutes
  • MCP server included — 79 primary tools; AI agents get full queue control out of the box
  • Everything server-side — retries with backoff, priorities, cron, rate limits, dead letter queue
  • Measured, operation-specific performance — 729K jobs/sec internal in-memory batch push, 186K jobs/sec public on-disk Embedded addBulk, and 159K jobs/sec TCP PUSHB; methodology and distributions

Great for: embedded and single-server deployments, PostgreSQL-backed broker fleets, AI agents that need a scheduler, edge/serverless spooling, and teams that don't want to operate Redis.

Not ideal for: multi-region consensus or deployments that require MySQL as the queue store. If you already run Redis and BullMQ works for you, keep it.

When to choose bunqueue →

Two Modes

EmbeddedServer (TCP)
How it worksQueue runs inside your processStandalone server, clients connect via TCP
Setupbun add bunqueuedocker run or bunqueue start
Performance186K jobs/sec on-disk addBulk; 729K internal in-memory batchSQLite: 159K TCP PUSHB; 17K jobs/sec worker drain
Best forSingle-process apps, CLIs, serverlessMultiple workers, separate producer/consumer
ScalingSame process onlyMultiple clients; multiple brokers with PostgreSQL 15–18

Embedded

Everything in your process. Without a data path the queue is in-memory: pass dataPath (or set BUNQUEUE_DATA_PATH) to persist jobs.

import { Queue, Worker } from 'bunqueue/client';

const queue = new Queue('emails', { embedded: true, dataPath: './data/app.db' });

const worker = new Worker(
  'emails',
  async (job) => {
    return { sent: true };
  },
  { embedded: true }
);

await queue.add('welcome', { to: '[email protected]' });

Server (TCP)

docker run -d -p 6789:6789 -p 6790:6790 \
  -v bunqueue-data:/app/data \
  egeominotti/bunqueue:latest
import { Queue, Worker } from 'bunqueue/client';

const queue = new Queue('tasks', { connection: { host: 'localhost', port: 6789 } });

const worker = new Worker(
  'tasks',
  async (job) => {
    return { done: true };
  },
  { connection: { host: 'localhost', port: 6789 } }
);

await queue.add('process', { data: 'hello' });

Running the server → · Deployment guide →

BullMQ Pro-compatible groups and batches

The Bun client exposes Pro-style aliases backed by the same native queue and worker implementations. No separate runtime or license is required:

import { QueuePro, WorkerPro } from 'bunqueue/client';

const queue = new QueuePro('webhooks', { embedded: true, dataPath: './data/app.db' });

await queue.add('deliver', payload, {
  group: { id: 'tenant-a', priority: 1, maxSize: 10_000 },
});

const worker = new WorkerPro(
  'webhooks',
  async (job, { signal } = { signal: new AbortController().signal }) => {
    const batch = job.getBatch?.() ?? [job];
    return await deliver(batch, signal);
  },
  {
    embedded: true,
    batch: { size: 100, minSize: 10, timeout: 250, groupAffinity: true },
    group: { concurrency: 2, limit: { max: 20, duration: 1000 } },
  }
);

Groups support atomic maxSize admission, priority within a group, pause and resume, manual rate limits, per-group concurrency/rate defaults, pending-job queries, and fair round-robin claims. Native batches support minimum size, timeout, group affinity, and selective job.setAsFailed(error). Processor timeouts and explicit cancellation abort the second-argument AbortSignal; Promise handlers remain cooperative, while structural Observable handlers are unsubscribed. These contracts are identical with SQLite and PostgreSQL 15–18. BullMQ Pro telemetry and NestJS integration are intentionally not included.

Job Groups guide → · Worker options →

One Queue, Any Language (SDKs)

The server does all the heavy lifting. Official client SDKs share the protocol-conformant core Queue, Worker, and Flow surface, so producers and workers can live anywhere in your stack — add a job from TypeScript, process it from Python. Language-specific capabilities are tracked in the SDK guide.

Where your code runsInstall
Node.js ≥ 20, Deno ≥ 2, Bun, Cloudflare Workersnpm install bunqueue-client
Python ≥ 3.9pip install bunqueue-client
PHP ≥ 8.1composer require bunqueue/client
Go ≥ 1.26.5go get github.com/egeominotti/bunqueue/sdk/go
Rust ≥ 1.85cargo add bunqueue-client
Elixir ≥ 1.15Hex coming soon — today: use sdk/elixir as a path dependency
// Node.js / Deno / Cloudflare Workers
import { Queue, Worker } from 'bunqueue-client';

const queue = new Queue('emails', { connection: { host: 'localhost', port: 6789 } });
await queue.add('welcome', { to: '[email protected]' });

new Worker('emails', async (job) => ({ sent: true }), { concurrency: 10 });
# Python
from bunqueue import Queue, Worker

queue = Queue("emails", host="localhost", port=6789)
queue.add("welcome", {"to": "[email protected]"})

Worker("emails", lambda job: {"sent": True}, concurrency=10).run()

Every SDK is certified against the same public wire protocol and conformance suite.

Atomic flows, in every SDK

Every official FlowProducer resolves all job IDs and reciprocal dependency edges locally, then sends one PUSHF command. The broker validates the complete graph and commits it atomically, so a worker cannot observe a leaf from a partially-created flow.

import { FlowProducer } from 'bunqueue-client';

const flows = new FlowProducer({ connection: { host: 'localhost', port: 6789 } });
const root = await flows.add({
  name: 'publish-release',
  queueName: 'release',
  data: { version: 'candidate-42' },
  children: [
    { name: 'unit-tests', queueName: 'checks', data: { suite: 'unit' } },
    { name: 'sdk-tests', queueName: 'checks', data: { suite: 'sdk' } },
  ],
});

console.log(
  root.job.id,
  root.children?.map(({ job }) => job.id)
);
await flows.close();

The repository records the contracts and the test strategy beside each implementation:

SDKRuntime invariantsGenerated testsMutation engine
TypeScriptcontractfast-checknone¹
PythoncontractHypothesismutmut
PHPcontractErisInfection
GocontractRapidGremlins
Rustcontractproptestcargo-mutants
ElixircontractStreamDataMuex

¹ The TypeScript SDK has no mutation engine. StrykerJS was removed because its dependency graph produced every advisory the weekly audit had to answer for, none of it reachable from the published client; the planners keep their fast-check coverage.

Property campaigns run in the ordinary SDK gate with deterministic replay seeds. Mutation campaigns run separately against the pure planners and snapshot validators. Contributors can reproduce the complete isolated SDK gate with bun run test:sandbox:sdk; language-specific commands live in each SDK README and AGENTS.md.

SDK guide (all six languages) →

Simple Mode

Bunqueue bundles Queue + Worker + routes + middleware + cron in one object:

import { Bunqueue } from 'bunqueue/client';

const app = new Bunqueue('notifications', {
  embedded: true,
  routes: {
    'send-email': async (job) => ({ sent: true }),
    'send-sms': async (job) => ({ sent: true }),
  },
  concurrency: 10,
  retry: { maxAttempts: 5, strategy: 'jitter' },
  circuitBreaker: { threshold: 5, resetTimeout: 30000 },
});

// Onion middleware around every job
app.use(async (job, next) => {
  const start = Date.now();
  const result = await next();
  console.log(`${job.name}: ${Date.now() - start}ms`);
  return result;
});

await app.cron('daily-report', '0 9 * * *', { type: 'summary' });
await app.add('send-email', { to: '[email protected]' });

app.on('completed', (job, result) => console.log(result));
await app.close();

Also included: batch processing, event triggers (job A completes → create job B), job TTL, priority aging, deduplication, per-group rate limiting, DLQ with auto-retry, graceful cancellation via AbortController.

Simple Mode reference →

Workflow Engine

Multi-step orchestration with saga compensation, branching, parallel steps and human-in-the-loop signals — built on bunqueue, no new infrastructure:

import { Workflow, Engine } from 'bunqueue/workflow';

const orderFlow = new Workflow('order-pipeline')
  .step(
    'reserve-stock',
    async () => {
      await inventory.reserve();
      return { reserved: true };
    },
    {
      compensate: async () => await inventory.release(), // auto-rollback on failure
    }
  )
  .step(
    'charge',
    async () => {
      return { txId: await payments.charge() };
    },
    {
      compensate: async () => await payments.refund(),
    }
  )
  .waitFor('manager-approval', { timeout: 86_400_000 }) // human-in-the-loop
  .step('confirm', async (ctx) => {
    return { txId: (ctx.steps['charge'] as { txId: string }).txId };
  });

const engine = new Engine({ embedded: true });
engine.register(orderFlow);
const run = await engine.start('order-pipeline', { orderId: 'ORD-1' });
await engine.signal(run.id, 'manager-approval', { approved: true });
bunqueueTemporalInngestTrigger.dev
InfrastructureNone (embedded)PostgreSQL + 7 servicesCloud-onlyRedis + PostgreSQL
Saga compensationBuilt-inManualManualManual
Human-in-the-loop.waitFor()Signals APIstep.waitForEvent()Waitpoint tokens
Self-hostedZero-configComplexNoComplex
PricingFree (MIT)Free / Cloud $$Per-executionFree tier, then $50/mo+

Also included: nested workflows, doUntil/doWhile loops, forEach over dynamic lists, schema validation (Zod, ArkType, Valibot or any .parse()), step timeouts, typed events, SQLite-persisted execution state.

Workflow Engine guide →

Built for AI Agents (MCP Server)

bunqueue ships a native MCP server: 79 primary tools, 5 resources, 3 prompts. Agents schedule cron jobs, push and process jobs, retry failures, set rate limits, and read stats — no glue code. HTTP handlers let an agent register a URL and have a worker call it for every job. Run it over stdio or Streamable HTTP, and use the agent preset for dynamic toolsets and destructive-call confirmation. The 79 primary tools include bounded paging, job observation and queue diagnosis; 2 scoped resource templates limit reads to a queue. Optional workflows add up to 3 tools. Dynamic discovery tools are counted separately.

bun add bunqueue @modelcontextprotocol/sdk   # the MCP SDK is an optional peer
claude mcp add bunqueue -e BUNQUEUE_MCP_PROFILE=agent -- bunx --package=bunqueue bunqueue-mcp
// Claude Desktop / Cursor / Windsurf
{
  "mcpServers": {
    "bunqueue": {
      "command": "bunx",
      "args": ["--package=bunqueue", "bunqueue-mcp"],
      "env": { "BUNQUEUE_MCP_PROFILE": "agent" }
    }
  }
}

Then just ask: "Schedule a cleanup job every day at 3 AM" · "Show me all failed jobs and retry them" · "Set rate limit to 50/sec on api-calls".

MCP guide →

Check configuration before connecting: BUNQUEUE_MCP_PROFILE=agent bunx --package=bunqueue bunqueue-mcp --doctor --json. Doctor reports readiness without opening embedded databases; startup still checks workflow schemas. Read structured outcomes and observe final job state before claiming success. Queue-scoped pages omit payloads by default.

For durable agent turns, use Workflow + Engine and the optional bunqueue/agents journal for sessions, redacted progress replay and uncertain external effects. Session resume and queue retries still require provider idempotency and explicit business-effect reconciliation. The repository skill routes directly to queue, workflow, MCP and agent references.

Dashboard

A web dashboard that fully drives your server — queues, jobs, DLQ, cron, webhooks, workers, live activity, SQLite inspector and an AI copilot. Open source, currently in beta:

bunx bunqueue-dashboard

https://github.com/user-attachments/assets/e8a8d38e-b4a6-4dc8-8360-876c0f24d116

8-minute tour on YouTube · Live demo · Dashboard guide · User guide · GitHub

Performance

Native Ryzen 9 9950X3D, Bun 1.3.14; medians from repeated fresh processes:

WorkloadModeMedianPersistence
Internal batched push, 1M jobsEmbedded729,395 jobs/secIn-memory, no dataPath
Public sustained addBulk, 50K cellEmbedded186,384 jobs/secOn-disk buffered SQLite
PUSHB, fresh 50K sampleTCP158,779 jobs/secOn-disk buffered SQLite
No-work worker drain, concurrency 50TCP17,256 jobs/secFull pull/process/ACK
Linear Workflow EngineEmbedded / TCP2,700 / 3,187 workflows/secWorkflow SQLite + 3 queue nodes

These operations do different work; the internal in-memory result is not an SQLite or public-API claim. Run bun run bench, bun run bench:tcp, or bun run bench:workflow on your hardware. Benchmark methodology → · full engineering report

Documentation

bunqueue.dev →

Project & Governance

License

MIT — provided as is, without warranty of any kind. The authors accept no liability for lost data, lost money or any other damage arising from official or unofficial use of bunqueue. See DISCLAIMER.md.

ai
ai-agents
ai-scheduler
background-jobs
bullmq-alternative
bun
bun-queue
cron-scheduler
deno
job-queue
message-queue
nodejs
postgres
postgresql
queue
redis-alternative
s3
sqlite3
task-queue-typescript

egeominotti/bunqueue

⚡ High-performance job queue for Bun. SQLite by default, PostgreSQL multi-broker when you scale. DLQ, cron, SQLite S3 backups, and native MCP. No Redis.

TypeScript

587

873 commits

updated Oct 15, 2026

See the code

See what people are saying

README

bunqueue

npm version npm downloads CI GitHub Stars License

High-performance job queue for Bun. Memory or one-file SQLite; PostgreSQL 15–18 multi-broker when you scale.
DLQ, cron, SQLite S3 backups, and native MCP. Built for AI agents and automation. No Redis.

Documentation · Quick Start · Video course · Benchmarks · npm


Quickstart

Your first job in about a minute. On Bun the queue runs inside your app, with no server to start:

bun add bunqueue
// jobs.ts
import { Queue, Worker } from 'bunqueue/client';

const queue = new Queue<{ to: string }>('emails', { embedded: true });

const worker = new Worker<{ to: string }>(
  'emails',
  async (job) => {
    console.log('Processing:', job.data.to);
    return { sent: true };
  },
  { embedded: true }
);

worker.on('error', (error) => console.error(error));
await queue.add('welcome', { to: '[email protected]' });
bun jobs.ts
# Processing: [email protected]

That's it: no Redis, no config, no setup. The worker keeps waiting for more jobs until you stop it with Ctrl+C. The example keeps jobs in memory; pass dataPath: './jobs.db' to the queue and the worker to persist them in one SQLite file. msgpackr is the only runtime dependency; cron, SQLite, S3, HTTP and WebSocket use Bun's built-ins.

Not on Bun? Run the server, connect from anywhere

Start the server with Docker (no Bun needed) or, if Bun is installed, with bunx bunqueue start --host 127.0.0.1 --data-path ./bunqueue.db:

docker run -d --name bunqueue \
  --restart unless-stopped \
  -p 127.0.0.1:6789:6789 \
  -p 127.0.0.1:6790:6790 \
  -v bunqueue-data:/app/data \
  egeominotti/bunqueue:alpine

curl --fail http://127.0.0.1:6790/health

Then, in another terminal, add a job and process it from Node.js:

npm install bunqueue-client
// jobs.mjs
import { Queue, Worker } from 'bunqueue-client';

const options = {
  embedded: false,
  connection: { host: '127.0.0.1', port: 6789 },
};
const queue = new Queue('emails', options);

const worker = new Worker(
  'emails',
  async (job) => {
    console.log('Processing:', job.data.to);
    return { sent: true };
  },
  options
);

worker.on('error', (error) => console.error(error));
await queue.add('welcome', { to: '[email protected]' });
node jobs.mjs
# Processing: [email protected]

Deno, Python, PHP, Go, Rust and Elixir take the same three steps: see the Quick Start for each language.

For multiple active brokers, the repository includes a topology pinned to PostgreSQL 18.6:

POSTGRES_PASSWORD='replace-me' \
  BUNQUEUE_POSTGRES_URL='postgres://bunqueue:replace-me@postgres:5432/bunqueue' \
  docker compose -f docker-compose.postgres.yml up --build -d

It starts two brokers against one database/namespace. PostgreSQL is server-only; embedded mode keeps using memory/SQLite. Supply both Compose values when the password changes, percent-encoding reserved characters in the URL only. MySQL is not supported. CI validates PostgreSQL 15, 16, 17, and the pinned/recommended 18.6 release. See the storage guide.

Docker images

All four images run the same bunqueue server with the same queue features, protocols, and persistence options. The difference is the Linux base and the tools available inside the container.

Starting with 2.9.5, completed releases publish to both Docker Hub (egeominotti/bunqueue) and GHCR (ghcr.io/egeominotti/bunqueue), with matching version, latest, and variant tags. Build references are kept on GHCR only. Each image supports linux/amd64 and linux/arm64; Docker selects the matching architecture automatically. You choose the distribution, not the CPU tag. Pin a version or digest for reproducible deployments. Confirm a tag exists with docker buildx imagetools inspect egeominotti/bunqueue:<tag> before using it.

Choose a Linux distribution with the same tags on either registry:

VariantVersion tagMoving tagRuntime base
Alpine (default)2.9.16-alpinealpine, latestAlpine 3.22, musl
Debian2.9.16-debiandebianDebian 13
Debian slim2.9.16-slimslimDebian 13 slim
Distroless2.9.16-distrolessdistrolessDebian 13, no shell or package manager

Which variant should I use?

  • Alpine is the default for a compact deployment. It uses musl and includes a shell and the apk package manager. Choose it when you do not need Debian tooling or glibc compatibility for additional software.
  • Debian uses glibc and the standard Debian base, with a shell and apt. Choose it when familiar Debian tools and a fuller base matter more than image size. Additional troubleshooting tools may still need to be installed.
  • Debian slim keeps glibc, a shell, and apt in a reduced Debian base. Choose it when you want Debian compatibility with fewer bundled utilities.
  • Distroless contains the compiled server and its required runtime libraries, without a shell or package manager. Choose it when you operate through logs, health checks, and external debugging tools; docker exec ... sh is unavailable.

The distribution does not select a faster queue engine or unlock extra features. All images run as a non-root user; having a package manager does not grant that user permission to install packages at runtime.

Try a specific variant by changing only the image tag:

docker run -d --name bunqueue --restart unless-stopped \
  -p 127.0.0.1:6789:6789 -p 127.0.0.1:6790:6790 \
  -v bunqueue-data:/app/data \
  egeominotti/bunqueue:2.9.16-alpine

curl --fail http://127.0.0.1:6790/health

This example exposes the APIs only on your machine. Replace 2.9.16-alpine with 2.9.16-debian, 2.9.16-slim, or 2.9.16-distroless to choose another base. Moving tags such as alpine follow newer releases; version tags identify a release, while a digest pins the exact image even across base-image rebuilds.

Every variant supports both architectures, runs as UID/GID 1001:1001, and stores SQLite data in /app/data. Unsuffixed tags such as 2.9.16 stay on Alpine. Production images contain the compiled server and required system libraries; development dependencies and a separate Bun installation stay out of the image. The built-in health check uses /app/bunqueue healthcheck, including on distroless. See the deployment guide for custom probes.

Standalone executables and network clients

Prefer a standalone executable? GitHub releases include the Bun runtime, so no Bun or Node.js installation is needed. From 2.9.5, the eight downloads are:

SystemArchitectureArchive
Linux (glibc)x64bunqueue-linux-x64.tar.gz
Linux (glibc)arm64bunqueue-linux-arm64.tar.gz
Linux (musl / Alpine)x64bunqueue-linux-x64-musl.tar.gz
Linux (musl / Alpine)arm64bunqueue-linux-arm64-musl.tar.gz
macOSx64 / Intelbunqueue-darwin-x64.tar.gz
macOSarm64 / Apple Siliconbunqueue-darwin-arm64.tar.gz
Windowsx64bunqueue-windows-x64.zip
Windowsarm64bunqueue-windows-arm64.zip

Extract the archive for your operating system and architecture, and verify it against the release's SHA256SUMS. See the installation guide.

Then produce and process from the language you already use:

npm install bunqueue-client    # Node.js ≥ 20, Deno ≥ 2, Bun, Cloudflare Workers
import { Queue, Worker } from 'bunqueue-client';

const queue = new Queue('emails'); // localhost:6789 by default
await queue.add('welcome', { to: '[email protected]' });

new Worker('emails', async (job) => ({ sent: true }), { concurrency: 10 });

Python, PHP, Go, Rust and Elixir clients speak the same protocol — see One Queue, Any Language.

Only the server and embedded mode are Bun-only (bun >= 1.4.0, bun.sh); producers and workers can run anywhere.

Quick Start guide →

Learn with bunqueue Academy

A free video course on YouTube, from your first queue to production. Each episode follows a guide on bunqueue.dev, with chapters and English subtitles.

Watch the 60-second bunqueue overview on YouTube

Coming next: 07 Workflow Engine · 08 SDKs, CLI & MCP · 09 Production · 10 Framework Integrations.

Full playlist → · All episodes on bunqueue.dev →

Why bunqueue?

LibraryRequiresAI-native
BullMQRedisNo
AgendaMongoDBNo
pg-bossPostgreSQLNo
bunqueueNothing (memory/SQLite) · PostgreSQL optionalYes
  • Zero external infrastructure by default — memory by default; one SQLite file when local persistence is configured
  • PostgreSQL 15–18 multi-broker mode — PostgreSQL 18.6 is recommended; database-authoritative claims, fenced leases, shared limits, cron, workers, job-state/lifecycle metrics, and failover state (tested three-broker Docker example)
  • BullMQ and BullMQ Pro-compatible API — Queue, Worker, QueueEvents, FlowProducer, plus QueuePro/WorkerPro aliases, fair job groups, native processor batches, cooperative cancellation, and Observable results; migrating takes minutes
  • MCP server included — 79 primary tools; AI agents get full queue control out of the box
  • Everything server-side — retries with backoff, priorities, cron, rate limits, dead letter queue
  • Measured, operation-specific performance — 729K jobs/sec internal in-memory batch push, 186K jobs/sec public on-disk Embedded addBulk, and 159K jobs/sec TCP PUSHB; methodology and distributions

Great for: embedded and single-server deployments, PostgreSQL-backed broker fleets, AI agents that need a scheduler, edge/serverless spooling, and teams that don't want to operate Redis.

Not ideal for: multi-region consensus or deployments that require MySQL as the queue store. If you already run Redis and BullMQ works for you, keep it.

When to choose bunqueue →

Two Modes

EmbeddedServer (TCP)
How it worksQueue runs inside your processStandalone server, clients connect via TCP
Setupbun add bunqueuedocker run or bunqueue start
Performance186K jobs/sec on-disk addBulk; 729K internal in-memory batchSQLite: 159K TCP PUSHB; 17K jobs/sec worker drain
Best forSingle-process apps, CLIs, serverlessMultiple workers, separate producer/consumer
ScalingSame process onlyMultiple clients; multiple brokers with PostgreSQL 15–18

Embedded

Everything in your process. Without a data path the queue is in-memory: pass dataPath (or set BUNQUEUE_DATA_PATH) to persist jobs.

import { Queue, Worker } from 'bunqueue/client';

const queue = new Queue('emails', { embedded: true, dataPath: './data/app.db' });

const worker = new Worker(
  'emails',
  async (job) => {
    return { sent: true };
  },
  { embedded: true }
);

await queue.add('welcome', { to: '[email protected]' });

Server (TCP)

docker run -d -p 6789:6789 -p 6790:6790 \
  -v bunqueue-data:/app/data \
  egeominotti/bunqueue:latest
import { Queue, Worker } from 'bunqueue/client';

const queue = new Queue('tasks', { connection: { host: 'localhost', port: 6789 } });

const worker = new Worker(
  'tasks',
  async (job) => {
    return { done: true };
  },
  { connection: { host: 'localhost', port: 6789 } }
);

await queue.add('process', { data: 'hello' });

Running the server → · Deployment guide →

BullMQ Pro-compatible groups and batches

The Bun client exposes Pro-style aliases backed by the same native queue and worker implementations. No separate runtime or license is required:

import { QueuePro, WorkerPro } from 'bunqueue/client';

const queue = new QueuePro('webhooks', { embedded: true, dataPath: './data/app.db' });

await queue.add('deliver', payload, {
  group: { id: 'tenant-a', priority: 1, maxSize: 10_000 },
});

const worker = new WorkerPro(
  'webhooks',
  async (job, { signal } = { signal: new AbortController().signal }) => {
    const batch = job.getBatch?.() ?? [job];
    return await deliver(batch, signal);
  },
  {
    embedded: true,
    batch: { size: 100, minSize: 10, timeout: 250, groupAffinity: true },
    group: { concurrency: 2, limit: { max: 20, duration: 1000 } },
  }
);

Groups support atomic maxSize admission, priority within a group, pause and resume, manual rate limits, per-group concurrency/rate defaults, pending-job queries, and fair round-robin claims. Native batches support minimum size, timeout, group affinity, and selective job.setAsFailed(error). Processor timeouts and explicit cancellation abort the second-argument AbortSignal; Promise handlers remain cooperative, while structural Observable handlers are unsubscribed. These contracts are identical with SQLite and PostgreSQL 15–18. BullMQ Pro telemetry and NestJS integration are intentionally not included.

Job Groups guide → · Worker options →

One Queue, Any Language (SDKs)

The server does all the heavy lifting. Official client SDKs share the protocol-conformant core Queue, Worker, and Flow surface, so producers and workers can live anywhere in your stack — add a job from TypeScript, process it from Python. Language-specific capabilities are tracked in the SDK guide.

Where your code runsInstall
Node.js ≥ 20, Deno ≥ 2, Bun, Cloudflare Workersnpm install bunqueue-client
Python ≥ 3.9pip install bunqueue-client
PHP ≥ 8.1composer require bunqueue/client
Go ≥ 1.26.5go get github.com/egeominotti/bunqueue/sdk/go
Rust ≥ 1.85cargo add bunqueue-client
Elixir ≥ 1.15Hex coming soon — today: use sdk/elixir as a path dependency
// Node.js / Deno / Cloudflare Workers
import { Queue, Worker } from 'bunqueue-client';

const queue = new Queue('emails', { connection: { host: 'localhost', port: 6789 } });
await queue.add('welcome', { to: '[email protected]' });

new Worker('emails', async (job) => ({ sent: true }), { concurrency: 10 });
# Python
from bunqueue import Queue, Worker

queue = Queue("emails", host="localhost", port=6789)
queue.add("welcome", {"to": "[email protected]"})

Worker("emails", lambda job: {"sent": True}, concurrency=10).run()

Every SDK is certified against the same public wire protocol and conformance suite.

Atomic flows, in every SDK

Every official FlowProducer resolves all job IDs and reciprocal dependency edges locally, then sends one PUSHF command. The broker validates the complete graph and commits it atomically, so a worker cannot observe a leaf from a partially-created flow.

import { FlowProducer } from 'bunqueue-client';

const flows = new FlowProducer({ connection: { host: 'localhost', port: 6789 } });
const root = await flows.add({
  name: 'publish-release',
  queueName: 'release',
  data: { version: 'candidate-42' },
  children: [
    { name: 'unit-tests', queueName: 'checks', data: { suite: 'unit' } },
    { name: 'sdk-tests', queueName: 'checks', data: { suite: 'sdk' } },
  ],
});

console.log(
  root.job.id,
  root.children?.map(({ job }) => job.id)
);
await flows.close();

The repository records the contracts and the test strategy beside each implementation:

SDKRuntime invariantsGenerated testsMutation engine
TypeScriptcontractfast-checknone¹
PythoncontractHypothesismutmut
PHPcontractErisInfection
GocontractRapidGremlins
Rustcontractproptestcargo-mutants
ElixircontractStreamDataMuex

¹ The TypeScript SDK has no mutation engine. StrykerJS was removed because its dependency graph produced every advisory the weekly audit had to answer for, none of it reachable from the published client; the planners keep their fast-check coverage.

Property campaigns run in the ordinary SDK gate with deterministic replay seeds. Mutation campaigns run separately against the pure planners and snapshot validators. Contributors can reproduce the complete isolated SDK gate with bun run test:sandbox:sdk; language-specific commands live in each SDK README and AGENTS.md.

SDK guide (all six languages) →

Simple Mode

Bunqueue bundles Queue + Worker + routes + middleware + cron in one object:

import { Bunqueue } from 'bunqueue/client';

const app = new Bunqueue('notifications', {
  embedded: true,
  routes: {
    'send-email': async (job) => ({ sent: true }),
    'send-sms': async (job) => ({ sent: true }),
  },
  concurrency: 10,
  retry: { maxAttempts: 5, strategy: 'jitter' },
  circuitBreaker: { threshold: 5, resetTimeout: 30000 },
});

// Onion middleware around every job
app.use(async (job, next) => {
  const start = Date.now();
  const result = await next();
  console.log(`${job.name}: ${Date.now() - start}ms`);
  return result;
});

await app.cron('daily-report', '0 9 * * *', { type: 'summary' });
await app.add('send-email', { to: '[email protected]' });

app.on('completed', (job, result) => console.log(result));
await app.close();

Also included: batch processing, event triggers (job A completes → create job B), job TTL, priority aging, deduplication, per-group rate limiting, DLQ with auto-retry, graceful cancellation via AbortController.

Simple Mode reference →

Workflow Engine

Multi-step orchestration with saga compensation, branching, parallel steps and human-in-the-loop signals — built on bunqueue, no new infrastructure:

import { Workflow, Engine } from 'bunqueue/workflow';

const orderFlow = new Workflow('order-pipeline')
  .step(
    'reserve-stock',
    async () => {
      await inventory.reserve();
      return { reserved: true };
    },
    {
      compensate: async () => await inventory.release(), // auto-rollback on failure
    }
  )
  .step(
    'charge',
    async () => {
      return { txId: await payments.charge() };
    },
    {
      compensate: async () => await payments.refund(),
    }
  )
  .waitFor('manager-approval', { timeout: 86_400_000 }) // human-in-the-loop
  .step('confirm', async (ctx) => {
    return { txId: (ctx.steps['charge'] as { txId: string }).txId };
  });

const engine = new Engine({ embedded: true });
engine.register(orderFlow);
const run = await engine.start('order-pipeline', { orderId: 'ORD-1' });
await engine.signal(run.id, 'manager-approval', { approved: true });
bunqueueTemporalInngestTrigger.dev
InfrastructureNone (embedded)PostgreSQL + 7 servicesCloud-onlyRedis + PostgreSQL
Saga compensationBuilt-inManualManualManual
Human-in-the-loop.waitFor()Signals APIstep.waitForEvent()Waitpoint tokens
Self-hostedZero-configComplexNoComplex
PricingFree (MIT)Free / Cloud $$Per-executionFree tier, then $50/mo+

Also included: nested workflows, doUntil/doWhile loops, forEach over dynamic lists, schema validation (Zod, ArkType, Valibot or any .parse()), step timeouts, typed events, SQLite-persisted execution state.

Workflow Engine guide →

Built for AI Agents (MCP Server)

bunqueue ships a native MCP server: 79 primary tools, 5 resources, 3 prompts. Agents schedule cron jobs, push and process jobs, retry failures, set rate limits, and read stats — no glue code. HTTP handlers let an agent register a URL and have a worker call it for every job. Run it over stdio or Streamable HTTP, and use the agent preset for dynamic toolsets and destructive-call confirmation. The 79 primary tools include bounded paging, job observation and queue diagnosis; 2 scoped resource templates limit reads to a queue. Optional workflows add up to 3 tools. Dynamic discovery tools are counted separately.

bun add bunqueue @modelcontextprotocol/sdk   # the MCP SDK is an optional peer
claude mcp add bunqueue -e BUNQUEUE_MCP_PROFILE=agent -- bunx --package=bunqueue bunqueue-mcp
// Claude Desktop / Cursor / Windsurf
{
  "mcpServers": {
    "bunqueue": {
      "command": "bunx",
      "args": ["--package=bunqueue", "bunqueue-mcp"],
      "env": { "BUNQUEUE_MCP_PROFILE": "agent" }
    }
  }
}

Then just ask: "Schedule a cleanup job every day at 3 AM" · "Show me all failed jobs and retry them" · "Set rate limit to 50/sec on api-calls".

MCP guide →

Check configuration before connecting: BUNQUEUE_MCP_PROFILE=agent bunx --package=bunqueue bunqueue-mcp --doctor --json. Doctor reports readiness without opening embedded databases; startup still checks workflow schemas. Read structured outcomes and observe final job state before claiming success. Queue-scoped pages omit payloads by default.

For durable agent turns, use Workflow + Engine and the optional bunqueue/agents journal for sessions, redacted progress replay and uncertain external effects. Session resume and queue retries still require provider idempotency and explicit business-effect reconciliation. The repository skill routes directly to queue, workflow, MCP and agent references.

Dashboard

A web dashboard that fully drives your server — queues, jobs, DLQ, cron, webhooks, workers, live activity, SQLite inspector and an AI copilot. Open source, currently in beta:

bunx bunqueue-dashboard

https://github.com/user-attachments/assets/e8a8d38e-b4a6-4dc8-8360-876c0f24d116

8-minute tour on YouTube · Live demo · Dashboard guide · User guide · GitHub

Performance

Native Ryzen 9 9950X3D, Bun 1.3.14; medians from repeated fresh processes:

WorkloadModeMedianPersistence
Internal batched push, 1M jobsEmbedded729,395 jobs/secIn-memory, no dataPath
Public sustained addBulk, 50K cellEmbedded186,384 jobs/secOn-disk buffered SQLite
PUSHB, fresh 50K sampleTCP158,779 jobs/secOn-disk buffered SQLite
No-work worker drain, concurrency 50TCP17,256 jobs/secFull pull/process/ACK
Linear Workflow EngineEmbedded / TCP2,700 / 3,187 workflows/secWorkflow SQLite + 3 queue nodes

These operations do different work; the internal in-memory result is not an SQLite or public-API claim. Run bun run bench, bun run bench:tcp, or bun run bench:workflow on your hardware. Benchmark methodology → · full engineering report

Documentation

bunqueue.dev →

Project & Governance

License

MIT — provided as is, without warranty of any kind. The authors accept no liability for lost data, lost money or any other damage arising from official or unofficial use of bunqueue. See DISCLAIMER.md.

ai
ai-agents
ai-scheduler
background-jobs
bullmq-alternative
bun
bun-queue
cron-scheduler
deno
job-queue
message-queue
nodejs
postgres
postgresql
queue
redis-alternative
s3
sqlite3
task-queue-typescript

Project health

Latest release

2.9.16 · Oct 15, 2026

Releases this year

292

Significant stargazers

Greg Bergé

2,059 followers · starred Feb 2026

Konstantin Tarkus

806 followers · starred Feb 2026

Nico Domino

477 followers · starred Feb 2026

Yusuke Wada

4,088 followers · starred Jul 2026