Docker Image and Compose
Run Durabull with the official Docker image, then scale to a production-friendly Compose deployment.
Docker is the fastest way to get a self-hosted Durabull instance running.
Production Docker images collect anonymous/pseudonymous usage telemetry to understand feature usage
and improve Durabull. Configuring POSTHOG_KEY for your own PostHog project does not disable
Durabull telemetry; your project still receives the full PostHog browser analytics stream. Durabull
does not collect Redis URLs, queue names, Redis key names, job data, logs, emails, names,
organizations, or raw error messages.
1) Choose an Image Tag
Use the published image from GHCR:
ghcr.io/durabullhq/durabull:<version>
For local evaluation, latest is fine. For production, pin an explicit version tag.
2) Quick Run with docker run
This starts Redis and Durabull on a shared Docker network:
docker network create durabull
docker run -d --name durabull-redis --network durabull redis:8-alpine
docker run --rm -p 127.0.0.1:3000:3000 --network durabull \
-e DURABULL_AUTHLESS=true \
-e MCP_AUTHLESS_BEARER_TOKEN="$(openssl rand -hex 32)" \
-e DURABULL_ENV_CONNECTIONS=true \
-e DURABULL_REDIS_URL_ENCRYPTION_KEY=$(openssl rand -hex 32) \
-e DURABULL_REDIS_URL_MAIN=redis://durabull-redis:6379 \
-e DURABULL_REDIS_URL_MAIN_ENVIRONMENT=development \
-e DURABULL_REDIS_URL_DEFAULT=MAIN \
-e APP_BASE_URL=http://localhost:3000 \
-e VITE_PUBLIC_APP_URL=http://localhost:3000 \
ghcr.io/durabullhq/durabull:latest
This quick-start uses authless mode for local evaluation only on localhost (127.0.0.1:3000). Authless grants owner-level access to the entire web UI and API, not only /mcp. MCP_AUTHLESS_BEARER_TOKEN protects MCP only. Never expose authless on LAN/WAN without perimeter controls.
Production images use NODE_ENV=production and require MCP_AUTHLESS_BEARER_TOKEN when authless is enabled. For internet-facing deploys, use Compose with DURABULL_AUTHLESS=false and BETTER_AUTH_SECRET.
Open http://localhost:3000, then confirm:
GET /api/healthreturnsstatus: okGET /api/app/configreturns JSON instead of500- the queue dashboard loads for your default connection
- MCP discovery:
GET /.well-known/oauth-protected-resourcereturnsresource=http://localhost:3000/mcp
MCP uses the same port as the app (3000). Do not publish a separate MCP port.
3) Docker Compose (Durabull + Redis)
Use the maintained Compose file at tooling/docker/docker-compose.self-hosted.yaml:
- Single app port (
3000by default) serves API, web, and MCP at{APP_BASE_URL}/mcp - Defaults
DURABULL_AUTHLESStofalse(OAuth production path) - For local authless evaluation only, set
DURABULL_AUTHLESS=trueand a strongMCP_AUTHLESS_BEARER_TOKENwhenNODE_ENV=production - Redis is not published to the host in the shipped Compose file (internal network only). Do not add
6379:6379in production
Start it with:
export DURABULL_REDIS_URL_ENCRYPTION_KEY="$(openssl rand -hex 32)"
export BETTER_AUTH_SECRET="$(openssl rand -hex 32)"
# Optional for production: export APP_BASE_URL=https://your-domain.example
docker compose -f tooling/docker/docker-compose.self-hosted.yaml up -d
Then validate:
GET /api/healthGET /api/app/configGET /api/auth/get-session(authless mode) or OAuth sign-in (default Compose)GET /.well-known/oauth-protected-resource(MCP resource URI)- Full MCP smoke (staging/local only): see MCP operations runbook
4) Persistence Notes
When DATABASE_URL is unset, PGlite data is file-backed inside the container.
- Default location:
/app/data/pglite - For persistence, mount
/app/dataor setDURABULL_PGLITE_DIRto a mounted path. - Named Docker volumes are the simplest option for local deployments.
- If you bind-mount a host directory, ensure it is writable by container UID/GID
1000:1000(bun).
Older Image Workaround
If you are pinned to an older image that fails with EACCES: permission denied, mkdir '/app/data' or returns 500 for /api/app/config, use one of these temporary workarounds:
- upgrade to an image that includes the writable
/app/datafix - set
DURABULL_PGLITE_DIRto a writable path inside the container for ephemeral evaluation - switch to PostgreSQL mode with
DATABASE_URL
5) Production Notes
For internet-facing deployments:
- set
DURABULL_AUTHLESS=false - configure
BETTER_AUTH_SECRET - configure
DURABULL_REDIS_URL_ENCRYPTION_KEY - set correct
APP_BASE_URLandVITE_PUBLIC_APP_URL(required for MCP OAuth resource{APP_BASE_URL}/mcp) - review Security and Hardening
- use PostgreSQL (
DATABASE_URL) for stateful team environments - after deploy, follow MCP operations runbook (use staging for
mcp:e2e)
MCP on self-hosted Docker
- Endpoint:
{APP_BASE_URL}/mcpon port3000(orDURABULL_APP_PORT) - Production (internet-facing):
DURABULL_AUTHLESS=false, OAuth, requiredAPP_BASE_URL - Authless is for isolated lab networks only — not a substitute for OAuth on production ingress
- Details: MCP Server
6) Build Image from Source
Source Dockerfile path:
tooling/docker/Dockerfiletooling/docker/docker-compose.self-hosted.yaml
It uses turbo prune and multi-stage builds for optimized production images.