Media Server
This is all the media services running on my NAS. The OS/physical setup for that can be found at Server Setup: NAS.
I just run it all via a single Docker Compose file. Compose file found below.
Covers the streaming server, the Arr stack for automated media management, and a VPN-routed download client. Further configuration for most services is documented at TRaSH Guides.
-
Media server and streaming. Hardware transcoding via Intel QuickSync.
-
Torrent client. Runs behind Gluetun — all traffic routed through ProtonVPN.
-
VPN gateway container. qBittorrent and Qbit Manage use its network stack.
-
Automated movie downloads, library management, and quality upgrades.
-
Automated TV show downloads and library management.
-
Indexer manager. Connects to Radarr and Sonarr as a single source of indexers.
-
Media request interface. Users can request titles for automatic download.
-
Automated torrent management — tagging, categories, and cleanup.
Pages to write
Plex, Prowlarr, and Qbit Manage do not have guide pages yet.
Docker Compose
Note
No configuration change should be needed to the compose file. All configuration should be able to be accomplished via the .env file. Adjust those settings as needed and then run docker compose up -d to start the media server stack.
compose.yaml
services:
### ---------------------------------- ###
### ---------- Streaming ------------- ###
### ---------------------------------- ###
### --- Plex Media Server --- ###
plex:
image: lscr.io/linuxserver/plex:latest
container_name: plex
network_mode: host
environment:
- PUID=${PUID}
- PGID=${GUID}
- TZ=${TZ}
- VERSION=docker
- PLEX_CLAIM=${PLEX_CLAIM}
ports:
- 32400:32400
devices:
- /dev/dri:/dev/dri #Required for plex HW transcoding / QuickSync (Intel 8th gen or newer)
volumes:
- ${APPDATA_PATH}/plex/config:/config # config data - home nvme
- ${TORRENT_PATH}:/data/torrents
- ${APPDATA_PATH}/plex/transcode:/transcode #t ranscode temp data - appdata SSD
# - /dev/shm:/transcode - # transcode to ram (if available)
- ${MEDIA_DATA}/media:/data/media # media access only - mergerfs pool
restart: unless-stopped
### -------------------------------- ###
### ----------- Arr Stack ---------- ###
### -------------------------------- ###
#Radarr - used to find movies automatically
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
environment:
- PUID=${PUID}
- PGID=${GUID}
- TZ=${TZ}
volumes:
- ${APPDATA_PATH}/radarr/config:/config
- ${MEDIA_DATA}:/data #Access to the entire data
ports:
- 7878:7878
restart: unless-stopped
#Sonarr - used to find tv shows automatically
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
environment:
- PUID=${PUID}
- PGID=${GUID}
- TZ=${TZ}
volumes:
- ${APPDATA_PATH}/sonarr/config:/config
- ${MEDIA_DATA}:/data #Access to the entire data
ports:
- 8989:8989
restart: unless-stopped
#Prowlarr - manages your Sonarr, Radarr and download client
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
environment:
- PUID=${PUID}
- PGID=${GUID}
- TZ=${TZ}
volumes:
- ${APPDATA_PATH}/prowlarr/config:/config
ports:
- 9696:9696
restart: unless-stopped
# Seerr - allows users to request media on their own [Supercedes Overseerr]
seerr:
image: ghcr.io/seerr-team/seerr:latest
init: true
container_name: seerr
environment:
- PUID=${PUID}
- PGID=${GUID}
- TZ=${TZ}
- LOG_LEVEL=debug
- PORT=5055 # Optional
volumes:
- ${APPDATA_PATH}/seerr/config:/app/config
- ${MEDIA_DATA}:/data #Access to the entire data
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:5055/api/v1/status || exit 1
start_period: 20s
timeout: 3s
interval: 15s
retries: 3
ports:
- 5055:5055
restart: unless-stopped
### -------------------------------------- ###
### ----------- Download W/ VPN ---------- ###
### -------------------------------------- ###
### --- Qbittorrent: Torrent Client --- ###
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
depends_on:
gluetun:
condition: service_healthy
environment:
- PUID=${PUID}
- PGID=${GUID}
- TZ=${TZ}
- WEBUI_PORT=8080
# This torrenting port is enabled by default. Gluetun handles this here, so it is disabled.
# - TORRENTING_PORT=8694 # Make sure to port forward this port in your router so you can seed more effectively
volumes:
- ${MEDIA_DATA}:/data
- ${APPDATA_PATH}/qbittorrent/config:/config
restart: unless-stopped
network_mode: "service:gluetun"
qbit_manage:
container_name: qbit_manage
image: ghcr.io/stuffanthings/qbit_manage:latest
volumes:
- ${APPDATA_PATH}/qbit_manage/:/config:rw
- ${MEDIA_DATA}:/data:rw
- ${APPDATA_PATH}/qbittorrent/config:/qbittorrent/:ro
ports:
- "8084:8080" # Web API port (when enabled) - Default 8080 changed to 8084 to avoid conflict with qbittorrent's web UI
environment:
# Web API Configuration
- QBT_WEB_SERVER=true # Set to true to enable web API and web UI
- QBT_PORT=8080 # Web API port (default: 8080)
# Scheduler Configuration
- QBT_RUN=false
- QBT_SCHEDULE=1440
- QBT_CONFIG_DIR=/config
- QBT_LOGFILE=qbit_manage.log
# Command Flags
- QBT_RECHECK=false
- QBT_CAT_UPDATE=false
- QBT_TAG_UPDATE=false
- QBT_REM_UNREGISTERED=false
- QBT_REM_ORPHANED=false
- QBT_TAG_TRACKER_ERROR=false
- QBT_TAG_NOHARDLINKS=false
- QBT_SHARE_LIMITS=false
- QBT_SKIP_CLEANUP=false
- QBT_DRY_RUN=false
- QBT_STARTUP_DELAY=0
- QBT_SKIP_QB_VERSION_CHECK=false
- QBT_DEBUG=false
- QBT_TRACE=false
# Logging Configuration
- QBT_LOG_LEVEL=INFO
- QBT_LOG_SIZE=10
- QBT_LOG_COUNT=5
- QBT_DIVIDER==
- QBT_WIDTH=100
restart: on-failure:2
### --- Glutun: VPN for Downloading --- ###
# https://www.reddit.com/r/gluetun/comments/1kpbfs2/the_definitive_howto_for_setting_up_protonvpn/
gluetun:
image: qmcgaw/gluetun:v3
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
- 8080:8080/tcp # qbittorrent
environment:
- TZ=${TZ}
- UPDATER_PERIOD=24h
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=${VPN_TYPE}
- BLOCK_MALICIOUS=off
- OPENVPN_USER=${OPENVPN_USER}
- OPENVPN_PASSWORD=${OPENVPN_PASSWORD}
- OPENVPN_CIPHERS=AES-256-GCM
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
- PORT_FORWARD_ONLY=on
- VPN_PORT_FORWARDING=on
- VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c 'wget -O- --retry-connrefused --post-data "json={\"listen_port\":{{PORTS}}}" http://127.0.0.1:8080/api/v2/app/setPreferences 2>&1'
- SERVER_COUNTRIES=${SERVER_COUNTRIES}
volumes:
- ${APPDATA_PATH}/gluetun/config:/gluetun
restart: unless-stopped
.env
APPDATA_PATH=/path/to/appdata
MEDIA_DATA=/root/path/to/mediapool
TORRENT_PATH=/path/to/torrents
PUID=1000
GUID=1000
TZ=America/Chicago
PLEX_CLAIM=claim-xxxxxx # Optional - Get this from https://www.plex.tv/claim/ to link your plex server to your account automatically on first run.
### GLUETUN VPN SETTINGS ###
# Fill in either the OpenVPN or Wireguard sections. The choice of vpn is made with VPN_TYPE. Choose 'wireguard' or 'openvpn'. The settings for the other vpn type will be ignored.
# Alter the TZ, MEDIA_DIR, and SERVER_COUNTRIES to your preference. Run 'docker run --rm -v eraseme:/gluetun qmcgaw/gluetun format-servers -protonvpn' to get a list of server countries
# Gluetun config
VPN_TYPE=wireguard #openvpn # Choose 'wireguard' or 'openvpn'. The settings for the other vpn type will be ignored.
SERVER_COUNTRIES=United States
# OpenVPN config
OPENVPN_USER=username+pmp
OPENVPN_PASSWORD=password
# Wireguard config (example key)
WIREGUARD_PRIVATE_KEY=yourprivatekey