Open-source project
egeominotti/bunqueue avatar
egeominotti/bunqueue

bunqueue: a SQLite-backed job queue for Bun with an optional PostgreSQL broker mode

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

567 stars18 forksTypeScriptMIT

At a glance

What is it?
bunqueue bundles a queue and worker into one object, persists to a single SQLite file by default, and adds a PostgreSQL multi-broker mode plus S3 backups, cron and MCP when you need them. It is a reasonable fit if you are already on Bun and want to avoid running Redis.
Who is it for?
Adopt bunqueue if you are running Bun and want job persistence without operating Redis, or if you want a queue that can start embedded in the same process and later run as a standalone server behind TCP and HTTP. Do not adopt it if you need MySQL, if you are standardised on Node.js only and unwilling to run the standalone server, or if you need a queue whose durability guarantees you can reason about from a written spec rather than from source.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem bunqueue targets: job persistence without a Redis dependency

Most TypeScript job queues assume a separate broker process. You install Redis, you keep Redis alive, you configure a connection string, and your local development environment now has one more moving part than your application code. bunqueue's answer is to make the queue itself the storage layer. The README's quickstart shows a single Bunqueue object constructed with a name, an embedded flag and a dataPath pointing at one SQLite file, and that object both accepts jobs and processes them. The stated dependency footprint is msgpackr at runtime, with cron, SQLite, S3, HTTP and WebSocket handled by Bun's built-ins. That is the pitch: one file on disk, no broker to operate. The audience is Bun users writing background work, and the repository topics list AI agents and an AI scheduler alongside conventional background jobs. The MCP endpoint is aimed at the same group, letting an agent interact with the queue over a protocol rather than a bespoke HTTP client.

Embedded mode versus the standalone server: two deployment shapes from one codebase

The architecture has a clear split. In embedded mode the queue lives inside your Bun process, and the README notes that omitting dataPath runs it in memory, which means jobs are lost on restart. Supplying dataPath writes to SQLite. The second shape is the standalone server, started with bunx bunqueue start --data-path ./data/bunq.db, which the README says listens on TCP port 6789 and HTTP port 6790. That server mode is what makes the project usable from runtimes other than Bun, since a client only needs to speak the wire protocol. Memory is the zero-configuration default for the server; SQLite is described as the zero-infrastructure persistent option. The important consequence is that the same queue can begin embedded during development and move to a server process later without changing the job model. What the README does not spell out is the migration path between those two shapes. Moving from an embedded SQLite file to a server pointed at the same file is not described, and I would treat that as an open question rather than an assumed capability.

PostgreSQL 15 to 18 multi-broker mode and what it actually changes

For multiple active brokers, the repository ships a Compose file pinned to PostgreSQL 18.6. The command in the README sets POSTGRES_PASSWORD and BUNQUEUE_POSTGRES_URL, then runs docker compose -f docker-compose.postgres.yml up --build -d, which starts two brokers against one database and namespace. Two constraints are stated plainly. PostgreSQL is server-only, so embedded mode keeps using memory or SQLite, and MySQL is not supported. CI validates PostgreSQL 15, 16, 17 and the pinned 18.6 release, which tells you the supported range is deliberate rather than incidental. The README also warns that when the password changes you must supply both Compose values, percent-encoding reserved characters in the URL only. That is a small detail but it is the kind of thing that produces a confusing connection failure if you miss it. What the material does not give is any description of how two brokers coordinate: whether they claim jobs with row locks, advisory locks, or a lease table. If you are evaluating this for a workload where two workers must never process the same job, that mechanism is the thing to read in the source before you trust it.

Running it: commands, ports, config keys and the Docker variants

Installation is bun add bunqueue, and the client import path is bunqueue/client. The embedded example takes embedded: true, dataPath and a processor function, then calls app.add with a job name and a data payload. For the server, bunx bunqueue start --data-path ./data/bunq.db binds TCP 6789 and HTTP 6790. The Docker path uses egeominotti/bunqueue:latest with ports 6789 and 6790 published and a volume at /app/data. Four image variants exist: Alpine as the default on musl, Debian on glibc, Debian slim, and distroless. The README is explicit that the distribution does not select a faster engine or unlock features, which is a useful thing to say out loud because tag choice is otherwise easy to over-read. All images run as UID/GID 1001:1001, store SQLite data in /app/data, and support linux/amd64 and linux/arm64 with Docker selecting the architecture. Distroless has no shell or package manager, so docker exec into a shell is unavailable and you debug through logs and health checks. The built-in health check is /app/bunqueue healthcheck and works on distroless too. From 2.9.5, releases publish to both Docker Hub and GHCR with matching version, latest and variant tags. The README recommends confirming a tag exists with docker buildx imagetools inspect before you use it, and pinning a version or digest for reproducible deployments. Unsuffixed tags such as 2.9.5 stay on Alpine.

Where bunqueue is the wrong tool: the limitations the README states and the ones it does not

The stated limits are concrete. MySQL is unsupported. Embedded mode cannot use PostgreSQL. Omitting dataPath means in-memory storage and job loss on restart, which the README calls out in a comment rather than burying. The distroless image removes your ability to exec a shell, which matters if your operational habits depend on poking around inside a container. Beyond those, the gaps are in what is not described. The README mentions DLQ, cron, SQLite S3 backups and native MCP in the header, but the supplied material contains no detail on retry semantics, backoff strategy, job visibility timeouts, or what happens to an in-flight job when a worker dies mid-processing. For a queue, those are not footnotes. A single SQLite file also raises a concurrency question the README does not answer: SQLite's write locking is well known, and whether a second process can read the file while the first writes is exactly the question you need answered before running embedded mode alongside a separate worker process. The S3 backup feature is named but its failure behaviour on a partial upload is not described. None of this means the project is unsound. It means the documentation covers deployment thoroughly and execution semantics thinly, and you should read the source for the latter.

How it differs from BullMQ, the alternative its own topics name

The repository tags include bullmq-alternative, so the comparison is invited. The difference is architectural rather than feature-by-feature. BullMQ requires Redis as its broker, which brings its own persistence model, its own operational surface, and its own failure modes. bunqueue replaces that broker with SQLite in the default path and PostgreSQL when you need more than one active broker. That is a real trade. You give up Redis's mature ecosystem and its well-understood replication story, and you take on SQLite's file-level concurrency limits or PostgreSQL's coordination semantics instead. You gain the ability to run a queue with no additional service, which for a single-machine Bun application is a meaningful reduction in operational work. If you already run Redis for caching or sessions, the calculus changes: the marginal cost of adding BullMQ to an existing Redis is low, and the reason to pick bunqueue weakens considerably. If you do not run Redis and do not want to, bunqueue's default path is the more direct answer. The honest framing is that bunqueue trades ecosystem maturity for deployment simplicity, and whether that is a good trade depends entirely on whether you were already paying for a broker.

Release cadence, licence and the cost of staying current

Three releases landed within roughly a week of each other in early September 2026: v2.9.4 on the 3rd, v2.9.3 on the 2nd, and v2.9.5 on the 9th. That cadence is worth noting because it cuts both ways. Fast releases mean fixes arrive quickly, and they also mean the surface you pinned can move under you. The README's own advice to pin a version or digest for reproducible deployments is the right response, and it applies to the Docker images as much as to the npm package. Moving tags such as alpine, debian, slim, distroless and latest follow newer releases, so a deployment that tracks a moving tag will pick up changes without an explicit decision. The licence is MIT, which permits commercial use and modification, and the repository is not archived. That is a permissive licence with no copyleft obligation attached to your own code. It says nothing about the S3, PostgreSQL or Docker dependencies you bring alongside it, which carry their own terms. I am not in a position to give legal advice on how those combine in your distribution, and you should not treat a licence identifier as a substitute for reading the actual licence file and your dependency licences.

Editorial conclusion

Adopt bunqueue if you are running Bun and want job persistence without operating Redis, or if you want a queue that can start embedded in the same process and later run as a standalone server behind TCP and HTTP. Do not adopt it if you need MySQL, if you are standardised on Node.js only and unwilling to run the standalone server, or if you need a queue whose durability guarantees you can reason about from a written spec rather than from source. Before committing, verify three things: whether the PostgreSQL broker mode tolerates the failure you actually care about, what the S3 backup path does when an upload fails midway, and whether the embedded SQLite file can be read by a second process while the first is writing.

Official sources

  1. egeominotti/bunqueue on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Community notes

Community notes