The Complete *arr Stack Setup Guide
What Is the *arr Stack?
The *arr stack is a collection of self-hosted applications that automate media library management. Each app handles a different media type — Sonarr for TV shows, Radarr for movies, Lidarr for music, Readarr for books — all using a shared architecture. Together with an indexer manager (Prowlarr) and a download client (qBittorrent or SABnzbd), they form a fully automated pipeline: search → download → organize → serve.
Updated March 2026: Verified with latest Docker images and configurations.
| App | Purpose | Manages |
|---|---|---|
| Sonarr | TV show automation | Episodes, seasons, series |
| Radarr | Movie automation | Films by quality profile |
| Lidarr | Music automation | Albums, artists, tracks |
| Readarr | Book/audiobook automation | Ebooks, audiobooks |
| Prowlarr | Indexer manager | Manages indexers for all *arr apps |
| Bazarr | Subtitle automation | Subtitles for Sonarr/Radarr libraries |
| qBittorrent | Torrent client | Download management |
| SABnzbd | Usenet client | NZB download management |
Prerequisites
- A Linux server with Docker and Docker Compose (guide)
- 4 GB RAM minimum (8 GB recommended for the full stack)
- 50 GB+ free disk space for media storage
- A VPN service (recommended for torrent traffic)
- Basic understanding of Docker networking
Architecture Overview
The *arr stack uses a hub-and-spoke model:
Prowlarr (indexer hub)
├── Sonarr (TV) ──→ qBittorrent/SABnzbd ──→ Media Library ──→ Jellyfin
├── Radarr (Movies) ──→ qBittorrent/SABnzbd ──→ Media Library ──→ Jellyfin
├── Lidarr (Music) ──→ qBittorrent/SABnzbd ──→ Media Library ──→ Jellyfin
└── Readarr (Books) ──→ qBittorrent/SABnzbd ──→ Media Library ──→ Calibre-Web
Prowlarr pushes indexer configurations to all *arr apps. Each *arr app sends download requests to a shared download client. Completed downloads are moved and renamed into a structured media library that your media server reads.
Directory Structure
Get the directory structure right before deploying. This is where most people struggle.
# Create the base directories
mkdir -p /opt/arr-stack/{sonarr,radarr,lidarr,readarr,prowlarr,bazarr,qbittorrent,sabnzbd}/config
# Create media directories
mkdir -p /data/media/{movies,tv,music,books}
# Create download directories
mkdir -p /data/downloads/{complete,incomplete,torrents}
| Path | Purpose |
|---|---|
/opt/arr-stack/*/config | Per-app configuration and databases |
/data/media/movies | Organized movie library |
/data/media/tv | Organized TV show library |
/data/media/music | Organized music library |
/data/media/books | Organized book library |
/data/downloads/complete | Finished downloads |
/data/downloads/incomplete | In-progress downloads |
Critical rule: Mount /data as a single volume in every container that needs both downloads and media. This allows hard links (instant file moves) instead of slow copy-and-delete operations. If Sonarr and qBittorrent see the same filesystem, moving a completed download into the media library is instant.
Docker Compose Configuration
This is the complete stack. Start with the core (Sonarr + Radarr + Prowlarr + qBittorrent), then add optional apps as needed.
Create /opt/arr-stack/docker-compose.yml:
services:
# ── Indexer Manager ──────────────────────────────
prowlarr:
image: lscr.io/linuxserver/prowlarr:2.3.0
container_name: prowlarr
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York # CHANGE to your timezone
volumes:
- ./prowlarr/config:/config
ports:
- "9696:9696"
networks:
- arr-net
# ── TV Shows ─────────────────────────────────────
sonarr:
image: lscr.io/linuxserver/sonarr:4.0.17
container_name: sonarr
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./sonarr/config:/config
- /data:/data # Single mount for hard links
ports:
- "8989:8989"
networks:
- arr-net
# ── Movies ───────────────────────────────────────
radarr:
image: lscr.io/linuxserver/radarr:6.0.4
container_name: radarr
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./radarr/config:/config
- /data:/data
ports:
- "7878:7878"
networks:
- arr-net
# ── Download Client (Torrent) ────────────────────
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:5.1.4
container_name: qbittorrent
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- WEBUI_PORT=8080
volumes:
- ./qbittorrent/config:/config
- /data/downloads:/data/downloads
ports:
- "8080:8080" # Web UI
- "6881:6881" # Torrent port
- "6881:6881/udp"
networks:
- arr-net
# ── Music (Optional) ────────────────────────────
lidarr:
image: lscr.io/linuxserver/lidarr:3.1.0.4875
container_name: lidarr
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./lidarr/config:/config
- /data:/data
ports:
- "8686:8686"
networks:
- arr-net
# ── Books (Optional) ────────────────────────────
readarr:
image: lscr.io/linuxserver/readarr:0.4.18-develop
container_name: readarr
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./readarr/config:/config
- /data:/data
ports:
- "8787:8787"
networks:
- arr-net
# ── Subtitles (Optional) ────────────────────────
bazarr:
image: lscr.io/linuxserver/bazarr:1.5.6
container_name: bazarr
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./bazarr/config:/config
- /data/media:/data/media
ports:
- "6767:6767"
networks:
- arr-net
networks:
arr-net:
driver: bridge
Start the stack:
cd /opt/arr-stack
docker compose up -d
PUID/PGID: All containers run as the same user (1000:1000). This prevents permission issues when files move between containers. Verify your user ID with id -u and group with id -g.
Step-by-Step Configuration
Configure the apps in this specific order. Each step depends on the previous one.
Step 1: Set Up qBittorrent
- Open
http://your-server:8080 - Default credentials:
admin/ check container logs for temporary password:docker logs qbittorrent 2>&1 | grep -i password - Change the admin password immediately under Options → Web UI
- Set the default save path to
/data/downloads/complete - Under Downloads, set:
- Default Save Path:
/data/downloads/complete - Keep incomplete torrents in:
/data/downloads/incomplete
- Default Save Path:
- Under BitTorrent, consider limiting upload ratio and seeding time
Step 2: Set Up Prowlarr
- Open
http://your-server:9696 - Set up authentication (Forms or Basic — do not leave unprotected)
- Add indexers: Click Indexers → Add Indexer and add your preferred indexers. Prowlarr supports hundreds of public and private trackers.
- Add apps: Click Settings → Apps → Add Application:
- Add Sonarr: URL =
http://sonarr:8989, get API key from Sonarr’s Settings → General - Add Radarr: URL =
http://radarr:7878, get API key from Radarr’s Settings → General - Add Lidarr/Readarr if using them
- Add Sonarr: URL =
- Click Sync App Indexers — Prowlarr pushes all configured indexers to each *arr app automatically
Step 3: Set Up Sonarr
- Open
http://your-server:8989 - Settings → Media Management:
- Enable “Rename Episodes”
- Root Folder: add
/data/media/tv - Enable “Use Hardlinks instead of Copy” — this is critical for disk space efficiency
- Settings → Download Clients → Add:
- Type: qBittorrent
- Host:
qbittorrent - Port:
8080 - Username/password from Step 1
- Category:
tv(Sonarr creates this category automatically)
- Settings → Profiles: Review quality profiles. “HD-1080p” is a reasonable default.
- Add a TV show via Series → Add New to verify the pipeline works
Step 4: Set Up Radarr
- Open
http://your-server:7878 - Settings → Media Management:
- Enable “Rename Movies”
- Root Folder: add
/data/media/movies - Enable “Use Hardlinks instead of Copy”
- Settings → Download Clients → Add:
- Type: qBittorrent
- Host:
qbittorrent - Port:
8080 - Category:
movies
- Add a movie to test the pipeline
Step 5: Connect a Media Server
The *arr stack organizes your library. A media server presents it with a UI, metadata, streaming, and multi-device support.
| Media Server | Best For |
|---|---|
| Jellyfin | Free, open-source, no account required |
| Plex | Polish, mobile apps, remote streaming |
| Emby | Middle ground between Jellyfin and Plex |
Point your media server’s library at /data/media/movies and /data/media/tv. It scans and imports metadata automatically.
Step 6: Add Overseerr or Jellyseerr (Optional)
Overseerr (for Plex) or Jellyseerr (for Jellyfin) provides a request interface — users browse and request content, which automatically gets sent to Sonarr/Radarr for download.
Hard Links vs Copies
This is the single most important optimization in the *arr stack. Without hard links, every downloaded file gets copied from the download directory to the media directory — doubling disk usage and taking minutes for large files.
How hard links work: A hard link creates a second filename pointing to the same data on disk. The file appears in both /data/downloads/complete/movie.mkv and /data/media/movies/Movie (2024)/movie.mkv, but only uses disk space once.
Requirements for hard links:
- Both paths must be on the same filesystem (same partition/drive)
- Docker containers must see both paths under a common parent mount
- The download client and the *arr app must use the same
/datamount point
Why the /data mount matters: If you mount downloads as /downloads in qBittorrent and /downloads in Radarr separately, Docker may treat them as different filesystems even if they’re on the same host drive. Mounting /data:/data in both containers guarantees they share the same filesystem view.
Verify hard links are working:
# After a download completes and gets imported:
ls -li /data/downloads/complete/somefile.mkv /data/media/movies/Movie\ \(2024\)/somefile.mkv
# Both should show the same inode number (first column)
Common Mistakes
Separate volume mounts (breaks hard links)
# WRONG — hard links won't work
volumes:
- /mnt/downloads:/downloads
- /mnt/media:/media
# RIGHT — single parent mount
volumes:
- /data:/data
Mismatched PUID/PGID
If containers run as different users, one app can’t read files created by another. Always use the same PUID/PGID across all containers:
# Find your user/group ID
id
# uid=1000(youruser) gid=1000(youruser)
Wrong download client paths
The paths configured in Sonarr/Radarr must match what the download client reports. If qBittorrent saves to /data/downloads/complete inside its container, Sonarr must also see that path as /data/downloads/complete — not /downloads/complete or some other mapping.
Indexers not syncing from Prowlarr
If Prowlarr shows indexers but Sonarr/Radarr doesn’t:
- Verify the API key in Prowlarr → Settings → Apps matches the target app’s API key
- Use container names (e.g.,
http://sonarr:8989) notlocalhost— they’re on a Docker network - Click “Test” on each app connection in Prowlarr
- Click “Sync App Indexers” after adding new indexers
Using /data for both downloads and media without subdirectories
Don’t mount /data:/data and then set both the download path and media path to /data. Use subdirectories: /data/downloads/complete for downloads, /data/media/movies for organized content.
Adding a VPN (Recommended)
For torrent traffic, route the download client through a VPN. Use a VPN container with a kill switch:
services:
gluetun:
image: qmcgaw/gluetun:v3.41.1
container_name: gluetun
restart: unless-stopped
cap_add:
- NET_ADMIN
environment:
- VPN_SERVICE_PROVIDER=mullvad # CHANGE to your provider
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=your-key-here # CHANGE
- SERVER_COUNTRIES=Switzerland # CHANGE
ports:
- "8080:8080" # qBittorrent Web UI (routed through VPN)
- "6881:6881"
- "6881:6881/udp"
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:5.1.4
container_name: qbittorrent
restart: unless-stopped
network_mode: "service:gluetun" # Route ALL traffic through VPN
depends_on:
- gluetun
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- WEBUI_PORT=8080
volumes:
- ./qbittorrent/config:/config
- /data/downloads:/data/downloads
With network_mode: "service:gluetun", all qBittorrent traffic goes through the VPN. If the VPN drops, the kill switch stops all traffic — no IP leaks. Note that ports move from qBittorrent to the gluetun container.
Usenet Alternative
If using Usenet instead of (or alongside) torrents, replace or supplement qBittorrent with SABnzbd:
sabnzbd:
image: lscr.io/linuxserver/sabnzbd:4.5.5
container_name: sabnzbd
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
volumes:
- ./sabnzbd/config:/config
- /data/downloads:/data/downloads
ports:
- "8081:8080"
networks:
- arr-net
Configure SABnzbd with your Usenet provider credentials, then add it as a download client in Sonarr/Radarr alongside or instead of qBittorrent.
Resource Requirements
| Component | RAM (idle) | RAM (active) | CPU | Disk |
|---|---|---|---|---|
| Prowlarr | ~50 MB | ~150 MB | Low | 100 MB |
| Sonarr | ~150 MB | ~400 MB | Low-Medium | 500 MB |
| Radarr | ~150 MB | ~400 MB | Low-Medium | 500 MB |
| Lidarr | ~150 MB | ~300 MB | Low | 300 MB |
| Readarr | ~100 MB | ~250 MB | Low | 200 MB |
| Bazarr | ~80 MB | ~200 MB | Low | 100 MB |
| qBittorrent | ~100 MB | ~500 MB | Low-Medium | Variable |
| Full stack | ~800 MB | ~2.2 GB | Medium | 2 GB + media |
A full *arr stack with Sonarr, Radarr, Prowlarr, and qBittorrent runs on 4 GB RAM. Adding all optional apps (Lidarr, Readarr, Bazarr) needs 8 GB.
Next Steps
- Add Jellyfin or Plex to serve your media library
- Set up Overseerr or Jellyseerr for user requests
- Configure Bazarr for automatic subtitle downloads
- Add FlareSolverr if indexers use Cloudflare protection
- Set up a reverse proxy for remote access
- Implement backups for your *arr configuration databases
Related
- How to Self-Host Sonarr
- How to Self-Host Radarr
- How to Self-Host Prowlarr
- How to Self-Host Lidarr
- How to Self-Host Readarr
- How to Self-Host Bazarr
- How to Self-Host qBittorrent
- How to Self-Host SABnzbd
- How to Self-Host Jellyfin
- How to Self-Host Overseerr
- How to Self-Host Jellyseerr
- Best Self-Hosted Media Servers
- Docker Compose Basics
- Docker Networking
- Reverse Proxy Setup
- Usenet Setup Guide
- Backup Strategy
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments