Docker Compose
This is the full reference for running Dubby with Docker Compose. For a quick setup, see the Quick Start guide.
Full docker-compose.yml
Section titled “Full docker-compose.yml”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:Services
Section titled “Services”The main application server. Handles the web UI, API, media streaming, and background jobs.
| Setting | Value | Description |
|---|---|---|
| Image | dubbytv/dubby:beta | See release channels |
| Port | 3000 | Web UI and API |
| Restart | unless-stopped | Auto-restart on crash |
valkey
Section titled “valkey”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.
Volumes
Section titled “Volumes”| Container path | Purpose | Persistence |
|---|---|---|
/data | Database, metadata images, trickplay thumbnails | Persistent — back this up |
/cache | Transcode cache (HLS segments) | Ephemeral — SSD recommended |
/media | Your media library | Read-write |
What’s stored where
Section titled “What’s stored where”/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└── ...Flexible media mounting
Section titled “Flexible media mounting”You can mount media however suits your setup:
Single mount (simple):
volumes: - /mnt/media:/mediaMultiple mounts (organized):
volumes: - /mnt/movies:/media/movies - /mnt/tv:/media/tv - /mnt/anime:/media/animeWhen adding libraries in the UI, use the container paths (e.g., /media/movies).
Environment variables
Section titled “Environment variables”| Variable | Required | Description |
|---|---|---|
BETTER_AUTH_SECRET | Yes | Auth secret. Generate with openssl rand -base64 32. Min 32 chars. |
TMDB_API_KEY | No | TMDB API key for metadata. Free at themoviedb.org. Can also be set in the setup wizard. |
REDIS_URL | No | Valkey/Redis URL for background jobs. Default: none. |
LOG_LEVEL | No | debug, info, warn, error. Default: info |
For the full list, see Environment Variables.
Updating
Section titled “Updating”# Pull latest imagesdocker compose pull
# Recreate containers with new imagesdocker compose up -d
# Optional: remove old images to free disk spacedocker image prune -fDatabase 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.
Backup and restore
Section titled “Backup and restore”The only directory you need to back up is /data (mapped to ./data by default). It contains your database, cached artwork, and trickplay thumbnails.
# Stop the server before backing up to avoid database corruptiondocker compose stop dubby
# Back upcp -r ./data ./data-backup-$(date +%Y%m%d)
# Restartdocker 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.
Docker CLI alternative
Section titled “Docker CLI alternative”If you prefer not to use Docker Compose:
# Create a network so the containers can communicatedocker network create dubby-net
# Create directoriesmkdir -p ./data ./cache
# Generate secretBETTER_AUTH_SECRET=$(openssl rand -base64 32)
# Start Valkeydocker run -d \ --name dubby-valkey \ --network dubby-net \ --restart unless-stopped \ -v valkey_data:/data \ valkey/valkey:latest
# Start Dubbydocker 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:betaTroubleshooting
Section titled “Troubleshooting”Container won’t start
Section titled “Container won’t start”docker logs dubbyCommon issues:
- Missing auth secret — Ensure
BETTER_AUTH_SECRETis set (min 32 chars) - Permission denied — Ensure the
./datadirectory 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.
Health check failing
Section titled “Health check failing”If docker ps shows the dubby container as unhealthy:
# Test the health endpoint manuallydocker 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.
No media showing
Section titled “No media showing”- Verify the mount is working:
Terminal window docker exec dubby ls -la /media - Check file permissions — the container runs as UID 1000 and needs read access to media files
- Ensure files follow naming conventions
- Make sure you used the container path (e.g.,
/media) when adding the library in the UI, not the host path
Background jobs not running
Section titled “Background jobs not running”If library scanning, metadata enrichment, or subtitle downloads aren’t working:
- Check that Valkey is running:
docker ps | grep valkey - Check Valkey logs:
docker logs dubby-valkey - Verify the
REDIS_URLenvironment variable is set correctly
Without Valkey, browsing and playback still work — only background tasks are affected.
Playback not working
Section titled “Playback not working”- Buffering or errors — Check that
/cachehas enough disk space. Transcode segments are written here during playback. - No audio/video — Some media formats require transcoding. Check
docker logs dubbyfor 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.
Cache filling up disk
Section titled “Cache filling up disk”The /cache directory stores temporary HLS segments during playback. It’s safe to clear when no one is streaming:
docker compose stop dubbyrm -rf ./cache/*docker compose start dubbySessions are automatically cleaned up after 2 minutes of inactivity, but interrupted sessions may leave orphaned segments.
View logs
Section titled “View logs”# Follow logs in real-timedocker logs -f dubby
# Last 100 linesdocker logs --tail 100 dubby
# Enable debug logging (add to environment in docker-compose.yml)# LOG_LEVEL=debugShell access
Section titled “Shell access”docker exec -it dubby /bin/shReset everything
Section titled “Reset everything”To start completely fresh (this deletes your database, users, and settings):
docker compose downrm -rf ./data ./cachedocker compose up -dThe setup wizard will appear again at http://localhost:3000.