Docker Image and Compose
Run the official image, preserve data, configure Compose, and upgrade a self-hosted deployment.
The official image serves the web app, HTTP API, and MCP on one port. For a disposable localhost trial, follow Installation. This guide covers a persistent setup.
Choose an image
Images are published as ghcr.io/durabullhq/durabull:<version>. Use latest for evaluation; pin
a published version tag or image digest for production. Find version tags in the
container registry.
Start the maintained Compose stack
The repository includes tooling/docker/docker-compose.self-hosted.yaml. It starts Durabull and
Redis, enables authentication, and stores both services' data in named volumes. Redis is accessible
only on the internal Docker network. The default database is PGlite.
From the repository root, generate two separate secrets:
openssl rand -hex 32
openssl rand -hex 32Create a private .env.self-hosted file and paste the generated values:
BETTER_AUTH_SECRET=<first-generated-value>
DURABULL_REDIS_URL_ENCRYPTION_KEY=<second-generated-value>
APP_BASE_URL=http://localhost:3000
VITE_PUBLIC_APP_URL=http://localhost:3000
DURABULL_APP_PORT=127.0.0.1:3000
DURABULL_AUTHLESS=falseThe placeholders must be replaced; dotenv files do not execute shell expressions. Keep this file
outside version control, preserve the secrets across restarts, and back them up securely.
DURABULL_APP_PORT=127.0.0.1:3000 binds the shipped port mapping to localhost for initial setup.
docker compose --env-file .env.self-hosted \
-f tooling/docker/docker-compose.self-hosted.yaml up -dOpen http://localhost:3000, create an account, and set up an organization. The default MAIN
connection points at the empty Redis service in this stack. Connect your BullMQ application to
that Redis instance, or set DURABULL_REDIS_URL_MAIN to an existing Redis URL reachable from the
container and recreate the app service.
Environment-managed connections are the Compose default. To manage them in the UI, set
DURABULL_ENV_CONNECTIONS=false. Redis URLs are encrypted in either mode, so keep the encryption key.
Configure a public deployment
Set APP_BASE_URL and VITE_PUBLIC_APP_URL to the public HTTPS origin. Keep
DURABULL_AUTHLESS=false, put Durabull behind a reverse proxy, and review
Security and Hardening.
The reverse proxy must route /, /api/*, /mcp, and /.well-known/* to the same app and preserve
the public Host. Set TRUST_PROXY=true in the container only when the proxy controls and replaces
forwarding headers. The shipped Compose file does not forward this variable automatically.
Add settings not included in the Compose file
Compose's --env-file supplies values for ${...} substitutions. It does not pass every variable
in the file into the container. The maintained stack forwards only the variables listed in its
environment block.
For PostgreSQL, email, Linear, custom queue prefixes, or other additional settings, add them to
that block or create an override file. For example, compose.override.yaml at the repository root:
services:
durabull:
environment:
DATABASE_URL: "${DATABASE_URL:?set the PostgreSQL connection URL}"
TRUST_PROXY: "${TRUST_PROXY:-false}"
DURABULL_REDIS_URL_MAIN_PREFIX: "${DURABULL_REDIS_URL_MAIN_PREFIX:-bull}"Add the corresponding values to .env.self-hosted, then include both files:
docker compose --env-file .env.self-hosted \
-f tooling/docker/docker-compose.self-hosted.yaml \
-f compose.override.yaml up -dChanging DATABASE_URL selects a different database; it does not migrate existing PGlite data to
PostgreSQL. Plan any data migration separately.
Preserve data
The shipped stack mounts durabull_data at /app/data and redis_data at /data.
- PGlite data lives at
/app/data/pgliteby default. - If using
docker run, mount a named volume at/app/data, or setDURABULL_PGLITE_DIRto a writable mounted directory. - Host bind mounts must be writable by UID/GID
1000:1000(bun). docker compose downpreserves named volumes.docker compose down -vdeletes them.- Back up the Durabull database, Redis data as needed, and encryption keys. A different encryption key cannot decrypt previously saved connection URLs or integration secrets.
For multiple API replicas, use a shared PostgreSQL database rather than sharing a PGlite directory.
Verify the deployment
curl -fsS http://localhost:3000/api/health
curl -fsS http://localhost:3000/api/app/config
curl -fsS http://localhost:3000/.well-known/oauth-protected-resourceUse your public origin for remote checks. Confirm the MCP resource equals {APP_BASE_URL}/mcp,
then sign in and verify the selected Redis connection in the dashboard. Health alone does not
validate Redis connectivity.
MCP uses the same app port; no additional port is needed. For OAuth smoke tests and incident
triage, see the MCP operations runbook.
Run its automated mcp:e2e checks only against disposable local or staging databases.
Upgrade and roll back
-
Back up the database, encryption keys, and relevant Redis data.
-
Set
DURABULL_IMAGEin.env.self-hostedto the target published image tag. -
Pull and recreate the app with the same Compose files used for deployment. For the PostgreSQL override above:
docker compose --env-file .env.self-hosted \ -f tooling/docker/docker-compose.self-hosted.yaml \ -f compose.override.yaml pull durabull docker compose --env-file .env.self-hosted \ -f tooling/docker/docker-compose.self-hosted.yaml \ -f compose.override.yaml up -d durabullIf you use only the base stack, omit
-f compose.override.yamlfrom both commands. Include any other override files in their original order. Omitting an override can change the database or other settings; Compose does not remember files from previous invocations. -
Repeat the health, login, and queue checks.
Database migrations run during initialization. Rolling back the image does not undo migrations; confirm schema compatibility or restore a compatible backup before downgrading.
Build an image from source
From the repository root:
docker build -f tooling/docker/Dockerfile -t durabull:local .Set DURABULL_IMAGE=durabull:local to use it with Compose. The Dockerfile prunes the monorepo and
uses separate dependency, build, and runtime stages.
Usage telemetry
Production images collect anonymous/pseudonymous usage telemetry. Configuring your own PostHog project does not disable Durabull telemetry. Read the telemetry disclosure for the collected fields and configuration.