431 lines
14 KiB
Markdown
431 lines
14 KiB
Markdown
---
|
|
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. |