Automated media management on Docker — requesting, downloading, organizing, and subtitling — with Jellyfin running separately for playback.
graph LR
Seerr -->|TV| Sonarr
Seerr -->|Movies| Radarr
Prowlarr -.->|Indexers| Sonarr & Radarr
Sonarr & Radarr --> SABnzbd -->|Organize| Storage[("Storage")]
Bazarr -.->|Subtitles| Storage
Storage --> Jellyfin
| Service | Port | Purpose | Guide |
|---|---|---|---|
| Seerr | 5055 | Media request portal | Wiki |
| Sonarr | 8989 | TV show management | Wiki |
| Radarr | 7878 | Movie management | Wiki |
| SABnzbd | 8080 | Usenet downloader | Wiki |
| Bazarr | 6767 | Automatic subtitles | Wiki |
| Prowlarr | 9696 | Indexer management | Wiki |
| Recyclarr | — | TRaSH quality profile sync | Wiki |
| Uptime Kuma | 3001 | Monitoring and alerting | Wiki |
| Tailscale | — | VPN for remote access | Wiki |
| cloudflared | — | Public hostnames, opt-in | Wiki |
That's the whole stack, deliberately. Everything here is running in a real install, so the guides describe what actually works rather than what should.
git clone https://github.com/bcanfield/docker-media-server.git
cd docker-media-server
cp .env.example .env # paths, timezone, Tailscale key
cp docker-compose.override.yml.example docker-compose.override.yml
docker compose up -dThe override file is not optional if you care about hardlinks: it mounts ${MEDIA_ROOT} once as
/data instead of passing /tv, /movies and /downloads separately. Separate bind mounts look
like separate filesystems to the container, so link() fails and every import silently becomes a
full copy. Its header explains the layout.
Then configure each service through its web UI — see the wiki.
The configs are the painful part to lose; the media is re-downloadable.
sudo ./backup/install-local-backup-timer.sh # weekly tarball to another disk, no credentialsFor off-site copies via restic, see the Backups guide.