This commit is contained in:
Mike McFetridge
2026-07-20 09:23:17 -04:00
parent c1315882da
commit 72272e4006
3179 changed files with 562960 additions and 14 deletions
@@ -0,0 +1,431 @@
---
tags:
- Docker
- Media
---
Directory Structure
cd docker
mkdir -p ~/arr-stack/{config/{gluetun,qbittorrent,prowlarr,radarr,sonarr},data/{torrents,media/{movies,tv}}}
cd ~/arr-stack
touch docker-compose.yml .env
```
# Tom Spark's ARR Stack — Automated Media Server
# https://github.com/loponai/arrstack
#
# Usage:
# 1. Copy .env.example to .env and fill in your VPN credentials
# 2. Run: bash setup-folders.sh
# 3. Run: docker compose up -d
#
# All VPN-protected services (qBittorrent, Prowlarr, FlareSolverr) run
# through Gluetun. If the VPN drops, traffic stops. Zero leaks.
#
# Radarr, Sonarr, Lidarr, Bazarr, Jellyfin, and Seerr do NOT run through
# the VPN — they need direct network access for speed and local connectivity.
networks:
arrnetwork:
name: arrnetwork
ipam:
config:
- subnet: 172.39.0.0/24
services:
# ============================================================
# GLUETUN — VPN Container (kill switch + tunnel)
# All VPN-protected services route through this container.
# Ports for those services are mapped HERE, not on the services themselves.
# Docs: https://github.com/qdm12/gluetun-wiki
# ============================================================
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
networks:
arrnetwork:
ipv4_address: ${IP_GLUETUN}
ports:
- 8000:8000 # Gluetun Control Server
- 8080:8080 # qBittorrent WebUI
- 6881:6881 # qBittorrent torrenting port
- 6881:6881/udp
- 9696:9696 # Prowlarr
- 8191:8191 # FlareSolverr
volumes:
- ./gluetun:/gluetun
environment:
- VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
- VPN_TYPE=${VPN_TYPE}
# --- WireGuard credentials (most providers) ---
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
# - WIREGUARD_PUBLIC_KEY=${WIREGUARD_PUBLIC_KEY}
- WIREGUARD_PRESHARED_KEY=${WIREGUARD_PRESHARED_KEY}
# --- OpenVPN credentials (if using OpenVPN instead) ---
# - OPENVPN_USER=${OPENVPN_USER}
# - OPENVPN_PASSWORD=${OPENVPN_PASSWORD}
# --- Server selection ---
- SERVER_COUNTRIES=${SERVER_COUNTRIES}
# --- Port forwarding (ProtonVPN, AirVPN, PIA) ---
# - VPN_PORT_FORWARDING=${VPN_PORT_FORWARDING}
- FIREWALL_VPN_INPUT_PORTS=${FIREWALL_VPN_INPUT_PORTS}
# --- General ---
- TZ=${TZ}
- BLOCK_MALICIOUS=off
- HTTP_CONTROL_SERVER_ADDRESS=:8000
- HTTP_CONTROL_SERVER_LOG=on
- HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE={"auth":"none"}
healthcheck:
test: wget -qO /dev/null http://127.0.0.1:9999 || exit 1
interval: 20s
timeout: 10s
retries: 5
restart: unless-stopped
# ============================================================
# QBITTORRENT — Torrent Client (runs through Gluetun VPN)
# ALL traffic goes through the VPN tunnel. No direct internet.
# ============================================================
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
network_mode: service:gluetun
depends_on:
gluetun:
condition: service_healthy
restart: true
labels:
- deunhealth.restart.on.unhealthy=true
environment:
- PUID=${PUID}
- PGID=${PGID}
- UMASK=002
- TZ=${TZ}
- WEBUI_PORT=8080
- TORRENTING_PORT=${FIREWALL_VPN_INPUT_PORTS}
volumes:
- ./qbittorrent:/config
- /data:/data
healthcheck:
test: wget -q --spider http://localhost:8080 || exit 1
interval: 60s
timeout: 10s
retries: 3
start_period: 20s
restart: unless-stopped
# ============================================================
# DEUNHEALTH — Auto-restarts unhealthy containers
# If qBittorrent loses VPN connection, this restarts it automatically.
# ============================================================
deunhealth:
image: qmcgaw/deunhealth
container_name: deunhealth
network_mode: none
environment:
- LOG_LEVEL=info
- HEALTH_SERVER_ADDRESS=127.0.0.1:9999
- TZ=${TZ}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: always
# ============================================================
# PROWLARR — Indexer Manager (runs through Gluetun VPN)
# Manages torrent/usenet indexers. Syncs to Radarr/Sonarr/Lidarr.
# ============================================================
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
network_mode: service:gluetun
depends_on:
gluetun:
condition: service_healthy
restart: true
environment:
- PUID=${PUID}
- PGID=${PGID}
- UMASK=002
- TZ=${TZ}
volumes:
- ./prowlarr:/config
restart: unless-stopped
# ============================================================
# FLARESOLVERR — Cloudflare Bypass (runs through Gluetun VPN)
# Some indexers use Cloudflare protection. This gets around it.
# ============================================================
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: flaresolverr
network_mode: service:gluetun
depends_on:
gluetun:
condition: service_healthy
restart: true
environment:
- LOG_LEVEL=info
- TZ=${TZ}
restart: unless-stopped
# ============================================================
# RADARR — Movie Manager (NOT behind VPN)
# Searches via Prowlarr, sends downloads to qBittorrent,
# renames and hard-links completed files to media folder.
# ============================================================
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- UMASK=002
- TZ=${TZ}
volumes:
- ./radarr:/config
- /data:/data
ports:
- 7878:7878
networks:
arrnetwork:
ipv4_address: ${IP_RADARR}
restart: unless-stopped
# ============================================================
# SONARR — TV Show Manager (NOT behind VPN)
# Same pattern as Radarr but for TV series.
# ============================================================
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- UMASK=002
- TZ=${TZ}
volumes:
- ./sonarr:/config
- /data:/data
ports:
- 8989:8989
networks:
arrnetwork:
ipv4_address: ${IP_SONARR}
restart: unless-stopped
# ============================================================
# LIDARR — Music Manager (NOT behind VPN)
# Optional. Comment out if you don't need music automation.
# ============================================================
lidarr:
image: lscr.io/linuxserver/lidarr:latest
container_name: lidarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- UMASK=002
- TZ=${TZ}
volumes:
- ./lidarr:/config
- /data:/data
ports:
- 8686:8686
networks:
arrnetwork:
ipv4_address: ${IP_LIDARR}
restart: unless-stopped
# ============================================================
# BAZARR — Subtitle Manager (NOT behind VPN)
# Automatically downloads subtitles for movies and TV shows.
# ============================================================
bazarr:
image: lscr.io/linuxserver/bazarr:latest
container_name: bazarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- UMASK=002
- TZ=${TZ}
volumes:
- ./bazarr:/config
- /data:/data
ports:
- 6767:6767
networks:
arrnetwork:
ipv4_address: ${IP_BAZARR}
restart: unless-stopped
# ============================================================
# JELLYFIN — Media Server (NOT behind VPN)
# Your personal streaming service. Plays movies, TV, music.
# Needs full bandwidth — never put this behind the VPN.
# ============================================================
jellyfin:
image: lscr.io/linuxserver/jellyfin:latest
container_name: jellyfin
environment:
- PUID=${PUID}
- PGID=${PGID}
- UMASK=002
- TZ=${TZ}
volumes:
- ./jellyfin:/config
- /data/media:/data/media
ports:
- 8096:8096
# Uncomment the lines below to enable hardware transcoding (Intel Quick Sync / VAAPI).
# Only works if your system has Intel/AMD integrated graphics (/dev/dri must exist).
# If you get an error about /dev/dri not found, leave these commented out.
# devices:
# - /dev/dri:/dev/dri
networks:
arrnetwork:
ipv4_address: ${IP_JELLYFIN}
restart: unless-stopped
# ============================================================
# SEERR — Request System (NOT behind VPN)
# Netflix-like UI for requesting movies and TV shows.
# Share this with family — they never need to touch Radarr.
#
# Seerr is the unified successor to Overseerr and Jellyseerr
# (merged under seerr-team). Supports Plex, Jellyfin, and Emby.
#
# Config uses a NAMED Docker volume (not a bind mount). This is
# required: Seerr runs as the `node` user (UID 1000) and a
# bind-mounted host folder is created root-owned, causing a
# permission-denied crash loop. On Windows/WSL, bind mounts also
# corrupt the SQLite DB over SMB. Named volumes fix both cases
# (matches upstream Seerr docs).
#
# Migrating from ./jellyseerr or ./seerr bind mount? See README
# troubleshooting "Migrating Seerr config to a named volume".
# ============================================================
seerr:
image: ghcr.io/seerr-team/seerr:v3.0.1
init: true
container_name: seerr
environment:
- LOG_LEVEL=info
- TZ=${TZ}
- PORT=5055
volumes:
- seerr_config:/app/config
ports:
- 5055:5055
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:5055/api/v1/settings/public || exit 1
start_period: 20s
timeout: 3s
interval: 15s
retries: 3
networks:
arrnetwork:
ipv4_address: ${IP_SEERR}
restart: unless-stopped
# ============================================================
# AUDIO BookShelf — Server (NOT behind VPN)
# Your personal streaming service. Plays all audiobooks.
# Needs full bandwidth — never put this behind the VPN.
# ============================================================
audiobookshelf:
image: ghcr.io/advplyr/audiobookshelf:latest
container_name: audiobookshelf
ports:
- 13378:80
volumes:
- /data/media/audiobookshelf/books:/audiobooks
- /data/media/audiobookshelf/podcasts:/podcasts
- /data/media/audiobookshelf/metadata:/metadata
- ./audiobookshelf/config:/config
restart: unless-stopped
networks:
arrnetwork:
ipv4_address: ${IP_AUDIO}
# ============================================================
# NAVIDROME — Music Media Server (NOT behind VPN)
# Your personal streaming service. Plays music.
# Needs full bandwidth — never put this behind the VPN.
# ============================================================
navidrome:
image: deluan/navidrome:latest
user: 1000:1000
container_name: navidrome
ports:
- 4533:4533
restart: unless-stopped
environment:
ND_SCANSCHEDULE: "1h"
ND_LOGLEVEL: info
ND_SESSIONTIMEOUT: "24h"
volumes:
- ./navi/data:/data
- /data/media/music:/music:ro
networks:
arrnetwork:
ipv4_address: ${IP_NAVI}
volumes:
seerr_config:
```
## 2. Step-by-Step qBittorrent Configuration
Once your stack is running via `docker compose up -d`, navigate to your qBittorrent interface at `http://localhost:8118` or your server's IP address (e.g., `[http://192.168.1.50:8118](http://192.168.1.50:8118)`).
### Step 2a: Log In and Find Credentials
- Look at your container logs (`docker logs qbittorrent`) to locate the **temporary password** generated automatically by linuxserver/qbittorrent for security.
- Use `admin` as the username and paste that password.
- _Recommended:_ Instantly go to **Tools > Options > Web UI** to change the password to a permanent one.
### Step 2b: Configure Connection & Port Forwarding
This ensures your traffic efficiently routes through AirVPN's system:
**1.Open Connection Settings:**Inside Web UI.
Go to **Tools** in the top navigation bar and select **Options**. In the sidebar menu that pops up, click on **Connection**.
**2.Set the Torrenting Port:**Match Gluetun mapping.
Locate **Port used for incoming connections** and type in exactly: `29261`.
**3.Disable UPnP / NAT-PMP:**Security step.
**Uncheck** the box next to _Use UPnP / NAT-PMP port forwarding from my router_. Your VPN handles port assignment, so your local router shouldn't intervene.
### Step 2c: Bind to the VPN Interface (The Hard Kill Switch)
Binding ensures that if Gluetun ever collapses, leaks, or drops connection, qBittorrent immediately stops downloading or seeding instead of trying to pass data via an unprotected network.
**1.Open Advanced Panel:**Scroll down sidebar.
While still in the Options menu, scroll down the left sidebar panel and select **Advanced**.
**2.Bind Network Interface:**Target VPN tunnel.
Look for **Network interface** near the top of the list. Change the dropdown menu from _Any interface_ to exactly **`tun0`**.
**3.Bind IP Address:**Optional but safer.
Look right underneath at the **Optional IP address to bind to** setting. Change it from _All addresses_ to **`All IPv4 addresses`**.
**4.Save and Restart:**Apply changes.
Click **Save / Apply** at the bottom right. Restart your container with `docker compose restart qbittorrent` to guarantee the network interfaces lock into place cleanly.