
Building a complete media center with Jellyfin and the *arr stack
Why self-host a media center
Stacking subscriptions to watch two shows a month gets expensive, and every film watched, every pause, every search is logged and monetised. Self-hosting hands back control: no price hikes, no content vanishing from the catalogue, and family sharing with no simultaneous-stream limits.
The stack described here runs on a TerraMaster F4-424 under Debian 13, from search all the way to playback on any screen in the house.
Jellyfin: the core
Jellyfin is an open-source fork of Emby, with no features behind a paywall. It handles movies, TV, music, photos, and live TV with a tuner.
Unlike the NAS's other Docker services, Jellyfin runs in host network mode: DLNA/SSDP, the network auto-discovery protocol, needs multicast, which the Docker bridge handles poorly. Without it, a DLNA client on the LAN simply won't see the server.
# docker-compose.yml - Jellyfin
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
network_mode: host
environment:
- JELLYFIN_PublishedServerUrl=https://jellyfin.home.lan
volumes:
- /mnt/data/apps/jellyfin/config:/config
- /mnt/data/apps/jellyfin/cache:/cache
- /mnt/data/media/movies:/data/movies
- /mnt/data/media/tv:/data/tv
- /mnt/data/media/music:/data/music
restart: unless-stopped
The web interface listens on port 8096. Jellyfin supports hardware transcoding through Intel Quick Sync (QSV) thanks to the N95's integrated GPU: real-time 4K HEVC transcoding without heating the CPU. For authentication, Jellyfin connects to Authelia over OIDC/SSO, giving every family member centralised credentials.
The *arr stack
The *arr ecosystem automates search, download and organisation. Each application has a specific role:
- Prowlarr (port 9696) centralises indexers. Instead of configuring the same sources in both Sonarr and Radarr, you declare them once and Prowlarr syncs automatically.
- Sonarr (8989) for TV and Radarr (7878) for movies. Add a title, set a quality profile, and the app watches RSS feeds, grabs new episodes or better releases, and renames everything to a convention Jellyfin understands.
- Bazarr (6767) watches the Sonarr/Radarr libraries and downloads missing subtitles, picking the best match for the file's release.
- qBittorrent is the download client Sonarr/Radarr use: web UI on 8080, BitTorrent protocol on 6881.
- FlareSolverr (8191) acts as a proxy to solve CloudFlare challenges, required by some indexers.
- Jellyseerr (5055) is the user-facing layer: search a show, click "Request", and Sonarr/Radarr handle the rest. Connected to Authelia over OIDC.
Network and storage architecture
All *arr services run on an isolated arr_net Docker network. Only Jellyfin (host network) and Jellyseerr (through the reverse proxy) are reachable from the LAN.
# docker-compose.yml - Sonarr + Radarr
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
volumes:
- /mnt/data/apps/arr/sonarr:/config
- /mnt/data/media/tv:/tv
- /mnt/data/downloads:/downloads
networks:
- arr_net
restart: unless-stopped
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
volumes:
- /mnt/data/apps/arr/radarr:/config
- /mnt/data/media/movies:/movies
- /mnt/data/downloads:/downloads
networks:
- arr_net
restart: unless-stopped
networks:
arr_net:
name: arr_net
The LinuxServer images use PUID/PGID (1000:1000 here) so permissions on created files come out right.
Directory layout, the critical part
/mnt/data/
├── apps/arr/
│ ├── sonarr/ # Sonarr config
│ ├── radarr/ # Radarr config
│ ├── prowlarr/ # Prowlarr config
│ ├── bazarr/ # Bazarr config
│ ├── qbittorrent/ # qBittorrent config
│ └── jellyseerr/ # Jellyseerr config
├── downloads/
│ ├── complete/ # Finished downloads
│ └── incomplete/ # In progress
└── media/
├── movies/ # Movies organised by Radarr
└── tv/ # TV organised by Sonarr
The essential point: downloads and media must sit on the same filesystem. Sonarr and Radarr can then use hardlinks instead of copying — instant, with no duplicated disk space.
The full flow
- Jellyseerr: the user searches and requests a movie
- Radarr: receives the request, queries indexers via Prowlarr
- Prowlarr: forwards the search to the sources
- qBittorrent: downloads into
/mnt/data/downloads/complete - Radarr: spots the finished file, renames it, creates a hardlink in
/mnt/data/media/movies - Bazarr: detects the new movie, downloads subtitles
- Jellyfin: scans the library, the movie is available
From the user's point of view: one button, and the movie shows up minutes later. No technical interface in sight.
Verdict
Initial configuration takes a solid day, but the whole thing then runs unattended, with Ansible handling redeployment. The *arr + Jellyfin stack reaches a level of service comparable to commercial streaming, with no subscription and full control. The N95 handles hardware transcoding effortlessly, and arr_net isolation keeps everything tidy.
Debian NAS from scratch series — This article is part of a complete series on building a Debian NAS.
Previous: Secure remote access to your NAS with Tailscale | Next: Managing photos with self-hosted Immich
Related articles