Building a Self-Hosted Media Stack That Actually Works
Running Jellyfin on its own is straightforward enough. But pairing it with Radarr for movies and Sonarr for TV shows – all inside a single Docker Compose stack – is where a home media server goes from a weekend experiment to something genuinely useful. The whole setup handles discovery, downloading, organization, and streaming in one connected pipeline, and once it’s running, you rarely need to touch it.
This guide walks through configuring all three services together using Docker Compose, connecting them through a shared network, and wiring up the integrations so Radarr and Sonarr feed content directly into Jellyfin’s library. You’ll need Docker and Docker Compose installed, a machine with enough storage, and about an hour of focused setup time.

Writing the Docker Compose File
Start by creating a project directory – something like /opt/mediastack or wherever you prefer to keep your Docker projects. Inside that directory, create a docker-compose.yml file. The stack will define five services: Jellyfin, Radarr, Sonarr, a download client (qBittorrent works well here), and Prowlarr to manage indexers. All five services need to share a custom bridge network so they can reference each other by container name.
Here’s what the Compose file should look like:
version: "3.8"
networks:
medianet:
driver: bridge
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
networks:
- medianet
ports:
- "8096:8096"
volumes:
- ./jellyfin/config:/config
- ./media/movies:/movies
- ./media/tv:/tv
restart: unless-stopped
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
networks:
- medianet
ports:
- "7878:7878"
volumes:
- ./radarr/config:/config
- ./media/movies:/movies
- ./downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
restart: unless-stopped
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
networks:
- medianet
ports:
- "8989:8989"
volumes:
- ./sonarr/config:/config
- ./media/tv:/tv
- ./downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
networks:
- medianet
ports:
- "8080:8080"
volumes:
- ./qbittorrent/config:/config
- ./downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- WEBUI_PORT=8080
restart: unless-stopped
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
networks:
- medianet
ports:
- "9696:9696"
volumes:
- ./prowlarr/config:/config
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
restart: unless-stopped
Adjust the TZ value to match your timezone and replace the PUID and PGID values with your actual user and group IDs (run id in your terminal to find them). The shared /downloads volume is the key to making this work – Radarr and Sonarr need to see the same download directory that qBittorrent writes to, so they can move files after a download completes. Once the file is saved, bring everything up with docker compose up -d.

Connecting the Services
With all containers running, open Prowlarr at http://localhost:9696 first. Add your indexers here – Prowlarr acts as a central hub and syncs them to both Radarr and Sonarr automatically. Go to Settings > Apps and add Radarr and Sonarr as applications. For the host, use the container name directly: http://radarr:7878 and http://sonarr:8989. This works because all services share the same Docker network, so container names resolve as hostnames.
Next, open Radarr at http://localhost:7878 and go to Settings > Download Clients. Add qBittorrent with the host set to http://qbittorrent:8080. Use the default credentials qBittorrent sets on first launch (admin/adminadmin) and change them immediately through qBittorrent’s web UI. Repeat the same step in Sonarr at http://localhost:8989.
In Radarr, go to Settings > Media Management and set the root folder to /movies. In Sonarr, set it to /tv. These paths match the container-side volume mappings defined in the Compose file. When Radarr or Sonarr grabs a release, qBittorrent downloads it to /downloads, and once complete, Radarr or Sonarr moves and renames the file into the correct media folder automatically.
Quality profiles are worth spending a few minutes on. Radarr ships with “HD-1080p” and “Any” by default. For most setups, creating a custom profile that targets 1080p remux or web-dl releases while excluding low-quality CAM releases keeps the library clean. Sonarr has the same profile system. Set preferred words or scoring rules under Settings > Profiles to deprioritize scene releases with unwanted tags like “YIFY” or low-bitrate encodes.
Adding Libraries in Jellyfin
Open Jellyfin at http://localhost:8096 and go through initial setup. When prompted to add a media library, add two: one for Movies pointing to /movies, and one for TV Shows pointing to /tv. Jellyfin will scan both folders and pull metadata from TheMovieDB and TheTVDB by default. For subtitle management, Jellyfin integrates well with Bazarr – you can find a full walkthrough on setting up Jellyfin with Bazarr for automated subtitle management if that’s part of your setup.
One thing to configure before adding content: Jellyfin’s library scan is triggered on a schedule, but you can also enable real-time monitoring under Dashboard > Libraries. With real-time monitoring on, Jellyfin detects new files the moment Radarr or Sonarr drops them into the media folder – no waiting for a scheduled scan to catch up.

File Permissions and Common Errors
Most problems in this stack come down to one thing: file permissions. If Radarr or Sonarr can’t move files from /downloads to /movies or /tv, it’s almost always because the PUID/PGID values in the Compose file don’t match the owner of those directories on the host. Run ls -la on your media directories and verify ownership. Fix it with chown -R 1000:1000 /path/to/media if needed, adjusting the numbers to match your actual user.
A second common error is hardlink failure. When Radarr moves a completed download, it tries to hardlink the file from the downloads folder to the media folder rather than copying it – which is faster and saves disk space. Hardlinks only work within the same filesystem. If your /downloads and /media directories live on different drives or partitions, hardlinks will fail and Radarr falls back to copying, which doubles disk usage temporarily. Keeping all volumes on the same physical filesystem solves this entirely.
Container name resolution occasionally causes confusion when first setting up connections between services. If Prowlarr can’t reach Radarr at http://radarr:7878, confirm both containers are attached to the same named network by running docker network inspect medianet and checking that all five containers appear in the output. A missing network declaration in one of the service definitions is the usual culprit – easy to miss during editing, and the only fix is adding the network key and restarting that container.
Frequently Asked Questions
Can I run Jellyfin, Radarr, and Sonarr on the same machine?
Yes. All three services are lightweight enough to run together on a single machine using Docker Compose with a shared network.
Why can’t Radarr move files after downloading?
Usually a permissions issue – make sure the PUID and PGID in your Compose file match the owner of your media and downloads directories on the host system.





