# bunqueue: a Bun job queue that keeps SQLite until PostgreSQL is actually needed

> bunqueue is a TypeScript job queue for Bun that runs embedded against memory or a single SQLite file and switches to PostgreSQL multi-broker only when you scale. The README is explicit about what it does not support, and that is the interesting part.

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

- Repository: https://github.com/egeominotti/bunqueue
- Website: https://bunqueue.dev
- Stars: 570 · Forks: 18
- Language: TypeScript
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/egeominotti-bunqueue

## The problem bunqueue picks: a queue without a Redis to operate

Most TypeScript job queues assume a broker process you have to run, monitor and back up separately. bunqueue's premise is that for a large class of workloads the broker is the expensive part, not the queueing logic. The README states the design directly: "No Redis, no config, no setup", with msgpackr as the only runtime dependency and cron, SQLite, S3, HTTP and WebSocket served by Bun's built-ins.

The target user is a Bun application that needs background jobs, retries and cron but does not want a second stateful service in the deployment. The package description also names AI agents and automation as a focus, and the repository ships an MCP entry point (bunqueue-mcp) alongside the CLI. That is a narrower audience than "anyone running background jobs": it is teams already committed to Bun, or willing to run the standalone server so other runtimes can connect over TCP and HTTP.

## Embedded mode: queue and worker in one object over one SQLite file

The embedded path is the shortest thing in the project. You construct a Bunqueue with embedded: true, point dataPath at a file, and pass a processor function. The README's quickstart shows the queue and the worker living in the same object, persisted to a single SQLite file. Omit dataPath and the queue runs in memory, which the README notes is lost on restart.

That single-object shape is the real architectural decision. There is no separate worker process to deploy, no connection string, and no broker to keep alive. The trade-off is equally clear: an embedded queue scales with the process that hosts it, and the README positions PostgreSQL multi-broker as the answer once you need more than one active broker. The repository layout backs this up: docker-compose.postgres.yml exists specifically for that topology, and examples/postgres-multibroker/ is a separate example rather than the default.

## Installing bunqueue and running a first persisted job

Install with Bun's package manager. The README gives this as the only install step for embedded use.

```bash
bun add bunqueue
```

Then create a queue with a processor and add one job. This is the README's quickstart, unchanged.

```typescript
import { Bunqueue } from 'bunqueue/client';

const app = new Bunqueue('emails', {
  embedded: true,
  dataPath: './data/emails.db',
  processor: async (job) => {
    console.log(`Sending to ${job.data.to}`);
    return { sent: true };
  },
});

await app.add('send', { to: 'alice@example.com' });
```

Running that prints the recipient line from the processor and writes the job into ./data/emails.db. If you remove dataPath, the same code runs in memory and the job does not survive a restart.

If you are not on Bun, run the server instead. The README shows the CLI form with a data path, and the ports it binds.

```bash
bunx bunqueue start --data-path ./data/bunq.db
```

That starts TCP on :6789 and HTTP on :6790. With no --data-path the server defaults to in-memory. The Docker equivalent mounts a volume at /app/data and publishes the same two ports; the README's health check example is a curl against http://127.0.0.1:6790/health.

## PostgreSQL multi-broker, and the storage limits the README states outright

Scaling past one broker means PostgreSQL, and the repository pins the version it recommends. The README says the topology is pinned to PostgreSQL 18.6, that CI validates 15, 16, 17 and 18.6, and that it starts two brokers against one database and namespace. The Compose command requires two values, and the README is specific that you supply both when the password changes and percent-encode reserved characters in the URL only.

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

The constraint that matters most is stated in one line: PostgreSQL is server-only, and embedded mode keeps using memory or SQLite. You cannot point the embedded client at Postgres. MySQL is not supported at all. If your organisation standardised on MySQL, this project is the wrong tool regardless of how well the rest of it fits.

## Four Docker variants, and what choosing between them does not buy you

The README devotes substantial space to the image matrix: Alpine (default), Debian, Debian slim, and distroless, each with a version tag such as 2.9.5-alpine and a moving tag such as alpine or latest. From 2.9.5, completed releases publish to both Docker Hub and GHCR with matching tags, and build references stay on GHCR only.

The README is unusually honest about what the choice means: "The distribution does not select a faster queue engine or unlock extra features." All four run the same server, support linux/amd64 and linux/arm64, run as UID/GID 1001:1001, and store SQLite data in /app/data. The difference is the base image and the tools inside it. Distroless has no shell, so docker exec ... sh is unavailable and you operate through logs, health checks and external debugging tools. Alpine uses musl; Debian variants use glibc. The README also notes that images run as a non-root user, so having a package manager does not mean that user can install packages at runtime. That last point is the kind of detail most projects leave out and then get bug reports about.

## Retention defaults that surprise people: the completed-job cache

The .env.example draws a distinction that is easy to miss. BUNQUEUE_MAX_COMPLETED_JOBS is described as a completed-job hot cache and explicitly "not a disk-retention limit". Durable retention is a separate, optional setting: BUNQUEUE_COMPLETED_RETENTION_MS, in milliseconds, disabled when unset. The example value is 604800000, which is seven days.

So the default posture is that completed jobs accumulate in SQLite unless you configure retention. For a queue that persists to a single file, that is a growth path you should plan for rather than discover. The same file also documents the S3 backup settings, with S3_BACKUP_ENABLED defaulting to 0, a default interval of 21600000 (six hours), a retention count of 7, and a backups/ prefix. Non-AWS endpoints are supported through S3_ENDPOINT, with Cloudflare R2, MinIO and DigitalOcean Spaces named as examples. This is a real operational surface, and it is opt-in.

## Where bunqueue is the wrong choice, and what to compare it against

The clearest boundary is runtime. The package.json exports map gives the bun condition a real implementation and the node condition a file named dist/bun-only.js, which is a naming choice that tells you what to expect. The README's own answer for non-Bun users is to run the standalone server and connect from anywhere. That works, but it means you are operating a broker again, which is the thing the embedded mode was avoiding.

Against BullMQ, the difference is the dependency itself. BullMQ requires Redis, a separate process with its own persistence, memory and failover characteristics. bunqueue replaces that with a file or a database you may already run. The repository even ships MIGRATION_FROM_BULLMQ.md, so the project treats this as a real migration path rather than a hypothetical. The cost is ecosystem maturity: BullMQ has years of production deployments behind it, and bunqueue's release history visible here is a run of patch releases in September 2026 (2.9.3, 2.9.4, 2.9.5), which reads as active iteration rather than a frozen, long-settled API.

Against a Postgres-backed queue such as pg-boss, the trade-off is inverted. pg-boss assumes Postgres from the start; bunqueue assumes SQLite first and treats Postgres as the scaling step. If you already run Postgres everywhere and never want a local file, pg-boss matches your infrastructure more directly. If you want a single-file queue for a Bun service and a documented upgrade path later, bunqueue is shaped for that.

## Maintenance, licensing and what to verify before you depend on it

The repository is not archived, and the last push was on 2026-09-09, with v2.9.5 released the same day. That is recent enough to treat the project as currently being worked on, but the visible release cadence is patch-level, and the README does not document rollback between versions or a compatibility policy for the SQLite file format.

The licence is MIT, which is permissive and carries no copyleft obligation on your own code. That is a statement about the licence text, not legal advice; if you redistribute the software or bundle it into a product, have your own counsel read the LICENSE file rather than this paragraph.

What to verify first is concrete. Check that the image tag you plan to pin exists with docker buildx imagetools inspect egeominotti/bunqueue:<tag>, as the README instructs, because moving tags such as alpine and latest follow newer releases while a digest pins the exact image across base-image rebuilds. Decide explicitly whether you want BUNQUEUE_COMPLETED_RETENTION_MS set, since leaving it unset means completed jobs stay on disk. And if you plan to run more than one broker, confirm your PostgreSQL version against the range the README states (15 through 18.6) before designing around it.

## Conclusion

Adopt bunqueue if you are already on Bun and want queue plus worker in one object backed by a single SQLite file, with a documented path to PostgreSQL brokers when one process stops being enough. Do not adopt it if you need MySQL, if you want to run the embedded mode from Node.js (the package exports a bun-only entry for that runtime), or if you need a queueing system whose retention and rollback behaviour is documented in more detail than the README currently gives. Before committing, verify that the Docker tag you intend to pin exists with docker buildx imagetools inspect egeominotti/bunqueue:<tag>, and confirm which of the eight standalone release archives matches your platform, since the README table is truncated in the copy available here.

## FAQ

### Does bunqueue need Redis?

No. The README states the design as "No Redis, no config, no setup", with msgpackr as the only runtime dependency and cron, SQLite, S3, HTTP and WebSocket handled by Bun's built-ins. Persistence comes from a single SQLite file in embedded mode or from PostgreSQL when you run multiple brokers.

### Can I run bunqueue without Bun, for example from Node.js?

The README's answer is to run the standalone server and connect from anywhere, using bunx bunqueue start --data-path ./data/bunq.db or the Docker image. The package exports a bun-only entry for the Node condition, so embedded mode is not the path for non-Bun runtimes.

### Which databases does bunqueue support?

Memory and SQLite for embedded mode, and PostgreSQL for multi-broker server deployments. The README states that PostgreSQL is server-only, that embedded mode keeps using memory or SQLite, and that MySQL is not supported.

### Which Docker image variant should I pick for bunqueue?

The README says the distribution does not select a faster queue engine or unlock extra features, so choose on tooling: Alpine for a compact musl base with apk, Debian or Debian slim for glibc with apt, and distroless when you operate through logs and health checks and do not need a shell.

## Sources

- [egeominotti/bunqueue on GitHub](https://github.com/egeominotti/bunqueue)
- [License: MIT](https://github.com/egeominotti/bunqueue/blob/main/LICENSE)
- [Project website](https://bunqueue.dev)
- [README](https://github.com/egeominotti/bunqueue/blob/main/README.md)
- [Releases](https://github.com/egeominotti/bunqueue/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/egeominotti-bunqueue
