On this page
If you’ve read what the Arr stack is and you’re ready to actually build it, this is the guide. The stack is a handful of containers that are individually simple but only shine when they’re wired together correctly — and most of the frustration people hit comes from getting the wiring wrong, not the apps themselves. We’ll stand up the whole thing in one Docker Compose file — Prowlarr, Radarr, Sonarr, and a VPN-shielded qBittorrent — and, just as importantly, use the folder layout that makes imports instant instead of slow. That layout decision is the part most tutorials skip, and it’s the one you can’t easily fix later.
You can run this wherever Docker lives in your lab: a NAS, a Docker-on-Proxmox LXC or VM, or a dedicated Linux box. Once it’s up, the automation guide takes over and connects it to your media server.
Task 1: Get the folder layout right first
Before writing a single line of Compose, sort out where files will live — this is the single most important decision in the whole build, and the most common mistake. The rule: downloads and your media library must live under one shared root, mounted identically into every container. Here’s why it matters. When both sit on the same filesystem, Radarr and Sonarr can import a finished download as an instant hardlink — a second directory entry pointing at the same data on disk. The file appears in your library immediately, costs no extra disk space, and the original stays in place for seeding. When downloads and media are split into separate mounts, the containers see two different filesystems, hardlinks become impossible, and every import falls back to a slow copy that temporarily doubles your disk usage.
The TRaSH Guides recommended layout uses a single /data root:
/data
├── torrents # download client writes here
│ ├── movies
│ └── tv
└── media # media server reads here
├── movies
└── tv
Every container mounts the parent /data, not the subfolders:
Do NOT mount /downloads and /movies as separate volumes. To the container that’s two filesystems, so imports fall back to copy-and-delete. Mount one shared parent (/data) into every app. Hardlinks and atomic moves require everything on one filesystem — and every app needs read access, while the importing apps need write access.
Create it with correct ownership (use the same UID/GID you’ll give the containers):
sudo mkdir -p /data/torrents/{movies,tv} /data/media/{movies,tv}
sudo chown -R 1000:1000 /data
Task 2: Write the Compose file
With the folders in place, the Compose file below runs Prowlarr, Radarr, and Sonarr normally on your LAN, while qBittorrent lives inside a gluetun VPN container — so the torrent traffic, and only the torrent traffic, is shielded behind the VPN. The images come from LinuxServer.io, whose containers are the community standard for the Arr apps and share a consistent PUID/PGID permissions model (those two variables tell the container which user to run as, which is how we keep file ownership consistent across the stack). The full file is also available as a copy-paste Arr stack Compose playbook.
This file contains placeholders you must replace before it will work: YOUR_WIREGUARD_PRIVATE_KEY (from your VPN provider’s WireGuard config — keep it in a secret store, never in a note), protonvpn and Netherlands (your VPN provider and preferred server country), 192.168.1.0/24 (your actual LAN subnet), 1000/1000 (the UID/GID that owns /data on your host — check with id), and the /opt/... config paths if you keep container config elsewhere. Rule of thumb: if a value looks specific to one machine, it’s a placeholder to change — not a literal to copy.
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
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
network_mode: "service:gluetun" # all traffic through the VPN
environment: [PUID=1000, PGID=1000, TZ=Etc/UTC, WEBUI_PORT=8080]
volumes:
- /opt/qbittorrent:/config
- /data/torrents:/data/torrents # same path as the Arr apps
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 # ONE shared root -> hardlinks
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
gluetun blocks everything except the VPN by default. qBittorrent needs to talk to Radarr/Sonarr on your LAN, so FIREWALL_OUTBOUND_SUBNETS must match your actual subnet or the Arr apps can’t reach the client. Full details and port-forward sync are in the VPN Torrent Stack playbook.
Bring it up and confirm the VPN is actually shielding traffic:
sudo docker compose up -d
# This MUST print a VPN IP, not your home IP:
sudo docker exec gluetun wget -qO- https://api.ipify.org ; echo
Task 3: Wire the apps together
At this point the containers are all running, but they’re strangers to each other — Prowlarr doesn’t know Radarr exists, and nothing knows where downloads go. There are three connections to make, all done in the web UIs, and the order below matters: apps first, then indexers, then the download client.
Grab each app’s API key from its Settings → General page. In Prowlarr → Settings → Apps → Add, add Radarr and Sonarr, pasting each API key and the container URL (http://radarr:7878, http://sonarr:8989 — containers reach each other by service name). Prowlarr will now sync indexers to both.
In Prowlarr → Indexers → Add Indexer, add the trackers or Usenet indexers you use. They sync to Radarr and Sonarr automatically — you never touch indexer settings in those apps.
In Radarr → Settings → Download Clients → Add → qBittorrent (and again in Sonarr), point it at the qBittorrent host and port (http://<host-ip>:8080, since qBittorrent’s UI is published by gluetun on the host). Set a category — radarr in Radarr, sonarr in Sonarr — so downloads land in the right subfolder.
Set each app’s root folder to the media subpath — /data/media/movies in Radarr, /data/media/tv in Sonarr. Because the download client writes to /data/torrents/... under the same /data mount, imports become instant hardlinks exactly as intended.
Task 4: Test the pipeline
Don’t just assume the wiring works — prove it with one end-to-end run. Add a single movie in Radarr, mark it monitored, and hit Search. Then watch the whole chain fire: Radarr queries Prowlarr’s indexers, picks a release according to its quality settings, hands it to qBittorrent, and on completion imports it into /data/media/movies as a hardlink. The tell-tale signs of success are that the import completes instantly and your disk usage doesn’t jump by the file’s size. If either of those is off, the folder layout from Task 1 is the first place to look.
If a download completes but Radarr/Sonarr won’t import it, it’s almost always permissions — the file is owned by a user the app can’t read or move. Keep the same PUID/PGID (1000 here) across qBittorrent and the Arr apps, and make sure /data is owned by that user.
What’s next
You have a working stack: add a title, it downloads and files itself. The automation guide takes it hands-off — import lists that add titles for you, quality profiles that pick the best release, and a notification that rescans your media server the moment a file lands.
Related posts:
- What Is the Arr Stack? — the concepts behind this deploy
- Automating the Arr Stack — import lists, quality profiles, media-server hookup
- VPN-Shielded Torrent Stack — the download client, with kill-switch and port-forward sync
- Docker on Proxmox: LXC vs VM, Done Right — where to run these containers
- Install Plex Media Server on a NAS or Proxmox LXC — the media server this stack feeds
Sources: TRaSH Guides — Hardlinks and Instant Moves, Servarr Docker Guide.
Comments
Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.