Building a complete media center with Jellyfin and the *arr stack

Building a complete media center with Jellyfin and the *arr stack

·5 min read·Updated on February 17, 2026

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

  1. Jellyseerr: the user searches and requests a movie
  2. Radarr: receives the request, queries indexers via Prowlarr
  3. Prowlarr: forwards the search to the sources
  4. qBittorrent: downloads into /mnt/data/downloads/complete
  5. Radarr: spots the finished file, renames it, creates a hardlink in /mnt/data/media/movies
  6. Bazarr: detects the new movie, downloads subtitles
  7. 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

ShareLinkedInXBluesky

Related articles