2. Docker Compose File
Sweet! So you want to install the arr stack on your self hosted device. Well, as with any app you need decide which app workflow deployment you want to use. Here, we focus on the docker compose deployment using a dedicated VM. Now, personally I like to run the arr stack in a dedicated VM and Jellyfin in another Docker VM. This gives you the possibility to move your Jellyfin instance around easily to different hosts and if something is wrong with your arr stack then your users can still enjoy the already downloaded media. Eventually, it is up to you and the amount of resources you have at your disposal what your decision will be. Hence, you can also run all of this in your existing docker vm.
1. Create a new docker vm by using the proxmox helper scripts ;) Or if you want to continue using your existing docker vm then you need
2.
I keep every tunable value in a .env file rather than hardcoding it in the Compose file. Ports, paths, and the user IDs all live in one place, which makes the Compose file readable and easy to reuse. I also run every service on an external network called media_network, which means the network is created once, outside of Compose, and the stack attaches to it. That keeps the network stable even when I tear the stack down and bring it back up.
Create a project directory and the .env file:
Hottio Decluttar
Whisparr is the Sonarr/Radarr equivalent for adult content
Whisper-AI generates subtitles on the fly for your media
compose.yml
services:
# Sonarr - TV Shows
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./sonarr/config:/config
- ${ROOT_MEDIA_PATH}:/data # Access to /data/downloads and /data/media/tv
ports:
- 8989:8989
restart: unless-stopped
networks:
- internal-proxy
# Radarr - Movies
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./radarr/config:/config
- ${ROOT_MEDIA_PATH}:/data # Access to /data/downloads and /data/media/movies
ports:
- 7878:7878
restart: unless-stopped
networks:
- internal-proxy
networks:
internal-proxy:
driver: bridge
# Prowlarr - Indexer centralized management
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./prowlarr/config:/config
ports:
- 9696:9696
restart: unless-stopped
networks:
- internal-proxy
# Profilarr & Decluttarr (Config only, no media access needed usually)
profilarr:
image: santiagosayshey/profilarr:latest
container_name: profilarr
environment:
- TZ=${TZ}
volumes:
- ./profilarr/config:/config
ports:
- 6868:6868
restart: unless-stopped
networks:
- internal-proxy
depends_on:
- sonarr
- radarr
# FlareSolverr - Bypasses Cloudflare protection for Indexers
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: flaresolverr
environment:
- LOG_LEVEL=${LOG_LEVEL:-info}
- TZ=${TZ}
ports:
- 8191:8191
restart: unless-stopped
networks:
- internal-proxy
# Gluetun - VPN Client
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add:
- NET_ADMIN
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
- VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
- VPN_TYPE=${VPN_TYPE}
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
- VPN_PORT_FORWARDING=${VPN_PORT_FORWARDING}
ports:
- 8888:8888 # Health check and control
- 6881:6881 # qBittorrent P2P port
- 6881:6881/udp
restart: unless-stopped
networks:
- internal-proxy
# If using Gluetun, use qbittorrent from linuxserver io, NOT the hotio image below!
qbittorrent:
image: lscr.io/linuxserver/qbittorrent
network_mode: "service:gluetun" # The "Magic" line
depends_on:
gluetun:
condition: service_healthy # Wait for VPN to be up first
# Lidarr - Music
lidarr:
image: lscr.io/linuxserver/lidarr:latest
container_name: lidarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./lidarr/config:/config
- ${ROOT_MEDIA_PATH}:/data
ports:
- 8686:8686
restart: unless-stopped
networks:
- internal-proxy
# Bazarr - Subtitles
bazarr:
image: lscr.io/linuxserver/bazarr:latest
container_name: bazarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./bazarr/config:/config
- ${ROOT_MEDIA_PATH}:/data
ports:
- 6767:6767
restart: unless-stopped
networks:
- internal-proxy
# whisper-ai - Local Subtitle Generation (Requires GPU)
whisper-ai:
image: ${WHISPER_IMAGE_URL} # Use a pre-built image like "jellyfin-whisper-lab/whisper-container"
container_name: whisper-ai
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./whisper/config:/config
- ${ROOT_MEDIA_PATH}:/data
# Enable GPU access if you have one, or comment out the 'deploy' section for CPU-only:
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
restart: unless-stopped
networks:
- internal-proxy
# Decluttarr - Queue Cleaner
decluttarr:
image: hotio/decluttarr:latest
container_name: decluttarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
- DECLUTTARR_CRON="*/15 * * * *"
# API Keys/URLs for Sonarr, Radarr, etc. go here post-setup!
volumes:
- ./decluttarr/config:/config
restart: unless-stopped
networks:
- internal-proxy
nzbget:
image: lscr.io/linuxserver/nzbget:latest
container_name: nzbget
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- /etc/localtime:/etc/localtime:ro
- ./nzbget:/config
- /mnt/data:/data
restart: unless-stopped
networks:
- internal-proxy
jellydash:
image: ghcr.io/themartz90/jellydash:latest
restart: unless-stopped
ports:
- "${APP_PORT:-8080}:80"
environment:
APP_ENV: "${APP_ENV:-production}"
APP_DEBUG: "${APP_DEBUG:-false}"
APP_TIMEZONE: "${APP_TIMEZONE:-UTC}"
# Bundled MariaDB below. Using your own database server instead? Point
# DB_HOST at it and drop the db service in docker-compose.override.yml.
DB_HOST: "${DB_HOST:-db}"
DB_PORT: "${DB_PORT:-3306}"
DB_DRIVER: mysqli
DB_NAME: "${DB_NAME:-jellydash}"
DB_USER: "${DB_USER:-jellydash}"
DB_PASS: "${DB_PASS:-change-me}"
JELLYFIN_URL: "${JELLYFIN_URL}"
JELLYFIN_API_TOKEN: "${JELLYFIN_API_TOKEN}"
JELLYFIN_VERIFY_SSL: "${JELLYFIN_VERIFY_SSL:-true}"
LIBRARIES_CACHE_TTL: "${LIBRARIES_CACHE_TTL:-300}"
# Jellyseerr (optional; the page hides until these are set)
JELLYSEER_URL: "${JELLYSEER_URL:-}"
JELLYSEER_API_TOKEN: "${JELLYSEER_API_TOKEN:-}"
JELLYSEER_VERIFY_SSL: "${JELLYSEER_VERIFY_SSL:-true}"
SEERR_POLL_INTERVAL: "${SEERR_POLL_INTERVAL:-120}"
SEERR_NOTIFY_ENABLED: "${SEERR_NOTIFY_ENABLED:-true}"
# Background poller (history recording, cache warming, alerts)
POLLER_ENABLED: "${POLLER_ENABLED:-true}"
POLL_INTERVAL: "${POLL_INTERVAL:-30}"
# Web Push notifications (optional; generate keys once, see README)
PUSH_ENABLED: "${PUSH_ENABLED:-true}"
VAPID_PUBLIC_KEY: "${VAPID_PUBLIC_KEY:-}"
VAPID_PRIVATE_KEY: "${VAPID_PRIVATE_KEY:-}"
VAPID_SUBJECT: "${VAPID_SUBJECT:-mailto:[email protected]}"
PUSH_IGNORE_USERS: "${PUSH_IGNORE_USERS:-}"
# Public base URL (optional): external alert links point back here.
APP_URL: "${APP_URL:-}"
# Extra notification channels; each is active once its config is set.
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN:-}"
TELEGRAM_CHAT_ID: "${TELEGRAM_CHAT_ID:-}"
PUSHOVER_APP_TOKEN: "${PUSHOVER_APP_TOKEN:-}"
PUSHOVER_USER_KEY: "${PUSHOVER_USER_KEY:-}"
DISCORD_WEBHOOK_URL: "${DISCORD_WEBHOOK_URL:-}"
# Libraries hidden from Trending/Most Watched (also editable in Settings)
TRENDING_EXCLUDE_LIBRARIES: "${TRENDING_EXCLUDE_LIBRARIES:-}"
# Optional login gate (recommended when internet-facing)
AUTH_ENABLED: "${AUTH_ENABLED:-false}"
AUTH_ADMIN_USER: "${AUTH_ADMIN_USER:-}"
AUTH_ADMIN_PASSWORD: "${AUTH_ADMIN_PASSWORD:-}"
depends_on:
db:
condition: service_healthy
volumes:
- ./jellydash_cache:/var/www/html/cache
- ./jellydash_runtime_cache:/var/www/html/var/cache
- ./jellydash_logs:/var/www/html/var/log
- ./jellydash_uploads:/var/www/html/public/uploads
# Drop-in feature modules (see docs/MODULES.md), e.g.:
# - ./my-modules/downloads:/var/www/html/modules/downloads:ro
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_RANDOM_ROOT_PASSWORD: "yes"
MARIADB_DATABASE: "${DB_NAME:-jellydash}"
MARIADB_USER: "${DB_USER:-jellydash}"
MARIADB_PASSWORD: "${DB_PASS:-change-me}"
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 12
volumes:
- ./jellydash_data:/var/lib/mysql
Gluetun
Source: ThomasWildeTech Arr stack docker compose file
Media Server Janitors
https://corelab.tech/maintainerr-declutarr-media-automation-guide/