--- # ============================================================ # Complete *arr Stack — Jellyfin / Jellyseerr + Mullvad VPN # Services: Mullvad VPN · Prowlarr · Radarr · Sonarr · Lidarr # ============================================================ # # REQUIREMENTS # ───────────── # 1. Copy .env.example → .env and fill in your values. # 2. Create the host directories listed under volumes or set # MEDIA_ROOT / CONFIG_ROOT in .env to existing paths. # 3. Your Mullvad account must have WireGuard enabled. # Generate credentials at: https://mullvad.net/en/account/wireguard-config # # DIRECTORY LAYOUT (all under MEDIA_ROOT) # ──────────────────────────────────────── # MEDIA_ROOT/ # downloads/ ← qBittorrent saves here # movies/ ← Radarr root folder # tv/ ← Sonarr root folder # music/ ← Lidarr root folder # books/ ← (optional) # # Inside containers everything is mounted at /data so # hardlinks work across downloads → media folders. # Update each app's settings to use /data/... paths. # # PORTS (host → container, all on localhost by default) # ──────────────────────────────────────────────────── # Jellyfin → 8096 # Jellyseerr → 5055 # Prowlarr → 9696 # Radarr → 7878 # Sonarr → 8989 # Lidarr → 8686 # qBittorrent → 8080 (WebUI), 6881 (torrent, routed via VPN) # # ============================================================ services: # ────────────────────────────────────────────────────────── # MULLVAD VPN (WireGuard via gluetun) # All *arr + qBittorrent traffic is routed through this # ────────────────────────────────────────────────────────── vpn: image: qmcgaw/gluetun:latest container_name: mullvad_vpn cap_add: - NET_ADMIN devices: - /dev/net/tun:/dev/net/tun environment: - VPN_SERVICE_PROVIDER=mullvad - VPN_TYPE=wireguard - WIREGUARD_PRIVATE_KEY=${MULLVAD_PRIVATE_KEY} - WIREGUARD_ADDRESSES=${MULLVAD_ADDRESSES} # e.g. 10.64.0.1/32 - SERVER_COUNTRIES=${MULLVAD_COUNTRIES:-Netherlands} - FIREWALL_OUTBOUND_SUBNETS=192.168.0.0/16,10.0.0.0/8 # LAN access - UPDATER_PERIOD=24h - TZ=${TZ:-America/Toronto} ports: # qBittorrent WebUI - "8080:8080" # qBittorrent torrenting - "6881:6881" - "6881:6881/udp" # Prowlarr - "9696:9696" # Radarr - "7878:7878" # Sonarr - "8989:8989" # Lidarr - "8686:8686" # FlareSolverr - "8191:8191" # Jellyseerr - "5055:5055" volumes: - ${CONFIG_ROOT:-./config}/gluetun:/gluetun networks: - arr_net restart: unless-stopped healthcheck: test: ["CMD", "/gluetun-entrypoint", "healthcheck"] interval: 30s timeout: 10s retries: 3 # ────────────────────────────────────────────────────────── # qBITTORRENT (network_mode: service:vpn) # ────────────────────────────────────────────────────────── qbittorrent: image: lscr.io/linuxserver/qbittorrent:latest container_name: qbittorrent network_mode: service:vpn depends_on: vpn: condition: service_healthy environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - TZ=${TZ:-America/Toronto} - WEBUI_PORT=8080 - TORRENTING_PORT=6881 volumes: - ${CONFIG_ROOT}/qbittorrent:/config # Single /data mount so hardlinks work - ${MEDIA_ROOT}:/data restart: unless-stopped # ────────────────────────────────────────────────────────── # PROWLARR — Indexer manager / proxy # ────────────────────────────────────────────────────────── prowlarr: image: lscr.io/linuxserver/prowlarr:latest container_name: prowlarr network_mode: service:vpn depends_on: vpn: condition: service_healthy environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - TZ=${TZ:-America/Toronto} volumes: - ${CONFIG_ROOT:-./config}/prowlarr:/config restart: unless-stopped # ────────────────────────────────────────────────────────── # FLARESOLVERR — Cloudflare bypass for indexers # ────────────────────────────────────────────────────────── flaresolverr: image: ghcr.io/flaresolverr/flaresolverr:latest container_name: flaresolverr network_mode: service:vpn depends_on: vpn: condition: service_healthy environment: - LOG_LEVEL=info - TZ=${TZ:-America/Toronto} restart: unless-stopped # ────────────────────────────────────────────────────────── # RADARR — Movies # ────────────────────────────────────────────────────────── radarr: image: lscr.io/linuxserver/radarr:latest container_name: radarr network_mode: service:vpn depends_on: vpn: condition: service_healthy environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - TZ=${TZ:-America/Toronto} volumes: - ${CONFIG_ROOT}/radarr:/config # Single /data mount so hardlinks work - ${MEDIA_ROOT}:/data restart: unless-stopped # ────────────────────────────────────────────────────────── # SONARR — TV Shows # ────────────────────────────────────────────────────────── sonarr: image: lscr.io/linuxserver/sonarr:latest container_name: sonarr network_mode: service:vpn depends_on: vpn: condition: service_healthy environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - TZ=${TZ:-America/Toronto} volumes: - ${CONFIG_ROOT}/sonarr:/config # Single /data mount so hardlinks work - ${MEDIA_ROOT}:/data restart: unless-stopped # ────────────────────────────────────────────────────────── # LIDARR — Music # ────────────────────────────────────────────────────────── lidarr: image: lscr.io/linuxserver/lidarr:latest container_name: lidarr network_mode: service:vpn depends_on: vpn: condition: service_healthy environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - TZ=${TZ:-America/Toronto} volumes: - ${CONFIG_ROOT}/lidarr:/config # Single /data mount so hardlinks work - ${MEDIA_ROOT}:/data restart: unless-stopped # ────────────────────────────────────────────────────────── # JELLYFIN — Media server # (NOT routed through VPN — direct access needed) # ────────────────────────────────────────────────────────── jellyfin: image: lscr.io/linuxserver/jellyfin:latest container_name: jellyfin environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - TZ=${TZ:-America/Toronto} - JELLYFIN_PublishedServerUrl=${JELLYFIN_URL:-http://localhost:8096} volumes: - ${CONFIG_ROOT:-./config}/jellyfin:/config - ${MEDIA_ROOT}:/data # Optional: hardware transcoding (uncomment one below) # - /dev/dri:/dev/dri # Intel/AMD VAAPI # devices: # - /dev/dri:/dev/dri # uncomment for HW transcoding ports: - "8096:8096" # HTTP - "8920:8920" # HTTPS (optional) - "7359:7359/udp" # Local network discovery - "1900:1900/udp" # DLNA (optional) networks: - arr_net restart: unless-stopped # ────────────────────────────────────────────────────────── # JELLYSEERR — Request management for Jellyfin # ────────────────────────────────────────────────────────── jellyseerr: image: fallenbagel/jellyseerr:latest network_mode: service:vpn container_name: jellyseerr depends_on: jellyfin: condition: service_started environment: - LOG_LEVEL=info - TZ=${TZ:-America/Toronto} volumes: - ${CONFIG_ROOT:-./config}/jellyseerr:/app/config restart: unless-stopped networks: arr_net: driver: bridge