commit b3adf16cb7049e94f3708cbf6d25384517640517 Author: Jeremy Janella Date: Wed Jun 3 14:21:11 2026 -0400 compose file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..697b569 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +config diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..1412c95 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,253 @@ +--- +# ============================================================ +# 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