Bun is an all-in-one JavaScript and TypeScript toolkit: a runtime, package manager, bundler, and test runner in a single executable. It is designed as a drop-in replacement for Node.js and runs most npm packages and Node.js APIs unchanged.
I believe Bun is the future of the JavaScript runtime. In December 2025 Anthropic acquired Bun, and it now powers Claude Code and the Claude Agent SDK. Bun stays open source and MIT-licensed, and now has long-term backing. That is why I spend time making Matterbridge and its plugins run well on Bun.
bun add matterbridge --global --omit=dev
bunx --bun matterbridge
The image (tag bun 69 MB) includes only Matterbridge, using the latest release published on npm. This image is based on oven/bun:slim. Plugins are not included in the image: they will be reinstalled on first run.
docker pull luligu/matterbridge:bun && docker run --name matterbridge -v ~/Matterbridge:/root/Matterbridge -v ~/.matterbridge:/root/.matterbridge -v ~/.mattercert:/root/.mattercert --network host --restart always --stop-timeout 60 -d luligu/matterbridge:bun
The bun image installs the latest Matterbridge release from npm and runs it with the Bun runtime.
oven/bun:slim.bun install matterbridge --global --omit=dev.src and cjs directories to reduce the image size.mb_health.bun --bun /usr/local/bin/matterbridge --docker.docker/entrypoint.bun.sh to print the container environment before starting Matterbridge.| File | Purpose |
|---|---|
docker/Dockerfile.bun |
Builds the Bun image with Matterbridge installed from npm |
docker/Dockerfile.bun.dockerignore |
Limits the Docker build context |
docker/entrypoint.bun.sh |
Prints container and Bun details, then starts Matterbridge |
npm run docker:build:local:bun # build the local image (matterbridge:bun)
npm run docker:run:local:bun # run the local image (container matterbridge-bun-local, port 8283)
npm run docker:run:hub:bun # pull and run luligu/matterbridge:bun
npm run docker:buildx:cloud:bun # build and publish the multi-platform image
The local-bun image copies the local Matterbridge source tree and runs it directly with the Bun runtime. It does not create a production TypeScript build.
oven/bun:slim.bun scripts/bun-exports.mjs.bun install --omit=dev.bun link.src and cjs directories to reduce the image size.mb_health.bun --bun bin/matterbridge.js --docker.docker/entrypoint.local.bun.sh to print the container environment before starting Matterbridge.| File | Purpose |
|---|---|
docker/Dockerfile.local.bun |
Builds the development image from the local source tree |
docker/Dockerfile.local.bun.dockerignore |
Limits the Docker build context |
docker/entrypoint.local.bun.sh |
Prints container and Bun details, then starts Matterbridge |
npm run docker:build:localbun # build the image (matterbridge:local-bun)
npm run docker:run:localbun # run it (container matterbridge-local-bun, port 8283)
npm run docker:exec:localbun # open a shell in the running container
npm run docker:log:localbun # follow the container logs
A headless Pi Zero 2 W (512 MB RAM) running Matterbridge on Bun, with zram swap, a trimmed service list, and the graphics stack disabled to free RAM. Tested with a bridge of 50 devices.
See Matterbridge on a Raspberry Pi Zero 2 W for the full setup.
A headless Pi 4 Model B (8 GB RAM) running Matterbridge on Bun as the only runtime, with no swap, no Docker, passwordless sudo, a trimmed service list, and the graphics stack disabled.
See Matterbridge on a Raspberry Pi 4 Model B for the full setup.
The core bridge runs on Bun: it creates its directories, initializes the Matter node storage, and brings up the server node and endpoints. The web frontend is built and served. See the TODO list below for the known limitations.
Bun docker image cannot resolve the container user name. In the official Bun images,
both node:os and bun:os return username: "unknown" and shell: "unknown"
from os.userInfo(), even though they correctly return the UID, GID, and home
directory. Consequently, Matterbridge sends User: unknown to the frontend
system-information view instead of the container account (for example, root).
Reproduce with bun -e "import * as os from 'bun:os'; console.log(os.userInfo())".
The ws package client ignores top-level TLS options under Bun. Under Node, both the
global WebSocket and the ws package client accept the TLS material (ca, cert, key,
rejectUnauthorized) as top-level constructor options, the same way https.request does:
new WebSocket(url, { ca, cert, key, rejectUnauthorized }). Under Bun, the ws package
delegates to Bun's native WebSocket implementation, which only reads TLS options nested
under a tls field (new WebSocket(url, { tls: { ca, cert, key, rejectUnauthorized } })).
Passing the Node-style top-level options under Bun does not throw — it silently falls back
to Bun's default TLS settings, so the handshake fails against a self-signed/mTLS server with
a close event, code 1015 ("TLS handshake failed"), or the connection is torn down entirely
(code 1006) if the server also requires a client certificate. This bit matterbridge-hass's
HomeAssistant.connect(), which opens an outbound wss:// connection to Home Assistant with
a custom CA and/or rejectUnauthorized: false and previously passed those fields top-level
only.
Workaround (works identically on both Node and Bun, no runtime detection needed): pass the
TLS fields both top-level and nested under tls in the same options object — each
runtime reads the shape it understands and ignores the other.
Reproduced and asserted by buntest/wssTest.test.ts.
Reported upstream: oven-sh/bun#31396 (open,
"WebSocket npm fails TLS handshake with self signed certificates"), with a fix proposed in
oven-sh/bun#31397 (open, not yet merged) —
revisit dropping the workaround once that lands in a release.