Streaming is Broken VI: The Complete Stack

Enough stalling, right?

Jokes aside, I consider the previous posts in this series extremely important. Each one covers a critical piece:

  • Streaming is Broken II: Choosing Hardware. Where we learned about a low-cost homeserver setup with my two cents on storage.
  • Streaming is Broken III: The Foundation (OS, Docker, and “Day One”). Where we configured our OS, Docker, and prepared the groundwork for what would come next.
  • Streaming is Broken IV: The Fortress: VPN and Gluetun. A chapter you normally won’t find in other tutorials, focused on security and privacy.
  • Streaming is Broken V: Hardlinks and Permissions. Tips to avoid future headaches in our configurations.

But if I had to choose just one post to explain how to configure a homeserver, it would be this one.

Without further delay, let’s begin.

The Multimedia Stack

qBittorrent

As I said in previous posts, piracy is the worst crime in the world, I love companies that offer crappy services, and I enjoy paying more for less. Of course, for legal reasons, I say this.

Qbittorrent

Now, speaking seriously, the flow of our home media server will be as follows:

  • We will search for movies and series in Overseerr (everything will be explained further below)
  • Overseerr will send requests to Radarr (Movies) and Sonarr (Series)
  • Radarr and Sonarr will search for the material on sites indexed by Prowlarr, whose sole function will be to manage these indexers
  • qBittorrent will download the file. Radarr and Sonarr will automatically identify the file and move the download to their own folders
  • Jellyfin will be our streaming app on the TV, iPad, computer, etc. It’s where we’ll watch the acquired content

With that in mind, let’s start with the first item to be configured on our list: the downloader.

In our docker compose, we’ll have something like this:

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - WEBUI_PORT=8080
    volumes:
      - /home/donkey/EmuleVision/Settings/Qbittorrent:/config
      - /home/donkey/EmuleVision:/data
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

Here, as I explained in my security post, qBittorrent will be protected via VPN, and Gluetun will be the one protecting our container. In my case, connected to a paid VPN service (it has integrations with NordVPN, Surfshark, etc.). Since we’re talking about downloading multimedia files, forget free VPNs: besides being limited for obvious reasons, they’re extremely insecure.

It’s important to make clear that to avoid IP leaks, qBittorrent will absolutely depend on Gluetun being functional. If the VPN drops, qBittorrent will stop automatically. This is the beauty of the depends_on with condition: service_healthy. The container won’t even start if Gluetun isn’t ready.

In this case and moving forward, we’ll be configuring permissions and timezone (PUID, PGID, and TZ) in the same way. For this, create a .env file for these parameters.

Dummy example:

PUID=1000
PGID=1000
TZ=Etc/UTC

To configure the application itself, I won’t reinvent the wheel. I’ll be linking the corresponding TRaSH Guides tutorial for each technology:

  • Basic-Setup - Basic setup of the most common settings.
  • Paths - Where to set your root download location.
  • How to add Categories - How to set up categories for qBittorrent, and how you can manage and organize your torrents in groups. Starr apps can use categories in qBittorrent to keep track of downloads to monitor, rather than watching every torrent in your client.
  • Port forwarding - Where in your download client you should add the port you’ve port forwarded with your VPN Service.

Prowlarr: The Indexer Manager

Prowlarr is the unsung hero of this stack. It’s the middleman that connects your Radarr and Sonarr to the torrent indexers. Think of it as a switchboard operator from the 1950s, except instead of connecting phone calls, it’s connecting your download requests to torrent sites.

  prowlarr:
    image: ghcr.io/hotio/prowlarr:latest
    container_name: prowlarr
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/Prowlarr:/config
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

Prowlarr

Notice the pattern here? It’s also behind Gluetun. Everything that touches the public internet for “alternative acquisition methods” stays behind the VPN. No exceptions.

Prowlarr configuration is straightforward:

  1. Add your indexers (public or private trackers)
  2. Connect it to Radarr and Sonarr via API keys
  3. Let it sync automatically

The beauty is that you configure indexers once in Prowlarr, and they propagate to all your Arr apps. No more copying and pasting tracker URLs into five different interfaces.

For detailed configuration, check TRaSH Guides - Prowlarr.

FlareSolverr: The Cloudflare Bypass

Some indexers protect themselves with Cloudflare’s anti-bot measures. FlareSolverr is a headless browser that solves these challenges automatically.

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    network_mode: "service:gluetun"
    environment:
      - TZ=${TZ}
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

You configure it once in Prowlarr, and it handles the rest. When Prowlarr hits a Cloudflare-protected site, FlareSolverr steps in, solves the challenge, and returns the results. It’s like having a robot lawyer who specializes in getting past bouncers. Guide is here

Sonarr: The TV Series Manager

Sonarr is where the magic happens for TV shows. It monitors your “watchlist,” searches Prowlarr for new episodes, sends them to qBittorrent, and organizes everything automatically.

  sonarr:
    image: ghcr.io/linuxserver/sonarr:latest
    container_name: sonarr
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/Sonarr:/config
      - /home/donkey/EmuleVision:/data
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

Sonarr

Key configuration points:

  • Root Folders: Set to /data/media/tv
  • Download Client: Point to qBittorrent at localhost:8080 (remember, they share the same network stack via Gluetun)
  • Quality Profiles: Use TRaSH Guides Quality Settings to avoid downloading garbage quality releases

Sonarr will automatically:

  • Search for new episodes when they air
  • Upgrade existing episodes if better quality versions appear
  • Rename files according to your preferred naming scheme
  • Move completed downloads to your media library using Hardlinks (remember that post?)

Radarr: The Movie Manager

Radarr is Sonarr’s sibling, but for movies. Same logic, different content type.

  radarr:
    image: ghcr.io/linuxserver/radarr:latest
    container_name: radarr
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/Radarr:/config
      - /home/donkey/EmuleVision:/data
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

Radarr

Configuration mirrors Sonarr:

  • Root Folder: /data/media/movies
  • Download Client: qBittorrent at localhost:8080
  • Quality Profiles: TRaSH Guides for Radarr

The difference is the content type. Radarr tracks individual movies instead of series/seasons/episodes. It also integrates beautifully with services like Trakt or IMDb lists if you want to automate adding movies to your collection.

Bazarr: The Subtitle Specialist

Remember when I complained about Netflix’s garbage subtitle quality in the first post? Bazarr solves that.

  bazarr:
    image: ghcr.io/linuxserver/bazarr:latest
    container_name: bazarr
    restart: unless-stopped
    network_mode: "service:gluetun"
    environment:
      - TZ=${TZ}
      - PUID=${PUID}
      - PGID=${PGID}
    volumes:
      - /home/donkey/EmuleVision/media/movies:/movies
      - /home/donkey/EmuleVision/media/tv:/tv
      - /home/donkey/EmuleVision/Settings/Bazarr:/config
    depends_on:
      gluetun:
        condition: service_healthy

Bazarr

Bazarr automatically:

  • Downloads subtitles for your movies and series
  • Supports multiple languages
  • Syncs with OpenSubtitles, Subscene, and other providers
  • Can even upgrade existing subtitles if better versions appear

Configuration is simple: connect it to Sonarr and Radarr via API keys, select your preferred languages and providers, and let it run. Your Polish thriller will finally have comprehensible English subtitles.

Kaizoku: The Manga Downloader

For those who consume more than just movies and series, Kaizoku handles manga acquisition.

  kaizoku:
    image: ghcr.io/oae/kaizoku:latest
    container_name: kaizoku
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - KAIZOKU_PORT=3002
    volumes:
      - /home/donkey/EmuleVision/Settings/Kaizoku:/config
      - /home/donkey/EmuleVision:/data
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

It tracks manga series, automatically downloads new chapters, and organizes them for your reading pleasure. Think of it as Sonarr, but for comics.

Overseerr: The Request Manager

This is the user-facing component. Overseerr is what your family members interact with when they want to watch something.

  overseerr:
    image: lscr.io/linuxserver/overseerr:latest
    container_name: overseerr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    ports:
      - "5055:5055"
    volumes:
      - /home/donkey/EmuleVision/Settings/Overseerr:/config
    restart: unless-stopped

Overserr

Notice something different? No Gluetun. Overseerr doesn’t download anything. It’s just a request interface. Your wife wants to watch Gossip Girl? She opens Overseerr, searches for it, clicks “Request,” and Sonarr handles the rest.

Plex: The Streaming Frontend

This is where everything comes together. Plex is your personal streaming service.

  plex:
    image: lscr.io/linuxserver/plex:latest
    container_name: plex
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - VERSION=docker
      - PLEX_CLAIM=claim-6DcZ6Chj7tSXYU4n6GS7
    network_mode: host
    volumes:
      - /home/donkey/EmuleVision/Settings/Plex:/config
      - /home/donkey/EmuleVision/media/movies:/data/movies
      - /home/donkey/EmuleVision/media/tv:/data/tvshows
    devices:
      - /dev/dri:/dev/dri
    restart: unless-stopped

Plex

Critical details:

  1. network_mode: host - Unlike the other containers, Plex needs direct network access for DLNA discovery and maximum streaming performance. Remember the architecture diagram from the VPN post? This is the “Clean Group.”

  2. devices: /dev/dri - This exposes the Intel QuickSync hardware encoder to the container. Without this, transcoding would murder your CPU. With it, your N100’s GPU handles 4K transcoding while the CPU naps.

  3. PLEX_CLAIM - This token links your Plex server to your Plex account on first boot. You get it from plex.tv/claim. It expires after 4 minutes, so generate it right before your first docker compose up.

  4. No Gluetun dependency - Plex streams locally. Passing 80Mbps 4K video through a VPN tunnel is insane. This traffic stays on your LAN, fast and clean.

Configuration essentials:

  • Enable Hardware Acceleration (Intel QuickSync) in Settings → Transcoder
  • Set up your libraries (Movies pointing to /data/movies, TV Shows pointing to /data/tvshows)
  • Configure remote access (Plex handles this automatically, but you can disable it for LAN-only)
  • Install the Plex app on your TV, phone, tablet

The beauty of Plex over open-source alternatives is the polish. The apps are native, fast, and work flawlessly across every device imaginable. Your wife won’t need a tutorial to watch Gossip Girl on her iPad.

For detailed setup, check the official Plex documentation.

The Support Stack

Portainer: The Docker Dashboard

Managing containers via CLI is fine when you’re setting things up. But when something breaks at 2 AM and you need to quickly restart a service, a web interface is invaluable.

  portainer:
    image: portainer/portainer-ce:2.20.2
    container_name: portainer
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock
      - /home/donkey/EmuleVision/Settings/Portainer:/data
    ports:
      - "9000:9000"
    restart: unless-stopped

Portainer gives you:

  • Container status at a glance
  • One-click restart/stop/remove
  • Log viewing without SSHing in
  • Resource usage monitoring

Access it at http://your-server-ip:9000.

Homepage: The Unified Dashboard

Homepage is your mission control. It aggregates all your services into a single, beautiful interface.

  homepage:
    image: ghcr.io/gethomepage/homepage:latest
    container_name: homepage
    volumes:
      - /home/donkey/EmuleVision/Settings/Homepage:/app/config
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - HOMEPAGE_ALLOWED_HOSTS=*
    ports:
      - "3000:3000"
    restart: unless-stopped

Homepage

You configure it with YAML files to display:

  • Service status
  • Quick links
  • API integrations (Sonarr download queue, Jellyfin recent additions)
  • System stats

This becomes your bookmarked homepage. No more memorizing which service is on which port.

Scrutiny: The Drive Health Monitor

Remember that 512GB SSD we’re relying on? We need to monitor its health.

  scrutiny:
    image: ghcr.io/analogj/scrutiny:master-omnibus
    container_name: scrutiny
    cap_add:
      - SYS_RAWIO
    devices:
      - /dev/sda:/dev/sda
    volumes:
      - /run/udev:/run/udev:ro
      - /home/donkey/EmuleVision/Settings/Scrutiny:/opt/scrutiny/config
      - /home/donkey/EmuleVision/Settings/Scrutiny/influxdb:/opt/scrutiny/influxdb
    ports:
      - "8086:8080"
    restart: unless-stopped

Scrutiny

Scrutiny reads S.M.A.R.T. data from your drive and alerts you if it detects degradation. SSDs don’t fail gradually; they fail suddenly. This gives you warning before catastrophic data loss.

FileBrowser: The Web File Manager

Sometimes you need to access files directly without SSHing in or mounting a Samba share.

  filebrowser:
    image: filebrowser/filebrowser:latest
    container_name: filebrowser
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
    volumes:
      - /home/donkey/EmuleVision/Settings/Filebrowser/filebrowser.db:/database.db
      - /home/donkey/EmuleVision/Settings/Filebrowser/.filebrowser.json:/.filebrowser.json
      - /home/donkey/EmuleVision:/srv
    ports:
      - "8082:80"
    restart: unless-stopped

FileBrowser provides:

  • Web-based file management
  • Upload/download capabilities
  • User permission controls
  • File sharing links

Useful for those rare times when you need to manually add a file or check something without terminal access.

Kavita and Calibre-Web: The Reading Stack

For the book enthusiasts:

  kavita:
    image: jvmilazz0/kavita:latest
    container_name: kavita
    environment:
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/Kavita:/kavita/config
      - /home/donkey/EmuleVision/media/manga:/manga
      - /home/donkey/EmuleVision/media/comics:/comics
    ports:
      - "5000:5000"
    restart: unless-stopped

  calibre-web:
    image: lscr.io/linuxserver/calibre-web:latest
    container_name: calibre-web
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/CalibreWeb:/config
      - /home/donkey/EmuleVision/books:/books
    ports:
      - "8083:8083"
    restart: unless-stopped
  • Kavita: For manga and comics (CBZ/CBR files)
  • Calibre-Web: For ebooks (EPUB, MOBI, PDF)

Calibre

Both provide web-based reading interfaces with progress tracking, metadata management, and mobile app support.

The Complete Docker Compose

Here’s how it all fits together. Notice the network segregation between the VPN group (Gluetun + downloaders) and the clean group (Jellyfin, Overseerr, etc.):

version: '3'

services:
  gluetun:
    image: qmcgaw/gluetun
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    ports:
      - "8080:8080" # Qbittorrent
      - "9696:9696" # Prowlarr
      - "8191:8191" # Flaresolverr
      - "8989:8989" # Sonarr
      - "7878:7878" # Radarr
      - "3002:3002" # Kaizoku
      - "6881:6881" # Torrent Port
      - "6881:6881/udp"
      - "6767:6767" # Bazarr
      - "8090:8090"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - VPN_SERVICE_PROVIDER=custom
      - VPN_TYPE=openvpn
      - OPENVPN_USER=${VPN_USER}
      - OPENVPN_PASSWORD=${VPN_PASS}
      - OPENVPN_CUSTOM_CONFIG=/gluetun/custom.conf
      - FIREWALL_VPN_INPUT_PORTS=6881
      - BLOCK_MALICIOUS=no
      - BLOCK_ADS=no
      - BLOCK_SURVEILLANCE=no
      - FIREWALL_INPUT_PORTS=8080,9696,8191,8989,7878,3002,6767,8090
    volumes:
      - ./Settings/Gluetun:/gluetun
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "ping -c 1 1.1.1.1 || ping -c 1 8.8.8.8 || ping -c 1 9.9.9.9 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 20s

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - WEBUI_PORT=8080
    volumes:
      - /home/donkey/EmuleVision/Settings/Qbittorrent:/config
      - /home/donkey/EmuleVision:/data
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

  prowlarr:
    image: ghcr.io/hotio/prowlarr:latest
    container_name: prowlarr
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/Prowlarr:/config
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    network_mode: "service:gluetun"
    environment:
      - TZ=${TZ}
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

  sonarr:
    image: ghcr.io/linuxserver/sonarr:latest
    container_name: sonarr
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/Sonarr:/config
      - /home/donkey/EmuleVision:/data
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

  radarr:
    image: ghcr.io/linuxserver/radarr:latest
    container_name: radarr
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/Radarr:/config
      - /home/donkey/EmuleVision:/data
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

  bazarr:
    image: ghcr.io/linuxserver/bazarr:latest
    container_name: bazarr
    restart: unless-stopped
    network_mode: "service:gluetun"
    environment:
      - TZ=${TZ}
      - PUID=${PUID}
      - PGID=${PGID}
    volumes:
      - /home/donkey/EmuleVision/media/movies:/movies
      - /home/donkey/EmuleVision/media/tv:/tv
      - /home/donkey/EmuleVision/Settings/Bazarr:/config
    depends_on:
      gluetun:
        condition: service_healthy

  kaizoku:
    image: ghcr.io/oae/kaizoku:latest
    container_name: kaizoku
    network_mode: "service:gluetun"
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - KAIZOKU_PORT=3002
    volumes:
      - /home/donkey/EmuleVision/Settings/Kaizoku:/config
      - /home/donkey/EmuleVision:/data
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

  overseerr:
    image: lscr.io/linuxserver/overseerr:latest
    container_name: overseerr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    ports:
      - "5055:5055"
    volumes:
      - /home/donkey/EmuleVision/Settings/Overseerr:/config
    restart: unless-stopped

  adguardhome:
    image: adguard/adguardhome
    container_name: adguardhome
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "3001:3000"
    volumes:
      - /home/donkey/EmuleVision/Settings/AdGuard/work:/opt/adguardhome/work
      - /home/donkey/EmuleVision/Settings/AdGuard/conf:/opt/adguardhome/conf
    restart: unless-stopped

  calibre-web:
    image: lscr.io/linuxserver/calibre-web:latest
    container_name: calibre-web
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/CalibreWeb:/config
      - /home/donkey/EmuleVision/books:/books
    ports:
      - "8083:8083"
    restart: unless-stopped

  kavita:
    image: jvmilazz0/kavita:latest
    container_name: kavita
    environment:
      - TZ=${TZ}
    volumes:
      - /home/donkey/EmuleVision/Settings/Kavita:/kavita/config
      - /home/donkey/EmuleVision/media/manga:/manga
      - /home/donkey/EmuleVision/media/comics:/comics
    ports:
      - "5000:5000"
    restart: unless-stopped

  filebrowser:
    image: filebrowser/filebrowser:latest
    container_name: filebrowser
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
    volumes:
      - /home/donkey/EmuleVision/Settings/Filebrowser/filebrowser.db:/database.db
      - /home/donkey/EmuleVision/Settings/Filebrowser/.filebrowser.json:/.filebrowser.json
      - /home/donkey/EmuleVision:/srv
    ports:
      - "8082:80"
    restart: unless-stopped

  portainer:
    image: portainer/portainer-ce:2.20.2
    container_name: portainer
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock
      - /home/donkey/EmuleVision/Settings/Portainer:/data
    ports:
      - "9000:9000"
    restart: unless-stopped

  homepage:
    image: ghcr.io/gethomepage/homepage:latest
    container_name: homepage
    volumes:
      - /home/donkey/EmuleVision/Settings/Homepage:/app/config
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - HOMEPAGE_ALLOWED_HOSTS=*
    ports:
      - "3000:3000"
    restart: unless-stopped

  scrutiny:
    image: ghcr.io/analogj/scrutiny:master-omnibus
    container_name: scrutiny
    cap_add:
      - SYS_RAWIO
    devices:
      - /dev/sda:/dev/sda
    volumes:
      - /run/udev:/run/udev:ro
      - /home/donkey/EmuleVision/Settings/Scrutiny:/opt/scrutiny/config
      - /home/donkey/EmuleVision/Settings/Scrutiny/influxdb:/opt/scrutiny/influxdb
    ports:
      - "8086:8080"
    restart: unless-stopped

  plex:
    image: lscr.io/linuxserver/plex:latest
    container_name: plex
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - VERSION=docker
      - PLEX_CLAIM=
    network_mode: host
    volumes:
      - /home/donkey/EmuleVision/Settings/Plex:/config
      - /home/donkey/EmuleVision/media/movies:/data/movies
      - /home/donkey/EmuleVision/media/tv:/data/tvshows
    devices:
      - /dev/dri:/dev/dri

First Boot Checklist

After running docker compose up -d, here’s your startup sequence:

  1. Wait for Gluetun - Check logs: docker logs gluetun

    • Look for “VPN connection successful”
    • Verify IP: docker exec gluetun curl ifconfig.me
  2. Configure Prowlarr (port 9696)

    • Add indexers
    • Connect to Sonarr/Radarr via API keys
    • Test FlareSolverr if using protected indexers
  3. Configure qBittorrent (port 8080)

    • Change default password
    • Set download path: /data/downloads/torrents
    • Add categories: tv, movies
    • Follow TRaSH Guides
  4. Configure Sonarr (port 8989)

    • Add Prowlarr as indexer
    • Add qBittorrent as download client (localhost:8080)
    • Set root folder: /data/media/tv
    • Import quality profiles from TRaSH Guides
  5. Configure Radarr (port 7878)

    • Same as Sonarr, but root folder: /data/media/movies
  6. Configure Bazarr (port 6767)

    • Connect to Sonarr/Radarr
    • Add subtitle providers (OpenSubtitles, Subscene)
    • Set preferred languages
  7. Configure Jellyfin (port 8096)

    • Add libraries: Movies (/data/movies), TV Shows (/data/tvshows)
    • Enable Hardware Acceleration (Intel QuickSync)
    • Create user accounts
  8. Configure Overseerr (port 5055)

    • Connect to Jellyfin
    • Connect to Sonarr/Radarr
    • Enable user requests

Performance Optimization Tips

Storage Management

Since we’re working with a 512GB SSD, space management is critical:

  1. Enable automatic deletion in Sonarr/Radarr after X days
  2. Set quality limits - Don’t download 80GB remuxes unless you actually need them
  3. Monitor disk usage via Homepage or Scrutiny
  4. Regular cleanup - Remove watched content you won’t rewatch

Network Optimization

  1. Port forwarding - Forward port 6881 on your VPN to maximize download speeds
  2. 5GHz WiFi for TV Box - As discussed in the hardware post, WiFi AC > 100Mbps Ethernet for 4K streaming
  3. Direct Play over Transcoding - Configure Jellyfin clients to support your file formats natively

Security Hardening

  1. Change all default passwords
  2. Disable remote access unless absolutely needed
  3. Keep Gluetun’s VPN credentials in .env - Never hardcode them
  4. Regular updates - Run docker compose pull && docker compose up -d monthly

Troubleshooting Common Issues

“qBittorrent won’t start”

  • Check Gluetun logs: docker logs gluetun
  • Verify VPN credentials in .env
  • Ensure healthcheck is passing: docker inspect gluetun | grep Health

“Sonarr/Radarr can’t reach qBittorrent”

  • They share Gluetun’s network, use localhost:8080
  • Don’t use container names or IPs
  • Check firewall rules in Gluetun config

“Jellyfin transcoding is slow”

  • Verify hardware acceleration is enabled
  • Check /dev/dri is properly mounted
  • Install intel-media-va-driver-non-free on host

“Subtitles aren’t downloading”

  • Check Bazarr logs for provider issues
  • Verify API keys are correct
  • Some providers rate-limit; wait and retry
  • All containers must map the same root volume (/data)
  • Downloads and media must be on the same filesystem
  • Check file ownership (PUID/PGID)

What’s Next?

We now have a fully functional home server that rivals any streaming service. But this is just the foundation. In future posts, I’ll cover:

  • Advanced Automation: Custom scripts for deletion, notifications, and maintenance
  • Remote Access: Securely exposing your server via Tailscale or Cloudflare Tunnels
  • 4K Optimization: HDR tone mapping, specific quality profiles, and bandwidth management
  • Backup Strategies: Protecting your configuration and media
  • Multi-User Management: Separate libraries, viewing restrictions, and bandwidth quotas
  • Integration with Trakt/IMDb: Automatic watchlist syncing
  • Advanced Jellyfin Customization: Plugins, themes, and client-specific settings

The beauty of this system is that it’s modular. You can add or remove services as needed. Don’t care about manga? Remove Kaizoku. Want to add music management? Throw in Lidarr. The architecture scales with your needs.

You’ve transformed a cheap mini PC into enterprise-grade infrastructure. You’ve taken control of your media consumption away from corporations that charge you more for less. And you’ve learned the fundamentals of containerization, networking, and system architecture along the way.

Victory!

The server is built. The stack is complete. Now go forth and acquire (legally, of course) the content you actually want to watch.

See you in the next post.