Arr Stack Docker Compose (Prowlarr, Radarr, Sonarr + VPN)
A complete docker-compose.yml for the *arr stack — Prowlarr, Radarr, Sonarr, and a VPN-shielded qBittorrent — with the single-root layout that makes imports instant hardlinks.
bash# docker-compose.yml — full Arr stack. qBittorrent runs inside gluetun's
# VPN namespace (kill-switch); the Arr apps stay on the LAN. Every app
# mounts the SAME /data root so imports are instant hardlinks, not copies.
services:
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add: [NET_ADMIN]
devices: [/dev/net/tun:/dev/net/tun]
ports:
- "8080:8080" # qBittorrent WebUI, published by gluetun
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=YOUR_WIREGUARD_PRIVATE_KEY
- SERVER_COUNTRIES=Netherlands
- VPN_PORT_FORWARDING=on
- FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24 # set to YOUR LAN
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
network_mode: "service:gluetun"
environment: [PUID=1000, PGID=1000, TZ=Etc/UTC, WEBUI_PORT=8080]
volumes:
- /opt/qbittorrent:/config
- /data/torrents:/data/torrents
depends_on: [gluetun]
restart: unless-stopped
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
ports: ["9696:9696"]
environment: [PUID=1000, PGID=1000, TZ=Etc/UTC]
volumes: [/opt/prowlarr:/config]
restart: unless-stopped
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
ports: ["7878:7878"]
environment: [PUID=1000, PGID=1000, TZ=Etc/UTC]
volumes:
- /opt/radarr:/config
- /data:/data
restart: unless-stopped
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
ports: ["8989:8989"]
environment: [PUID=1000, PGID=1000, TZ=Etc/UTC]
volumes:
- /opt/sonarr:/config
- /data:/data
restart: unless-stopped
What this does
Stands up the full Arr stack in one file: Prowlarr (indexer manager), Radarr (movies), Sonarr (TV), and qBittorrent routed through a gluetun VPN container with a kill-switch. The step-by-step wiring (Prowlarr apps, indexers, download client, quality profiles) is in the Deploy the Arr Stack and Automation guides.
Prerequisites
- Docker + Docker Compose (install guide)
- A VPN provider supporting WireGuard + port forwarding (ProtonVPN, for example)
- The single-root
/datalayout created first (below)
Create the single-root layout
Downloads and media must share one filesystem or imports fall back to slow copies. Per the TRaSH Guides folder structure:
sudo mkdir -p /data/torrents/{movies,tv} /data/media/{movies,tv}
sudo chown -R 1000:1000 /data
Deploy
Save the script above as /opt/arr-stack/docker-compose.yml, fill in your WireGuard key and LAN subnet, then:
cd /opt/arr-stack
sudo docker compose up -d
# Verify torrent traffic exits the VPN, not your home IP:
sudo docker exec gluetun wget -qO- https://api.ipify.org ; echo
Then set each app’s root folder to /data/media/movies (Radarr) and /data/media/tv (Sonarr), and the download client category folders under /data/torrents/ — so imports hardlink instantly.
Notes
- Only qBittorrent is VPN-shielded — the Arr apps move metadata only.
FIREWALL_OUTBOUND_SUBNETSmust be your real LAN or the apps can’t reach the download client. Port-forward sync details are in the VPN Torrent Stack playbook. - Keep
PUID/PGIDidentical (1000 here) across all containers so imports don’t hit permission errors. - Add indexers only in Prowlarr — register Radarr/Sonarr as applications and it syncs them automatically.
- Never commit your real WireGuard key. Keep it on the host or in an
.envfile outside version control.