version: '3.8' # Media Stack - Unified Media Management & Streaming # Services: Jellyfin, Sonarr, Radarr, Prowlarr, SABnzbd # Ports: 8096 (Jellyfin), 8989 (Sonarr), 7878 (Radarr), 9696 (Prowlarr), 8880 (SABnzbd) # GPU: YES (Jellyfin only) - Requires NVIDIA Container Toolkit # Storage: SSD (configs), HDD (media, downloads) # Database: PostgreSQL (postgres-shared) for Sonarr, Radarr, Prowlarr services: # ============================================================ # JELLYFIN - Media Server with GPU Transcoding # ============================================================ jellyfin: image: jellyfin/jellyfin:latest container_name: jellyfin user: 1000:1000 restart: unless-stopped ports: - "8096:8096" # HTTP web interface - "8920:8920" # HTTPS web interface - "7359:7359/udp" # Auto-discovery - "1900:1900/udp" # DLNA volumes: # Config and cache on SSD (performance-critical) - /home/jpmschweitzer/docker-data/jellyfin/config:/config - /home/jpmschweitzer/docker-data/jellyfin/cache:/cache # Media files on HDD (read-only for safety) - /mnt/media/jellyfin/movies:/media/movies:ro - /mnt/media/jellyfin/series:/media/series:ro environment: - NVIDIA_VISIBLE_DEVICES=all - NVIDIA_DRIVER_CAPABILITIES=all - TZ=Europe/Amsterdam healthcheck: test: ["CMD-SHELL", "curl -fSs http://localhost:8096/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 60s networks: - docker-dataplane # ============================================================ # SONARR - TV Show Management & Automation # ============================================================ sonarr: image: linuxserver/sonarr:latest container_name: sonarr restart: unless-stopped ports: - "8989:8989" volumes: # Config on SSD - /home/jpmschweitzer/docker-data/sonarr:/config # Media and downloads on HDD - /mnt/media/jellyfin/series:/tv - /mnt/media/downloads:/downloads environment: - PUID=1000 - PGID=1000 - TZ=Europe/Amsterdam # PostgreSQL (postgres-shared) - SONARR__POSTGRES__HOST=postgres-shared - SONARR__POSTGRES__PORT=5432 - SONARR__POSTGRES__USER=media_user - SONARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD} - SONARR__POSTGRES__MAINDB=sonarr healthcheck: test: ["CMD-SHELL", "curl -fSs http://localhost:8989/ping || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 60s depends_on: - sabnzbd - prowlarr networks: - docker-dataplane # ============================================================ # RADARR - Movie Management & Automation # ============================================================ radarr: image: linuxserver/radarr:latest container_name: radarr restart: unless-stopped ports: - "7878:7878" volumes: # Config on SSD - /home/jpmschweitzer/docker-data/radarr:/config # Media and downloads on HDD - /mnt/media/jellyfin/movies:/movies - /mnt/media/downloads:/downloads environment: - PUID=1000 - PGID=1000 - TZ=Europe/Amsterdam # PostgreSQL (postgres-shared) - RADARR__POSTGRES__HOST=postgres-shared - RADARR__POSTGRES__PORT=5432 - RADARR__POSTGRES__USER=media_user - RADARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD} - RADARR__POSTGRES__MAINDB=radarr healthcheck: test: ["CMD-SHELL", "curl -fSs http://localhost:7878/ping || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 60s depends_on: - sabnzbd - prowlarr networks: - docker-dataplane # ============================================================ # PROWLARR - Indexer Management # ============================================================ prowlarr: image: linuxserver/prowlarr:latest container_name: prowlarr restart: unless-stopped ports: - "9696:9696" volumes: # Config on SSD - /home/jpmschweitzer/docker-data/prowlarr:/config environment: - PUID=1000 - PGID=1000 - TZ=Europe/Amsterdam # PostgreSQL (postgres-shared) - PROWLARR__POSTGRES__HOST=postgres-shared - PROWLARR__POSTGRES__PORT=5432 - PROWLARR__POSTGRES__USER=media_user - PROWLARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD} - PROWLARR__POSTGRES__MAINDB=prowlarr healthcheck: test: ["CMD-SHELL", "curl -fSs http://localhost:9696/ping || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 60s networks: - docker-dataplane # ============================================================ # SABNZBD - Usenet Download Client # ============================================================ sabnzbd: image: linuxserver/sabnzbd:latest container_name: sabnzbd restart: unless-stopped ports: - "8880:8080" # Web UI (remapped to avoid Portainer conflict) volumes: # Config on SSD - /home/jpmschweitzer/docker-data/sabnzbd:/config # Downloads on HDD - /mnt/media/downloads:/downloads - /mnt/media/downloads/incomplete:/incomplete-downloads environment: - PUID=1000 - PGID=1000 - TZ=Europe/Amsterdam - HOST_WHITELIST=sabnzbd,localhost,192.168.86.149,tower-of-joy healthcheck: test: ["CMD-SHELL", "curl -fSs http://localhost:8080/api?mode=version || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 60s networks: - docker-dataplane networks: docker-dataplane: external: true name: docker-dataplane # ============================================================ # SETUP GUIDE # ============================================================ # # 0. Create PostgreSQL databases (run once): # docker exec -it postgres-shared psql -U postgres -c " # CREATE USER media_user WITH PASSWORD ''; # CREATE DATABASE sonarr OWNER media_user; # CREATE DATABASE radarr OWNER media_user; # CREATE DATABASE prowlarr OWNER media_user; # GRANT ALL PRIVILEGES ON DATABASE sonarr TO media_user; # GRANT ALL PRIVILEGES ON DATABASE radarr TO media_user; # GRANT ALL PRIVILEGES ON DATABASE prowlarr TO media_user; # " # # 1. Create download directories: # mkdir -p /mnt/media/downloads/complete /mnt/media/downloads/incomplete /mnt/media/downloads/usenet # chown -R 1000:1000 /mnt/media/downloads # # 2. Create config directories: # mkdir -p ~/docker-data/sonarr ~/docker-data/radarr ~/docker-data/prowlarr ~/docker-data/sabnzbd # chown -R 1000:1000 ~/docker-data/sonarr ~/docker-data/radarr ~/docker-data/prowlarr ~/docker-data/sabnzbd # # 3. Set MEDIA_DB_PASSWORD environment variable in Portainer stack or export before deploy # # 4. Deploy the stack via Portainer or: # docker stack deploy -c media.yml media # # 5. Configure services in order: # # a) SABnzbd (http://localhost:8880): # - Complete setup wizard # - Add Usenet server credentials # - Configure download categories: tv, movies # - Note the API key for Sonarr/Radarr # # b) Prowlarr (http://localhost:9696): # - Add indexers (NZBgeek, DrunkenSlug, etc.) # - Add Sonarr as application: http://sonarr:8989 # - Add Radarr as application: http://radarr:7878 # - Sync indexers to apps # # c) Sonarr (http://localhost:8989): # - Settings → Media Management → Root Folder: /tv # - Settings → Download Clients → Add SABnzbd: # Host: sabnzbd, Port: 8080, API Key: # - Indexers will be synced from Prowlarr # # d) Radarr (http://localhost:7878): # - Settings → Media Management → Root Folder: /movies # - Settings → Download Clients → Add SABnzbd: # Host: sabnzbd, Port: 8080, API Key: # - Indexers will be synced from Prowlarr # # e) Jellyfin (http://localhost:8096): # - Already configured (migrated from previous stack) # - Libraries should auto-detect new content # # 5. Inter-service communication (use container names): # - SABnzbd from Sonarr/Radarr: http://sabnzbd:8080 # - Prowlarr from Sonarr: http://prowlarr:9696 # - All services on docker-dataplane network # # STORAGE LAYOUT: # /mnt/media/ # ├── downloads/ # │ ├── complete/ # Completed downloads # │ ├── incomplete/ # In-progress downloads # │ └── usenet/ # Usenet-specific # └── jellyfin/ # ├── movies/ # Radarr imports here # └── series/ # Sonarr imports here