Skip to content
Go back

The *arr Stack Without 9000 Reddit Threads

By SumGuy 9 min read
The *arr Stack Without 9000 Reddit Threads

The *arr Stack Is Easy. The Folder Layout Is the Hard Part.

You’ve heard it a thousand times: “Just use Sonarr and Radarr. It’s simple.” Then you spend six hours untangling permissions, discovering that hardlinks don’t work because your folders are on different volumes, and cursing at the internet because the *arr automation suddenly stops working after a reboot.

Here’s the thing: the *arr stack is simple when you set it up right the first time. But “right” is a very specific folder layout and a Docker Compose file that doesn’t look like it was assembled from three Reddit threads from 2019.

This is the setup that survives. No duct tape. No debugging permissions at 2 AM. Just boring, durable automation.


What Each Piece Actually Does

Let’s be clear on the roles, because they matter for the folder layout:

Prowlarr is your indexer manager. It talks to all your torrent sites and usenet providers and pretends it’s a single API. Sonarr and Radarr ask Prowlarr “what’s available?” instead of each running their own 47 indexers. Centralized. Sane. One config to maintain.

Sonarr watches TV shows. You tell it which shows you want, which quality to grab, and it monitors Prowlarr for new episodes. When something lands, it triggers your download client (qBittorrent or SABnzbd), waits for completion, and moves the files into organized folders with metadata.

Radarr is Sonarr for movies. Same logic, different content type.

Bazarr sits on top of Radarr and Sonarr. It automatically grabs subtitles for your stuff and integrates with the folder layout they create. Set a language, forget about it.

The download client (qBittorrent or SABnzbd) is the worker. The *arr apps tell it “grab this”, it downloads, and then the *arr app moves the completed files to the media library. Everything passes through this client’s watch/download folder.

Jellyseerr (or Overseerr for Plex) is the request UI. Your family says “I want to watch X” in a friendly web interface, and Jellyseerr tells Radarr/Sonarr to grab it. Separates the “making requests” layer from the “managing the library” layer.

Now, here’s where 90% of people get it wrong:


The One Folder Layout That Works

You need a single root volume where both your media and downloads live. Not /media on one disk and /downloads on another. Not a symlink workaround. One filesystem, same parent, so hardlinks work.

Let me show you the tree:

/data/
├── torrents/ ← downloads go here
│ ├── tv/ ← incomplete TV downloads
│ ├── movies/ ← incomplete movie downloads
│ └── completed/ ← qBittorrent moves here on finish (optional, cleaner)
├── media/
│ ├── tv/ ← Sonarr moves completed TV here
│ │ └── Show Name/
│ │ ├── Season 01/
│ │ │ └── Show Name - S01E01 - Title.mkv
│ │ └── Season 02/
│ │ └── Show Name - S02E01 - Title.mkv
│ └── movies/ ← Radarr moves completed movies here
│ └── Movie Name (Year)/
│ └── Movie Name (Year).mkv
└── recyclebin/ ← Sonarr/Radarr soft-deletes go here (recoverable)

Why this matters: When a torrent finishes in /data/torrents/, qBittorrent can hard-link the file to /data/media/tv/ instead of copying. A hard link is a second reference to the same physical file on disk — no extra space used. You save 50% or more disk, and moves are instant (atomic).

If your downloads and media are on different volumes (different filesystems, even if they look like one disk), hardlinks die. You’re stuck copying, which is slower and uses double the space while the file exists in both places.


Here’s the car analogy: hardlinks are valet parking. Your movie file is one car. You give the valet two stickers pointing to the same car in the lot. You drive with one sticker, your friend drives with the other. Only one car exists. Neither of you can delete it until you both hand back your stickers.

Copy the file? That’s buying a second identical car. Now you have two cars taking up space, and if you change the paint on one, the other doesn’t match.

The *arr apps are designed around hardlinks. When Radarr moves a completed movie from /data/torrents/movies/ to /data/media/movies/, it tries to hardlink first. If that works (same filesystem), the move is instant and free. If it fails, it falls back to copying, which is slow and eats disk.

So:

This is why the layout matters more than the software.


The Compose File That Doesn’t Break

Here’s a complete, production-tested stack:

version: "3.8"
services:
prowlarr:
image: linuxserver/prowlarr:latest
container_name: prowlarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
volumes:
- /data/config/prowlarr:/config
- /data/torrents:/downloads
- /data/media:/media
ports:
- "6969:6969"
restart: unless-stopped
sonarr:
image: linuxserver/sonarr:latest
container_name: sonarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
volumes:
- /data/config/sonarr:/config
- /data/torrents:/downloads
- /data/media:/media
ports:
- "8989:8989"
restart: unless-stopped
depends_on:
- prowlarr
radarr:
image: linuxserver/radarr:latest
container_name: radarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
volumes:
- /data/config/radarr:/config
- /data/torrents:/downloads
- /data/media:/media
ports:
- "7878:7878"
restart: unless-stopped
depends_on:
- prowlarr
bazarr:
image: linuxserver/bazarr:latest
container_name: bazarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
volumes:
- /data/config/bazarr:/config
- /data/media:/media
ports:
- "6767:6767"
restart: unless-stopped
qbittorrent:
image: linuxserver/qbittorrent:latest
container_name: qbittorrent
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
- WEBUI_PORT=8080
volumes:
- /data/config/qbittorrent:/config
- /data/torrents:/downloads
ports:
- "8080:8080"
- "6881:6881"
- "6881:6881/udp"
restart: unless-stopped
jellyseerr:
image: fallenbagel/jellyseerr:latest
container_name: jellyseerr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
- LOG_LEVEL=info
volumes:
- /data/config/jellyseerr:/app/config
ports:
- "5055:5055"
restart: unless-stopped
depends_on:
- sonarr
- radarr

PUID/PGID: These must match your media user. Find them with:

Terminal window
id myuser

Look for uid=1000(myuser) gid=1000(mygroup). Use those numbers.

Volumes: Every service mounts:

Jellyseerr doesn’t need /data/torrents because it only reads from Sonarr/Radarr. Bazarr only needs /config and /media (it watches the media folder and matches subtitles).

Network mode: This example uses bridge (default). If you want VPN-only downloads, wrap qbittorrent with a network_mode: "container:gluetun" and add a gluetun service. The *arr services hit qbittorrent over the docker network at http://qbittorrent:6881.


Wiring It All Together: A Sonarr Root Folder

Once your stack is running, log into Sonarr and add a root folder:

  1. Settings → Media Management → Root Folders → Add Root Folder
  2. Set the path to /media/tv
  3. Leave “Set Permissions” and “Use Hardlinks” checked (they’re defaults)
  4. Save

Now when you add a TV show in Sonarr, it will land in /media/tv/Show Name/ on your filesystem, hardlinked from /data/torrents/. Your Plex or Jellyfin server mounts /data/media/ and sees the organized library.


Separating Downloads and Media in Your Media Server

Critical: Mount /data/media/ into Plex/Jellyfin, not /data/torrents/. The download folder is messy (in-progress files, incomplete stuff) and changes constantly. Your media folder is clean and stable.

Example Plex/Jellyfin Compose volume:

jellyfin:
image: jellyfin/jellyfin:latest
volumes:
- /data/media:/media:ro

Plex sees /media/tv/ and /media/movies/ at the same mount point. No confusion. No watching in-progress downloads. No weird playback glitches.


Common Gotchas (The Things That Break)

Permissions are backwards: PUID=1000 PGID=1000 doesn’t mean “the container runs as root”. It means “the container runs as user 1000, and all files it creates are owned by 1000:1000”. If your media files are owned by a different user, the containers can’t write to them. Check ls -n /data/media/ and match the numeric UID/GID.

Hardlinks aren’t failing silently: If hardlinks don’t work (different filesystems), Sonarr/Radarr will copy. But the copy happens, and then the original torrent is deleted, leaving your completed file vulnerable to interruption. Check the Sonarr/Radarr logs for Unable to hardlink to catch this early.

/data vs. /downloads: Some old guides use /downloads and /tv as separate roots. If they’re on different volumes (e.g., SSD and HDD), hardlinks fail. Pin everything to /data on one volume.

Recyclebin and permissions: Sonarr/Radarr soft-delete unwanted files to a recyclebin/ folder. If that folder doesn’t exist, deletions hard-delete (gone forever). Create /data/recyclebin/ with the same ownership as /data/media/ before starting.

Prowlarr indexers don’t sync: After adding indexers to Prowlarr, Sonarr/Radarr won’t see them immediately. Go to Settings → Indexers → Sync Indexers in Sonarr/Radarr, or restart the containers.


Backup What Matters

Your media library is replaceable. A torrent died? Grab it again. Your configs are irreplaceable. You’ve spent hours tuning Sonarr profiles, building Prowlarr indexers, and tweaking Bazarr settings. Lose those and you’re starting over.

Backup /data/config/ hourly. Use Restic, Kopia, or rsync. Doesn’t matter. Just protect the configs.

Media itself? Raid or replicated storage is nice, but if a drive dies, you re-download. The real cost of media loss is time, not the files themselves.


Network Isolation (Optional, But Smart)

If your download client is a piracy vector (and let’s be honest, indexers are what they are), isolate it:

qbittorrent:
image: linuxserver/qbittorrent:latest
network_mode: "container:gluetun"
depends_on:
- gluetun
gluetun:
image: qmcgaw/gluetun:latest
cap_add:
- NET_ADMIN
environment:
- VPN_SERVICE_PROVIDER=... # your provider
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=...

Now qBittorrent only has internet through the VPN. ISP never sees the traffic. Sonarr/Radarr talk to qBittorrent locally over the docker bridge (fast, no VPN), and qBittorrent’s upstream is encrypted.

This is defense in depth. The legal shield is on your infrastructure, not on the content.


A Stack That Doesn’t Break

This layout — single /data volume, hardlinks, proper PUID/PGID, backed-up configs — survives restarts, disk migrations, and the occasional angry email from your ISP.

The *arr stack is simple. You just have to build it once, correctly, and then get out of its way. No Reddit threads. No permission debugging at 2 AM. Just media, organized, arriving automatically, ready for your media server.

Set it up this way, and you won’t touch it again. That’s the goal.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Jellyseerr Tagging Workflows for Real Libraries

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts