Complete Docker Compose media stack for Jellyfin with request management, media automation, indexer management, downloads, and subtitles.
| Service | Role |
|---|---|
| Jellyfin | Your personal media server. Streams movies and TV shows to any device on your network. Think of it as your self-hosted Netflix. |
| Seerr | The request portal (the successor to Jellyseerr). This is where you search for and request movies or shows. It hands off approved requests to Radarr or Sonarr automatically. |
| Radarr | Handles movie automation. It receives a movie request, searches Prowlarr for a suitable torrent, sends it to qBittorrent, and moves the finished file into your media library. |
| Sonarr | Same as Radarr but for TV shows. Monitors for new episodes and downloads them automatically as they air. |
| Prowlarr | Manages your torrent indexers (search sites) in one place. Radarr and Sonarr query Prowlarr instead of connecting to indexers individually. |
| qBittorrent | The download client. Receives torrent jobs from Radarr and Sonarr and does the actual downloading. |
| Bazarr | Subtitle automation. Connects to Radarr and Sonarr, watches your library, and automatically downloads subtitles for your media. |
| FlareSolverr | A helper service for Prowlarr. Solves Cloudflare bot challenges so Prowlarr can access indexers that are protected by Cloudflare. |
You search in Seerr
│
▼
Seerr sends request to Radarr (movies) or Sonarr (TV)
│
▼
Radarr / Sonarr searches Prowlarr for a matching torrent
│
▼
Prowlarr queries your configured indexers (via FlareSolverr if needed)
│
▼
Radarr / Sonarr sends the best torrent to qBittorrent
│
▼
qBittorrent downloads the file
│
▼
Radarr / Sonarr moves the finished file to /movies or /tv
│
▼
Jellyfin picks it up and it appears in your library
│
▼
You watch it on any device running the Jellyfin app
Jellyfin has official apps for almost every platform:
| Platform | Where to get it |
|---|---|
| Android TV / Google TV | Search "Jellyfin" on the Play Store |
| Android phone / tablet | Search "Jellyfin" on the Play Store |
| iPhone / iPad | Search "Jellyfin" on the App Store |
| Web browser | http://<server-ip>:8096 directly |
| Roku | Jellyfin channel in the Roku Channel Store |
| Kodi | JellyCon add-on |
When the app asks for a server address, enter http://<server-ip>:8096 using the local IP of the machine running this stack. Both devices need to be on the same network.
To find your server's local IP:
hostname -I | awk '{print $1}'- Open Jellyseerr at
http://<server-ip>:5055 - Search for a movie or TV show
- Click Request
- For TV shows, select only the seasons you want — deselect the rest
- Submit — the request flows automatically through the stack
Radarr and Sonarr handle everything from here. Check qBittorrent at http://<server-ip>:8080 to watch downloads in progress. Once complete, the media appears in Jellyfin within a few minutes.
| Service | URL | Purpose |
|---|---|---|
| Jellyfin | http://<server-ip>:8096 |
Media server and playback |
| Seerr | http://<server-ip>:5055 |
Movie and TV request portal |
| Radarr | http://<server-ip>:7878 |
Movie automation |
| Sonarr | http://<server-ip>:8989 |
TV automation |
| Prowlarr | http://<server-ip>:9696 |
Indexer management for Radarr and Sonarr |
| qBittorrent | http://<server-ip>:8080 |
Torrent download client |
| Bazarr | http://<server-ip>:6767 |
Subtitle automation |
Replace <server-ip> with the IP address or hostname of the Docker host. On the same machine, use localhost.
This stack is intended for local network use only. Do not forward these ports on your router or expose them directly to the public internet.
This compose file stores application config in the project directory and media/download data outside the repo under /mnt by default.
JellyFin/
|-- docker-compose.yaml
|-- Makefile
|-- jellyfin/config/
|-- jellyfin/cache/
|-- jellyseerr/config/
|-- radarr/config/
|-- sonarr/config/
|-- prowlarr/config/
|-- qbittorrent/config/
`-- bazarr/config/
/mnt/media/
|-- movies/
`-- tv/
/mnt/downloads/
- Docker Engine
- Docker Compose plugin
- Linux host recommended for
/dev/drihardware transcoding passthrough - Media mounted at
/mnt/media, or setMEDIA_ROOT - Downloads mounted at
/mnt/downloads, or setDOWNLOADS_ROOT
The compose file defaults to PUID=1000, PGID=1000, TZ=Africa/Johannesburg, MEDIA_ROOT=/mnt/media, and DOWNLOADS_ROOT=/mnt/downloads. Make sure the user and group IDs match the host user that should own the config, media, and download files.
If the target host will not use hardware transcoding, remove the Jellyfin devices entry for /dev/dri before starting the stack.
You can override these values when starting the stack:
PUID=1001 PGID=1001 TZ=Etc/UTC MEDIA_ROOT=/mnt/storage/media DOWNLOADS_ROOT=/mnt/storage/downloads docker compose up -dIf make is not installed on the host, install it first:
sudo apt-get install -y makeInstall Docker Engine and the Docker Compose plugin on an Ubuntu-based host:
make depsCreate the default media and download folders:
make media-dirsPrompt for the media and download folder locations:
make media-dirs-promptCreate folders using explicit paths:
make media-dirs MEDIA_ROOT=/mnt/storage/media DOWNLOADS_ROOT=/mnt/storage/downloadsCreate the media and download folders before starting the stack:
sudo mkdir -p /mnt/media/movies /mnt/media/tv /mnt/downloads
sudo chown -R 1000:1000 /mnt/media /mnt/downloadsDocker will create the local config folders automatically, but creating them up front is also fine:
mkdir -p jellyfin/config jellyfin/cache
mkdir -p jellyseerr/config radarr/config sonarr/config prowlarr/config
mkdir -p qbittorrent/config bazarr/configFrom this directory:
docker compose up -dFor an existing installation, use the backup-first update target instead. It archives every persistent application config directory before pulling images or recreating containers:
make updateBackups are written to backups/config-<UTC timestamp>.tar.gz. The update keeps all existing bind mounts unchanged and never deletes media or downloads. If any expected config directory is missing, it stops before changing containers. The stack is stopped briefly for a consistent config backup; if the pull or recreation fails, the previously available images are started again automatically.
Running make backup by itself also stops only the currently running stack services for the duration of the archive, then starts those same services again.
If you use non-default media paths, pass the same values to Compose:
MEDIA_ROOT=/mnt/storage/media DOWNLOADS_ROOT=/mnt/storage/downloads docker compose up -dCheck container status:
docker compose psView logs:
docker compose logs -fView one service:
docker compose logs -f jellyfin- Open
http://<server-ip>:8096. - Complete the setup wizard.
- Add media libraries:
- Movies:
/media/movies - TV Shows:
/media/tv
- Movies:
- If the host supports Intel/AMD hardware acceleration through
/dev/dri, enable hardware transcoding in the Jellyfin admin dashboard.
Jellyfin is configured with network_mode: host, so it uses the host network directly instead of a Docker port mapping.
- Open
http://<server-ip>:8080. - Sign in using the initial credentials shown by the container logs if prompted.
- Set the default save path to
/downloads. - Keep the Web UI port as
8080.
Useful log command:
docker compose logs qbittorrent- Open
http://<server-ip>:9696. - Add your indexers.
- Connect Prowlarr to Radarr and Sonarr:
- Radarr URL:
http://radarr:7878 - Sonarr URL:
http://sonarr:8989
- Radarr URL:
- Use each app's API key from its settings page.
- Open
http://<server-ip>:7878. - Set the root folder to
/movies. - Add qBittorrent as the download client:
- Host:
qbittorrent - Port:
8080 - Category:
movies
- Host:
- Confirm completed downloads import from
/downloads.
- Open
http://<server-ip>:8989. - Set the root folder to
/tv. - Add qBittorrent as the download client:
- Host:
qbittorrent - Port:
8080 - Category:
tv
- Host:
- Confirm completed downloads import from
/downloads.
- Open
http://<server-ip>:6767. - Connect Bazarr to Radarr and Sonarr using their API keys.
- Confirm paths match:
- Movies:
/movies - TV:
/tv
- Movies:
- Open
http://<server-ip>:5055. - Connect Seerr to Jellyfin using
http://host.docker.internal:8096. - Connect Seerr to Radarr and Sonarr using their API keys.
- Send requests to the matching root folders and quality profiles.
Most services are on the default Docker Compose network and can reach each other by service name:
radarr
sonarr
prowlarr
qbittorrent
bazarr
jellyseerr
Use those service names for app-to-app settings inside the stack. Use <server-ip> only when accessing the web interfaces from a browser.
Jellyfin is the exception because it runs in host network mode. Jellyseerr includes a host.docker.internal mapping so it can reach Jellyfin at http://host.docker.internal:8096.
For a LAN-only deployment:
- Keep router port forwarding disabled for all stack ports.
- Use the Linux host firewall to restrict access to the local subnet, especially for Jellyfin because it uses host networking and is not controlled by Docker port bindings.
- Use a VPN such as WireGuard or Tailscale if remote access is needed later.
| Host Path | Container Path | Used By |
|---|---|---|
./jellyfin/config |
/config |
Jellyfin |
./jellyfin/cache |
/cache |
Jellyfin |
${MEDIA_ROOT:-/mnt/media} |
/media:ro |
Jellyfin |
./jellyseerr/config |
/app/config |
Jellyseerr |
./radarr/config |
/config |
Radarr |
${MEDIA_ROOT:-/mnt/media}/movies |
/movies |
Radarr, Bazarr |
./sonarr/config |
/config |
Sonarr |
${MEDIA_ROOT:-/mnt/media}/tv |
/tv |
Sonarr, Bazarr |
./prowlarr/config |
/config |
Prowlarr |
./qbittorrent/config |
/config |
qBittorrent |
${DOWNLOADS_ROOT:-/mnt/downloads} |
/downloads |
Radarr, Sonarr, qBittorrent |
./bazarr/config |
/config |
Bazarr |
Jellyfin mounts the media root read-only. Radarr, Sonarr, and Bazarr mount their specific media folders with write access.
Back up config, pull current stable images, and recreate containers:
make updateThe Seerr image automatically migrates an existing Jellyseerr database in ./jellyseerr/config. Keep this directory owned by UID/GID 1000:1000, which is the default user used by the Seerr container.
Restart one service:
docker compose restart jellyfinStop the stack without removing containers:
docker compose stopStop and remove containers without deleting config or media:
docker compose downRemove unused Docker images:
docker image pruneBack up the local config folders regularly:
jellyfin/
jellyseerr/
radarr/
sonarr/
prowlarr/
qbittorrent/
bazarr/
The media and download folders live outside this repo under MEDIA_ROOT and DOWNLOADS_ROOT.
If a web UI does not load, check that the container is running:
docker compose psIf imports fail, check permissions on MEDIA_ROOT and DOWNLOADS_ROOT, then confirm the same paths are configured inside Radarr, Sonarr, and qBittorrent.
If hardware transcoding does not work, confirm the host exposes /dev/dri:
ls -ld /dev/driIf qBittorrent credentials are not known, inspect its logs:
docker compose logs qbittorrent- Timezone:
Africa/Johannesburg - User/group IDs:
1000:1000for LinuxServer.io services - qBittorrent Web UI port:
8080 - Jellyfin host media path:
${MEDIA_ROOT:-/mnt/media} - Download path:
${DOWNLOADS_ROOT:-/mnt/downloads} - Jellyfin hardware device mount:
/dev/dri