Deployment
Building, running, and observing the worker in production.
Build and start
bunx openqueue buildbuild compiles every discovered task, its config, and the worker into a
self-contained Nitro node-server bundle at .output (configurable via
build.outDir) and prints what it found:
OpenQueue build wrote 14 tasks, 5 queues, 3 schedules, 5.6 MB to .outputbunx openqueue startstart prefers the built artifact — it spawns .output/server/index.mjs, polls
/health until ready, and forwards SIGTERM/SIGINT so in-flight jobs drain
before exit. When no artifact exists it falls back to booting the worker from
source. It injects PORT/NITRO_PORT, preserving the worker's 8090 default.
The artifact is a Node bundle, so start runs it under a node binary from
PATH when one satisfies the floor (^20.19 || >=22.12), falling back to Bun
(the CLI's own runtime) when no suitable Node is found. It logs which it chose:
[openqueue] artifact runtime: Node v22.12.0 (/usr/local/bin/node)Node is preferred because Bun's node-compat carries a heavier resident-memory profile for the long-lived worker; install Node in the runtime image to pin it.
Running the artifact directly
The .output bundle is a standard Nitro node-server; run it without the CLI on
Node ^20.19 || >=22.12 or Bun:
PORT=8090 node .output/server/index.mjsWithout PORT, a direct boot defaults to Nitro's port 3000 — set PORT
explicitly (or use openqueue start, which injects it).
Sourcemaps
build: { sourcemap: true } in worker.config.ts emits .map files alongside
the bundle in .output/server — for error symbolication (e.g. a Sentry
sourcemap upload). Maps are off by default and add tens of MB: upload them in
CI, then strip *.map from the deployed image.
Docker
The repository ships a worker Dockerfile under docker/worker. The image
runs openqueue build under Bun, then openqueue start — which runs the built
artifact under Node when the image provides one, else Bun. The only required
runtime input is REDIS_URL.
FROM oven/bun:1 AS base
WORKDIR /app
COPY . .
RUN bun install --frozen-lockfile && bunx openqueue build
CMD ["bunx", "openqueue", "start"]To run the artifact on Node in production, build it under Bun and copy .output
into a slim Node image — no Bun or source at runtime:
FROM oven/bun:1 AS build
WORKDIR /app
COPY . .
RUN bun install --frozen-lockfile && bunx openqueue build
FROM node:22-slim
WORKDIR /app
COPY --from=build /app/.output ./.output
ENV PORT=8090
EXPOSE 8090
CMD ["node", ".output/server/index.mjs"]Scale horizontally by running more replicas — BullMQ coordinates workers
through Redis, and concurrency.global caps each replica. To run heavy
queues on their own, bigger machines, see Scaling.
Observability
- OpenTelemetry — trace context is captured at enqueue and restored when the job runs, so a job appears as a child span of the request that triggered it. Spans are emitted into the process's existing tracer provider.
- Drains — run lifecycle events (enqueued, started, completed, failed)
can be fanned out to sinks.
consoleDrain()is built in;composeDrains()combines several; implementQueueDrainto ship events anywhere else. - Metrics — per-queue counters and latency percentiles power the
Workbench overview; enable persistence with the
metricsconfig block.
Production checklist
- Set a unique
namespaceper application sharing a Redis instance. - Put Workbench behind
author mount it inside your authenticated app. - Wire
postgresAdapterso run history survives Redis eviction. - Keep
attempts/backoffdeliberate on tasks with side effects — and make handlers idempotent, since at-least-once delivery is the contract.