Skip to content
🚧 These docs are a work in progress and may contain inaccuracies. Content is being actively reviewed and validated.

Docker Compose

This is the full reference for running Dubby with Docker Compose. For a quick setup, see the Quick Start guide.

services:
dubby:
image: dubbytv/dubby:beta
container_name: dubby
restart: unless-stopped
ports:
- '3000:3000'
environment:
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
- TMDB_API_KEY=${TMDB_API_KEY:-}
- REDIS_URL=redis://valkey:6379
- LOG_LEVEL=${LOG_LEVEL:-info}
volumes:
- ./data:/data
- ./cache:/cache
- /path/to/media:/media
depends_on:
valkey:
condition: service_healthy
healthcheck:
test:
[
'CMD',
'node',
'-e',
"fetch('http://localhost:3000/health').then(r=>r.ok?process.exit(0):process.exit(1)).catch(()=>process.exit(1))",
]
interval: 30s
timeout: 10s
start_period: 15s
retries: 3
valkey:
image: valkey/valkey:latest
container_name: dubby-valkey
restart: unless-stopped
volumes:
- valkey_data:/data
healthcheck:
test: ['CMD', 'valkey-cli', 'ping']
interval: 10s
timeout: 5s
retries: 3
volumes:
valkey_data:

The main application server. Handles the web UI, API, media streaming, and background jobs.

SettingValueDescription
Imagedubbytv/dubby:betaSee release channels
Port3000Web UI and API
Restartunless-stoppedAuto-restart on crash

Valkey is a Redis-compatible in-memory store used for background job queuing and real-time events.

Without Valkey, the server still works for browsing and playback, but background tasks (library scanning, metadata enrichment, subtitle downloads) won’t run.

Container pathPurposePersistence
/dataDatabase, metadata images, trickplay thumbnailsPersistent — back this up
/cacheTranscode cache (HLS segments)Ephemeral — SSD recommended
/mediaYour media libraryRead-write

/data (persistent, back this up):

/data
├── dubby.db # SQLite database (library, users, watch history)
├── images/ # Cached artwork from TMDB (posters, backdrops, logos)
└── trickplay/ # Seek preview thumbnail sprites

/cache (temporary, safe to delete when stopped):

/cache
├── {session-id}/ # HLS segments for active playback sessions
│ ├── master.m3u8
│ ├── playlist.m3u8
│ ├── segment_0.ts
│ └── {track-id}.vtt
└── ...

You can mount media however suits your setup:

Single mount (simple):

volumes:
- /mnt/media:/media

Multiple mounts (organized):

volumes:
- /mnt/movies:/media/movies
- /mnt/tv:/media/tv
- /mnt/anime:/media/anime

When adding libraries in the UI, use the container paths (e.g., /media/movies).

VariableRequiredDescription
BETTER_AUTH_SECRETYesAuth secret. Generate with openssl rand -base64 32. Min 32 chars.
TMDB_API_KEYNoTMDB API key for metadata. Free at themoviedb.org. Can also be set in the setup wizard.
REDIS_URLNoValkey/Redis URL for background jobs. Default: none.
LOG_LEVELNodebug, info, warn, error. Default: info

For the full list, see Environment Variables.

Terminal window
# Pull latest images
docker compose pull
# Recreate containers with new images
docker compose up -d
# Optional: remove old images to free disk space
docker image prune -f

Database migrations run automatically on startup — no manual steps needed. Your data, settings, and watch history are preserved across updates. docker compose up -d detects the updated image and recreates only affected containers — there is no need to run docker compose down first.

See Updating for release channels, version pinning, and rollback.

The only directory you need to back up is /data (mapped to ./data by default). It contains your database, cached artwork, and trickplay thumbnails.

Terminal window
# Stop the server before backing up to avoid database corruption
docker compose stop dubby
# Back up
cp -r ./data ./data-backup-$(date +%Y%m%d)
# Restart
docker compose start dubby

/cache is ephemeral (transcode segments) and does not need to be backed up. /media is your source media — back it up separately with your own strategy.

If you prefer not to use Docker Compose:

Terminal window
# Create a network so the containers can communicate
docker network create dubby-net
# Create directories
mkdir -p ./data ./cache
# Generate secret
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
# Start Valkey
docker run -d \
--name dubby-valkey \
--network dubby-net \
--restart unless-stopped \
-v valkey_data:/data \
valkey/valkey:latest
# Start Dubby
docker run -d \
--name dubby \
--network dubby-net \
--restart unless-stopped \
-p 3000:3000 \
-v ./data:/data \
-v ./cache:/cache \
-v /path/to/media:/media \
-e BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
-e REDIS_URL="redis://dubby-valkey:6379" \
dubbytv/dubby:beta
Terminal window
docker logs dubby

Common issues:

  • Missing auth secret — Ensure BETTER_AUTH_SECRET is set (min 32 chars)
  • Permission denied — Ensure the ./data directory is writable by UID 1000 (chown -R 1000:1000 ./data)
  • Port in use — Change the host port (e.g., 3001:3000)
  • Valkey not ready — The dubby container waits for Valkey’s health check. If Valkey fails to start, dubby won’t start either. Check docker logs dubby-valkey.

If docker ps shows the dubby container as unhealthy:

Terminal window
# Test the health endpoint manually
docker exec dubby node -e "fetch('http://localhost:3000/health').then(r=>console.log(r.status))"

The health check has a 15-second start period — the container may show as unhealthy briefly during startup. If it stays unhealthy, check docker logs dubby for errors.

  1. Verify the mount is working:
    Terminal window
    docker exec dubby ls -la /media
  2. Check file permissions — the container runs as UID 1000 and needs read access to media files
  3. Ensure files follow naming conventions
  4. Make sure you used the container path (e.g., /media) when adding the library in the UI, not the host path

If library scanning, metadata enrichment, or subtitle downloads aren’t working:

  1. Check that Valkey is running: docker ps | grep valkey
  2. Check Valkey logs: docker logs dubby-valkey
  3. Verify the REDIS_URL environment variable is set correctly

Without Valkey, browsing and playback still work — only background tasks are affected.

  • Buffering or errors — Check that /cache has enough disk space. Transcode segments are written here during playback.
  • No audio/video — Some media formats require transcoding. Check docker logs dubby for FFmpeg errors.
  • Subtitles missing — External subtitle files must be in the same directory as the media file. Ensure your media directory is mounted read-write (the default) so Dubby can download subtitles.

The /cache directory stores temporary HLS segments during playback. It’s safe to clear when no one is streaming:

Terminal window
docker compose stop dubby
rm -rf ./cache/*
docker compose start dubby

Sessions are automatically cleaned up after 2 minutes of inactivity, but interrupted sessions may leave orphaned segments.

Terminal window
# Follow logs in real-time
docker logs -f dubby
# Last 100 lines
docker logs --tail 100 dubby
# Enable debug logging (add to environment in docker-compose.yml)
# LOG_LEVEL=debug
Terminal window
docker exec -it dubby /bin/sh

To start completely fresh (this deletes your database, users, and settings):

Terminal window
docker compose down
rm -rf ./data ./cache
docker compose up -d

The setup wizard will appear again at http://localhost:3000.