The Complete Self-Hosted Media Stack 2026: From Request to Stream (Full Architecture Guide)

The Complete Self-Hosted Media Stack 2026: From Request to Stream (Full Architecture Guide)

The Complete Self-Hosted Media Stack 2026: From Request to Stream

You have seen the individual guides. Radarr here, Sonarr there, Jellyfin over there. But how does the entire system fit together? What talks to what? What is the exact sequence of events from "I want to watch Dune" to the movie playing on your TV?

This guide maps the complete architecture of a professional self-hosted media stack in 2026. Every tool, every connection, every data flow.


The Architecture Overview

User Request Flow:

User (phone/browser)
  |
  v
[Seerr] --- "I want Dune Part Two"
  |
  v
[Radarr] --- "Find and grab Dune Part Two"
  |
  v
[Prowlarr] --- "Search all indexers for releases"
  |
  v
[qBittorrent] --- "Download the best release"
  |
  v
[Radarr] --- "Import, rename, organize"
  |
  v
[Bazarr] --- "Download subtitles"
  |
  v
[Tdarr] --- "Optimize codec/size (optional)"
  |
  v
[Jellyfin] --- "Serve to any device"
  |
  v
User (TV/phone/browser) --- "Watching Dune Part Two"


Monitoring Layer:

[JellyWatch] --- Real-time sessions, push notifications
[Uptime Kuma] --- Service availability
[Jellystat] --- Historical analytics

Every Tool Explained

Layer 1: User Interface

Seerr (Media Requests)

What it does: Provides a Netflix-like interface where users search for movies and TV shows and submit requests.

Connects to:

  • Jellyfin/Emby (user authentication, library status)
  • Radarr (sends movie requests)
  • Sonarr (sends TV show requests)
  • TMDB (metadata and search)

Data flow: User searches TMDB via Seerr UI > clicks Request > Seerr sends the TMDB ID to Radarr or Sonarr > request status tracked until content is available.

Port: 5055


Layer 2: Media Management

Radarr (Movie Automation)

What it does: Monitors for movie releases, grabs them from indexers via download clients, imports and renames files into your library.

Connects to:

  • Prowlarr (receives indexer configurations)
  • qBittorrent/Transmission (sends download commands)
  • Jellyfin (triggers library scan after import)
  • Seerr (receives requests)
  • Recyclarr/Profilarr (receives quality profile configurations)

Data flow: Receives request from Seerr (or manual add) > searches indexers via Prowlarr > sends .torrent to qBittorrent > monitors download progress > on completion: renames file, moves to library folder, notifies Jellyfin.

Port: 7878

Sonarr (TV Show Automation)

What it does: Same as Radarr but for TV series. Monitors for new episodes, grabs them automatically as they air.

Connects to: Same as Radarr.

Data flow: Identical to Radarr but operates on series/seasons/episodes instead of movies.

Port: 8989

Prowlarr (Indexer Management)

What it does: Centralizes all your torrent/usenet indexer configurations. Configure indexers once in Prowlarr, they sync to all connected apps.

Connects to:

  • Radarr (pushes indexer configs)
  • Sonarr (pushes indexer configs)
  • External indexers (searches for releases)

Data flow: Radarr/Sonarr search requests are proxied through Prowlarr > Prowlarr queries all configured indexers > returns results to the requesting app.

Port: 9696


Layer 3: Download

qBittorrent (Download Client)

What it does: Downloads torrent files. Manages seeding, bandwidth, and queue priority.

Connects to:

  • Radarr (receives download commands, reports status)
  • Sonarr (receives download commands, reports status)
  • VPN (optional, for privacy)

Data flow: Receives .torrent or magnet link from Radarr/Sonarr > downloads to /downloads/incomplete > moves to /downloads/complete on finish > Radarr/Sonarr detect completion and import.

Port: 8080 (WebUI)


Layer 4: Post-Processing

Bazarr (Subtitle Automation)

What it does: Automatically downloads subtitles for every movie and episode in your library from multiple providers.

Connects to:

  • Radarr (reads movie library)
  • Sonarr (reads TV library)
  • OpenSubtitles, Addic7ed, Podnapisi (subtitle providers)

Data flow: Scans Radarr/Sonarr libraries > identifies missing subtitles > searches providers > downloads best match > places .srt file next to the video file.

Port: 6767

Tdarr (Library Transcoding - Optional)

What it does: Automatically transcodes your library to a target codec/format. Converts H.264 to H.265 to save storage, adds compatibility audio tracks, removes unwanted subtitle tracks.

Connects to:

JellyWatchTry JellyWatch — Your Jellyfin companion, everywhere.
  • Your media folder (reads and writes files)
  • Jellyfin (triggers library refresh after processing)

Data flow: Scans media folders > identifies files not matching target specs > transcodes using GPU/CPU > replaces original (or saves alongside) > notifies Jellyfin.

Port: 8265 (WebUI), 8266 (Server)


Layer 5: Media Server

Jellyfin (Streaming)

What it does: Organizes your media library with metadata, serves it to any device with transcoding when needed.

Connects to:

  • Radarr/Sonarr (receives library scan triggers)
  • All client devices (streams media)
  • JellyWatch (provides API data for monitoring)

Data flow: Scans library folders > fetches metadata from TMDB/TVDB > serves content to clients > transcodes if client cannot Direct Play.

Port: 8096


Layer 6: Monitoring

JellyWatch (Mobile Admin)

What it does: Native Android app for real-time server monitoring, push notifications, and arr stack management.

Connects to:

  • Jellyfin API (sessions, server health)
  • Radarr API (download queue)
  • Sonarr API (download queue)
  • Seerr API (media requests)

Data flow: Polls Jellyfin API every 5-10 seconds > displays active sessions > sends push notifications for events > allows request approval/denial.

Uptime Kuma (Availability)

What it does: Checks if all services are responding. Sends alerts when something goes down.

Connects to: All services via HTTP health endpoints.

Port: 3001

Jellystat (Analytics)

What it does: Historical playback statistics, per-user analytics, library growth tracking.

Connects to: Jellyfin API (playback history).

Port: 3000


The Complete Docker Compose

services:
  # === REVERSE PROXY ===
  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    restart: unless-stopped

  # === MEDIA SERVER ===
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    network_mode: host
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /mnt/media:/media:ro
    devices:
      - /dev/dri:/dev/dri
    restart: unless-stopped

  # === REQUESTS ===
  seerr:
    image: ghcr.io/seerr-team/seerr:latest
    container_name: seerr
    init: true
    environment:
      - TZ=Europe/Paris
    volumes:
      - ./seerr/config:/app/config
    ports:
      - 5055:5055
    restart: unless-stopped

  # === INDEXER MANAGEMENT ===
  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Paris
    volumes:
      - ./prowlarr/config:/config
    ports:
      - 9696:9696
    restart: unless-stopped

  # === MOVIE AUTOMATION ===
  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Paris
    volumes:
      - ./radarr/config:/config
      - /mnt/media/movies:/movies
      - /mnt/downloads:/downloads
    ports:
      - 7878:7878
    restart: unless-stopped

  # === TV AUTOMATION ===
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Paris
    volumes:
      - ./sonarr/config:/config
      - /mnt/media/tv:/tv
      - /mnt/downloads:/downloads
    ports:
      - 8989:8989
    restart: unless-stopped

  # === DOWNLOAD CLIENT ===
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Paris
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent/config:/config
      - /mnt/downloads:/downloads
    ports:
      - 8080:8080
      - 6881:6881
      - 6881:6881/udp
    restart: unless-stopped

  # === SUBTITLES ===
  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Paris
    volumes:
      - ./bazarr/config:/config
      - /mnt/media/movies:/movies
      - /mnt/media/tv:/tv
    ports:
      - 6767:6767
    restart: unless-stopped

  # === LIBRARY TRANSCODING (OPTIONAL) ===
  tdarr:
    image: ghcr.io/haveagitgat/tdarr:latest
    container_name: tdarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Paris
      - serverIP=0.0.0.0
      - serverPort=8266
      - webUIPort=8265
      - internalNode=true
      - inContainer=true
      - nodeName=InternalNode
    volumes:
      - ./tdarr/server:/app/server
      - ./tdarr/configs:/app/configs
      - ./tdarr/logs:/app/logs
      - /mnt/media:/media
      - /tmp/tdarr:/temp
    ports:
      - 8265:8265
      - 8266:8266
    devices:
      - /dev/dri:/dev/dri
    restart: unless-stopped

  # === MONITORING ===
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    volumes:
      - ./uptime-kuma/data:/app/data
    ports:
      - 3001:3001
    restart: unless-stopped

volumes:
  caddy_data:

The Connection Map (Setup Order)

  1. Prowlarr > Indexers - Add your torrent indexers
  2. Prowlarr > Radarr + Sonarr - Sync indexers to both apps
  3. Radarr + Sonarr > qBittorrent - Connect download client
  4. Radarr + Sonarr > Jellyfin - Trigger library scans on import
  5. Bazarr > Radarr + Sonarr - Read libraries for subtitle matching
  6. Seerr > Jellyfin + Radarr + Sonarr - Setup wizard connects all three
  7. JellyWatch > All services - Add each with URL and API key

The Data Flow: A Complete Example

  1. User opens Seerr on their phone, searches "Dune Part Two"
  2. Seerr queries TMDB, shows the movie with poster and description
  3. User taps Request. Seerr checks Jellyfin: not in library. Sends request to Radarr.
  4. Radarr receives the request, adds the movie as monitored
  5. Radarr searches via Prowlarr. Prowlarr queries all configured indexers simultaneously.
  6. Prowlarr returns results ranked by quality profile scores
  7. Radarr selects the best release based on custom format scoring
  8. Radarr sends the .torrent to qBittorrent with category "radarr"
  9. qBittorrent downloads the file to /downloads/incomplete, then moves to /downloads/complete
  10. Radarr detects completion, renames the file, hardlinks to /movies/
  11. Radarr notifies Jellyfin to scan the Movies library
  12. Bazarr detects the new movie, downloads English and French .srt files
  13. Tdarr detects the new file (if configured), checks codec, skips if already optimal
  14. Jellyfin scans, fetches TMDB metadata, downloads poster
  15. Seerr updates request status to "Available"
  16. JellyWatch sends a push notification: "Dune Part Two is now available"
  17. User opens Jellyfin on their TV, finds Dune Part Two
  18. Jellyfin serves the file via Direct Play
  19. JellyWatch shows the active session with codec details

Total time from request to watching: 10-30 minutes.


Storage Architecture

/mnt/
  downloads/
    incomplete/     <- qBittorrent active downloads
    complete/
      radarr/       <- Completed movie downloads (seeding)
      sonarr/       <- Completed TV downloads (seeding)
  media/
    movies/         <- Radarr imports here (Jellyfin reads)
    tv/             <- Sonarr imports here (Jellyfin reads)
    music/          <- Manual or Lidarr
    audiobooks/     <- Manual

Critical: /mnt/downloads and /mnt/media must be on the same filesystem for hardlinks to work.


Hardware Requirements by Stack Size

Stack sizeCPURAMStorage (OS+Apps)Storage (Media)
Solo userIntel N100 (4 cores)8 GB120 GB NVMe2-4 TB HDD
Family (2-5 users)Intel N100/N30516 GB256 GB NVMe4-12 TB HDD
Shared server (5-15 users)Intel i5 12th gen+32 GB512 GB NVMe12-40 TB
Community (15+ users)Intel i7/Xeon + dGPU64 GB1 TB NVMe40+ TB

Common Mistakes

MistakeConsequenceFix
Different filesystems for downloads and mediaHardlinks fail, storage doublesUse same mount point
Wrong PUID/PGID across containersPermission denied on importSet same UID/GID everywhere
No Prowlarr (indexers in each app)Configuration driftCentralize in Prowlarr
No Jellyfin notification in Radarr/SonarrLibrary does not update after importAdd Connect > Jellyfin
Tdarr running during peak hoursCompetes with active streamsSchedule off-peak
No monitoringProblems discovered by users complainingDeploy JellyWatch + Uptime Kuma

FAQ

Do I need ALL of these tools? No. The minimum viable stack is: Jellyfin + Radarr + Sonarr + Prowlarr + qBittorrent. Everything else is optional enhancement.

How much does this cost? All software is free. Hardware cost: $130 (Intel N100 mini PC) + storage drives. Electricity: ~$15/year.

Can I add tools incrementally? Yes. Start with Jellyfin alone. Add Radarr/Sonarr when you want automation. Add Seerr when you share with others. Add monitoring when you want visibility.

Does this work on a NAS? Yes. Synology, TrueNAS, and Unraid all support Docker and can run this entire stack.


Your stack is deployed. Monitor it all from one app. Download JellyWatch on Google Play - Jellyfin sessions, Radarr/Sonarr queues, Seerr requests, and server health in your pocket.

On Emby? Download EmbyWatch on Google Play

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment

Never displayed publicly.
0 / 2000 · Supports limited Markdown: **bold**, *italic*, `code`, [link](url), lists, > quote.