Self-hosted service
felixmosh/bull-board avatar
felixmosh/bull-board

bull-board: the image binds every interface, the documented run binds localhost

🎯 Queue background jobs inspector

3,501 stars493 forksTypeScriptMIT

At a glance

What is it?
A dashboard for Bull and BullMQ job queues, published as thirteen packages covering nine framework adapters, a CLI and a container image, with repeatable job management, a parent-and-child job graph and opt-in ninety-day throughput history. The container sets its bind address to all interfaces while the documented run command maps the port to loopback, and the packages table has two empty columns.
Who is it for?
bull-board is the straightforward way to see what a Redis-backed queue is doing, and the no-install routes are genuinely good, since a CLI command against a Redis URL or a container gives you the whole board before you write any code. Four things to check before you expose one.
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 received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

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

Editorial analysis

The image binds every interface and the documented command binds loopback

There are two ways to see a board without writing code, and the container one is a single command with a port mapping:

sh
docker run --rm -p 127.0.0.1:3000:3000 ghcr.io/felixmosh/bull-board --redis redis://host.docker.internal:6379

The mapping targets the loopback address on the host side, which means the dashboard is reachable only from the machine it runs on.

The image itself does not make that decision. Its environment sets the host variable to the all-interfaces address and sets a second variable to false, which appears to control whether the container tries to open a browser. So the default binding inside the container is every interface.

That makes the port mapping the only thing standing between a queue inspector and anyone who can reach the host on that port. Remove it, or publish it on all interfaces, and the board is reachable from the network.

Two other details from the image: the health check runs a small script every thirty seconds that fetches the root path and fails on a server error, and the version is a build argument that defaults to the latest release rather than a pinned one.

History is opt-in because the queue only keeps about an hour

The reason the metrics package exists is a limitation in the queue itself.

BullMQ keeps a short ring buffer of per-minute metrics, so a throughput chart drawn from it cannot look back much further than an hour. That is the default experience: a live chart and no history.

The optional metrics package, marked beta, changes that. It snapshots those metrics into long-retention Redis buckets and feeds them back to the board, which adds a history page and 7, 30 and 90 day ranges to every queue chart.

Two things about how it is wired. It is entirely opt-in, and the file is explicit that without it the core stays stateless and writes nothing, which means the default install has no storage cost at all. And the CLI and the container image already carry the package, so on those two routes you turn it on with a history flag and nothing to install. Embedding it in your own application is the case where you install the package yourself.

The cost is stated rather than hidden: a storage panel in the board tells you what keeping the history is using.

Nine adapters, thirteen packages, twenty examples

The adapter list is the shape of the project. You install an API package plus one adapter for your framework, and the comment line in the install snippet names the alternatives: fastify, koa, hapi, nestjs, hono, h3, elysia and bun, alongside express.

The packages table lists thirteen in total, which is the API package, the interface, the metrics package, the CLI and those nine adapters.

The examples directory is larger than either list, with twenty directories. The extra ones are variants rather than new adapters: several frameworks get an authentication example, express gets a cross-site request forgery example, fastify gets one for the visibility guard, nestjs gets both a fastify variant and a module variant, and Next.js appears twice for the two router styles.

Two examples have no adapter behind them. One covers running several board instances, which is a configuration question rather than a framework one. The other is for Sails, and Sails is not in the package table at all.

So the ratio of examples to adapters is two to one, and one example points at a framework the project does not publish an adapter for.

The packages table has two empty columns

The package table has three columns: name, version and downloads.

The name column is a link for every row. The version column is empty in every row. The downloads column is empty in every row.

Thirteen rows, twenty-six empty cells.

The table appears to be generated or templated from somewhere that is not being populated, since a hand-maintained table would have stale numbers rather than none. Either way, the practical effect is that the readme tells you which thirteen packages exist and nothing about which one you should pick for a new installation, or how active any of them are.

The install section does carry that information in a different form, by naming express as the example and listing the other eight as alternatives in a comment. So the guidance exists, it is just not in the table.

The test command skips the interface and the Bun package

This is a Yarn workspace monorepo, with three workspace globs: the packages directory, the website, and the website demo.

The build and clean scripts walk every workspace in parallel and exclude the two website entries. The test script walks them too, and excludes four: the website, the demo, the Bun adapter and the interface package.

So `yarn test` does not run the interface package's tests, and it does not run the Bun adapter's. Those two need their own invocation, and there is a separate script for the interface tests at the root.

The remaining tooling is compact. Linting is a Rust-based linter invoked with a fix flag by default and a check variant without it. Formatting is the matching formatter, and its check variant is separate. Releases go through a dedicated release tool. A postinstall step installs git hooks through a small helper package.

The coverage configuration and a development compose file that brings up Redis are both at the root, along with a single TypeScript file that serves as the development server entry point.

The manifest is one patch ahead of the newest tag

The root manifest reads version 9.10.3. The three most recent releases are 9.10.2 from late September 2026, 9.10.1 from September, and 9.10.0 from earlier in the same month.

So the version in the repository is one patch ahead of anything published. That is ordinary for a repository where the version is bumped on the way to a release rather than by the release itself, and the root package is private, so it is never published.

It matters for one specific case. The container image takes the version as a build argument and defaults it to the latest published release, so a build with no argument gives you 9.10.2 while the source tree says 9.10.3. Anyone comparing what is running against what is in the tree has to know that.

The root package is also the only one carrying contributor names in its metadata, with two individuals listed alongside the author.

The Express example stops inside the router call, and the docs mention a guard

The minimal example is the shortest thing in the file, and it is cut off at the end:

js
const express = require('express');
const { Queue } = require('bullmq');
const { createBullBoard } = require('@bull-board/api');
const { BullMQAdapter } = require('@bull-board/api/bullMQAdapter');
const { ExpressAdapter } = require('@bull-board/express');

const emailQueue = new Queue('emails', { connection: { host: 'localhost', port: 6379 } });

const serverAdapter = new ExpressAdapter();
serverAdapter.setBasePath('/admin/queues');

createBullBoard({
  queues: [new BullMQAdapter(emailQueue)],
  serverAdapter,
});

const app = express();

app.use('/admin/queues', serverAdapter.getRouter(

So the mounting call is unfinished, and with it the only statement in the readme that shows how the board reaches your HTTP server.

What the visible part does establish is worth reading twice. The adapter is given a base path, the board is created with a list of queue adapters and that server adapter, and then the server adapter's router is mounted at the same path. The board sits under a path you choose, in an application that has its own authentication if you put it behind some.

The documentation reference immediately after lists queue adapter options including read-only mode, retries, formatters and a visibility guard. Those four are the access-control surface of a tool that can retry and remove jobs, and none of them appears in the example.

Editorial conclusion

bull-board is the straightforward way to see what a Redis-backed queue is doing, and the no-install routes are genuinely good, since a CLI command against a Redis URL or a container gives you the whole board before you write any code. Four things to check before you expose one. The container image binds every interface, so the protection in the documented command comes from mapping the port to loopback, and you have to keep that. The board's own documentation points to adapter options for read-only access and a visibility guard, and those are what stand between a queue inspector and someone who can retry or delete your jobs, so set them. The ninety-day history is opt-in and writes to Redis when enabled, with a panel that prices it, so decide whether you want that storage before switching it on. And note the release gap: the manifest is one patch ahead of the newest tag.

Frequently asked questions

what is bull board

It is a dashboard interface for Bull and BullMQ job queues. You can run the CLI against a Redis URL, run the published container image, or embed it by installing an API package plus the adapter for your framework and mounting it on a base path of your choosing.

How does BullMQ work?

That question is about the queue library rather than about this dashboard. What this project does is read those queues and present them: repeatable jobs with their next and last run times, a parent-and-child job graph across queues, per-job logs, and optional throughput history.

bull board alternative

The repository names no alternative and makes no comparison. What it states is that the board supports BullMQ from 5.56.0 upwards including version 6 with a PostgreSQL backend, detects which one it has without configuration, and ships adapters for nine frameworks.

Does bull-board have its own authentication?

The readme does not describe an authentication layer of its own. It points to documentation for queue adapter options including read-only mode, retries, formatters and a visibility guard, and its own container command maps the port to the loopback address rather than all interfaces.

Official sources

  1. felixmosh/bull-board on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/felixmosh-bull-board.svg)](https://hysenlabs.com/projects/felixmosh-bull-board)