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.

AppPurposeManages
SonarrTV show automationEpisodes, seasons, series
RadarrMovie automationFilms by quality profile
LidarrMusic automationAlbums, artists, tracks
ReadarrBook/audiobook automationEbooks, audiobooks
ProwlarrIndexer managerManages indexers for all *arr apps
BazarrSubtitle automationSubtitles for Sonarr/Radarr libraries
qBittorrentTorrent clientDownload management
SABnzbdUsenet clientNZB 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}
PathPurpose
/opt/arr-stack/*/configPer-app configuration and databases
/data/media/moviesOrganized movie library
/data/media/tvOrganized TV show library
/data/media/musicOrganized music library
/data/media/booksOrganized book library
/data/downloads/completeFinished downloads
/data/downloads/incompleteIn-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

  1. Open http://your-server:8080
  2. Default credentials: admin / check container logs for temporary password:
    docker logs qbittorrent 2>&1 | grep -i password
  3. Change the admin password immediately under Options → Web UI
  4. Set the default save path to /data/downloads/complete
  5. Under Downloads, set:
    • Default Save Path: /data/downloads/complete
    • Keep incomplete torrents in: /data/downloads/incomplete
  6. Under BitTorrent, consider limiting upload ratio and seeding time

Step 2: Set Up Prowlarr

  1. Open http://your-server:9696
  2. Set up authentication (Forms or Basic — do not leave unprotected)
  3. Add indexers: Click Indexers → Add Indexer and add your preferred indexers. Prowlarr supports hundreds of public and private trackers.
  4. 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
  5. Click Sync App Indexers — Prowlarr pushes all configured indexers to each *arr app automatically

Step 3: Set Up Sonarr

  1. Open http://your-server:8989
  2. 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
  3. Settings → Download Clients → Add:
    • Type: qBittorrent
    • Host: qbittorrent
    • Port: 8080
    • Username/password from Step 1
    • Category: tv (Sonarr creates this category automatically)
  4. Settings → Profiles: Review quality profiles. “HD-1080p” is a reasonable default.
  5. Add a TV show via Series → Add New to verify the pipeline works

Step 4: Set Up Radarr

  1. Open http://your-server:7878
  2. Settings → Media Management:
    • Enable “Rename Movies”
    • Root Folder: add /data/media/movies
    • Enable “Use Hardlinks instead of Copy”
  3. Settings → Download Clients → Add:
    • Type: qBittorrent
    • Host: qbittorrent
    • Port: 8080
    • Category: movies
  4. 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 ServerBest For
JellyfinFree, open-source, no account required
PlexPolish, mobile apps, remote streaming
EmbyMiddle 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.

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 /data mount 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

# 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:

  1. Verify the API key in Prowlarr → Settings → Apps matches the target app’s API key
  2. Use container names (e.g., http://sonarr:8989) not localhost — they’re on a Docker network
  3. Click “Test” on each app connection in Prowlarr
  4. 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.

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

ComponentRAM (idle)RAM (active)CPUDisk
Prowlarr~50 MB~150 MBLow100 MB
Sonarr~150 MB~400 MBLow-Medium500 MB
Radarr~150 MB~400 MBLow-Medium500 MB
Lidarr~150 MB~300 MBLow300 MB
Readarr~100 MB~250 MBLow200 MB
Bazarr~80 MB~200 MBLow100 MB
qBittorrent~100 MB~500 MBLow-MediumVariable
Full stack~800 MB~2.2 GBMedium2 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

Comments