A premium, fully-parameterized Docker Compose stack for deploying a self-hosted media server containing Plex, qBittorrent (VPN secured), and the Arr Suite (radarr, sonarr, plusarr, prowlarr, decypharr, flaresolverr, gluetun).
To optimize performance and ease of use, this stack uses a hybrid network topology:
- Plex (Macvlan): Placed directly on your local network (
my_macvlan_net) with a static IP. This enables native DLNA/local discovery and direct, unthrottled media streaming without traversing Docker bridge routing. - Arr Suite (Internal Bridge): Isolated within a custom private network (
arr_net). The containers communicate securely via service names (e.g.,http://radarr:7878), while their user interfaces are safely mapped to individual host ports.
graph TD
subgraph Host Storage
DATA_ROOT["/volume1/portainer (DATA_ROOT)"]
SSD_CONF["/volume2/SSD/docker (DOCKER_CONFIG_DIR)"]
end
subgraph Container Networking [arr_net]
direction TB
subgraph Internal Bridge
radarr[Radarr]
sonarr[Sonarr]
prowlarr[Prowlarr]
decypharr[Decypharr]
gluetun[Gluetun VPN]
qbittorrent[qBittorrent]
pulsarr[Pulsarr]
end
subgraph Local Network
plex[Plex - Static IP on Macvlan]
end
end
%% Storage connections
DATA_ROOT -->|Mapped as /data| plex
DATA_ROOT -->|Mapped as /data| radarr
DATA_ROOT -->|Mapped as /data| sonarr
DATA_ROOT -->|Mapped as /data| qbittorrent
DATA_ROOT -->|decypharr_mnt Mapped as /mnt:rshared| decypharr
DATA_ROOT -->|decypharr_mnt Mapped as /mnt:rslave| plex
DATA_ROOT -->|decypharr_mnt Mapped as /mnt:rslave| radarr
DATA_ROOT -->|decypharr_mnt Mapped as /mnt:rslave| sonarr
%% VPN routing
qbittorrent -->|network_mode| gluetun
gluetun -->|All traffic encrypted| WAN((Internet))
decypharr -->|Direct HTTPS| RD((Real-Debrid))
%% Automation & Indexing flows
pulsarr -->|watches playlist| plex
prowlarr -->|supplies index| radarr
prowlarr -->|supplies index| sonarr
Follow these steps sequentially to prepare your host system, configure environment variables, and launch the media stack.
Before deploying, make sure the directories for container storage and configurations exist on your host system (e.g., Synology NAS, OMV, or generic Linux server).
Create the following directories on your host:
# Configuration & Application Data directory (usually on SSD for performance)
# Note: Plex transcoder uses RAM (/dev/shm) and does not require an SSD directory
mkdir -p /volume2/SSD/docker/decypharr/config
mkdir -p /volume2/SSD/docker/radarr/data
mkdir -p /volume2/SSD/docker/sonarr/data
mkdir -p /volume2/SSD/docker/pulsarr/data
mkdir -p /volume2/SSD/docker/prowlarr/data
mkdir -p /volume2/SSD/docker/flaresolver
mkdir -p /volume2/SSD/docker/qbittorrent
# Shared Media & Downloads directory (usually on high-capacity HDD storage)
mkdir -p /volume1/portainer/torrent/download
mkdir -p /volume1/portainer/torrent/decypharr_mnt
mkdir -p /volume1/portainer/Plex/media/Movies
mkdir -p /volume1/portainer/Plex/media/TvShowsDocker containers run with specific user accounts (PUID=1000, PGID=10 by default). You must assign correct ownership and write permissions on the host system to prevent "Permission Denied" errors in Plex or the Arr suite:
# Assign ownership of the media libraries to PUID 1000 / PGID 10
sudo chown -R 1000:10 /volume1/portainer/Plex/media/Movies
sudo chown -R 1000:10 /volume1/portainer/Plex/media/TvShows
sudo chown -R 1000:10 /volume1/portainer/torrent/download
# Grant full read, write, and execute permissions to the owner group
sudo chmod -R g+rwx /volume1/portainer/Plex/media/Movies
sudo chmod -R g+rwx /volume1/portainer/Plex/media/TvShows
sudo chmod -R g+rwx /volume1/portainer/torrent/downloadCreate a file named .env in the same directory as your docker-compose.yml and populate it with your specific system values:
# ==============================================================================
# Docker Compose Environment Variables
# ==============================================================================
# Plex Claim Token (Obtain from https://www.plex.tv/claim)
PLEX_CLAIM=claim-xxxxxxxxxxxxxxxxx
# Static IP for Plex on your local macvlan subnet
PLEX_IP=192.168.4.125
# Base path for container configs & databases (Ideally SSD)
DOCKER_CONFIG_DIR=/volume2/SSD/docker
# Common data root directory for media & downloads (Enables hardlinks/instant moves)
DATA_ROOT=/volume1/portainer
# ==============================================================================
# NordVPN & Gluetun VPN Configuration
# ==============================================================================
# Retrieve manual service credentials from your NordVPN dashboard
VPN_SERVICE_PROVIDER=nordvpn
VPN_TYPE=openvpn
OPENVPN_USER=YOUR_NORDVPN_SERVICE_USERNAME
OPENVPN_PASSWORD=YOUR_NORDVPN_SERVICE_PASSWORD
# To use Wireguard instead, set VPN_TYPE=wireguard and fill below:
WIREGUARD_PRIVATE_KEY=
# Specify target country/server (e.g. Netherlands, United Kingdom)
SERVER_COUNTRIES=NetherlandsOnce your .env is updated and directories are prepared, boot the containers in the background using Docker Compose:
# Start the stack
docker compose up -dThis stack uses a hybrid approach (local storage + debrid symlinks). To support both, configure your applications as follows:
In Radarr/Sonarr > Settings > Download Clients:
- Local Downloader (qBittorrent):
- Add a qBittorrent client.
- Set Host to
gluetun(since qBittorrent runs inside Gluetun's network namespace). - Set Port to
8080. - Assign the Tag
local.
- Debrid Downloader (Decypharr):
- Add a qBittorrent client.
- Set Host to
decypharr. - Set Port to
8282. - Assign the Tag
debrid.
Add two distinct root library directories to choose from:
- Local Path:
/data/Plex/media/Movies(or/TvShows) β for media you want to store and seed locally. - Debrid Path:
/mnt/symlinks/Movies(or/TvShows) β for media you want to stream via Debrid symlinks.
When monitoring or adding media in Radarr/Sonarr, assign the tag debrid to route the torrent to Decypharr (cloud streaming), or the tag local to route it to qBittorrent (local storage and seeding).
In your Plex Library settings, point your libraries to both local and debrid folders to merge them:
- Movies Library: Add
/data/Plex/media/MoviesAND/mnt/symlinks/Movies. - TV Shows Library: Add
/data/Plex/media/TvShowsAND/mnt/symlinks/TvShows.
Once deployed, the services will be accessible at the following local addresses:
| Service | Address / URL | Internal Port | Description |
|---|---|---|---|
| Plex | http://192.168.4.125:32400/web |
32400 |
Media Server Web Portal |
| Decypharr | http://<DOCKER_HOST_IP>:8282 |
8282 |
Blackhole & Cache Interface |
| qBittorrent | http://<DOCKER_HOST_IP>:8080 |
8080 |
Local Torrent Client (VPN Routed) |
| Radarr | http://<DOCKER_HOST_IP>:7878 |
7878 |
Movies Management |
| Sonarr | http://<DOCKER_HOST_IP>:8989 |
8989 |
TV Show/Series Management |
| Plusarr (Pulsarr) | http://<DOCKER_HOST_IP>:3003 |
3003 |
Torrent Search/Proxy |
| Prowlarr | http://<DOCKER_HOST_IP>:9696 |
9696 |
Indexer Management |
| Flaresolverr | http://<DOCKER_HOST_IP>:8191 |
8191 |
Cloudflare Bypass Proxy |
Tip
If you encounter issues where Plex or Arr applications don't have read/write access to media folders (resulting in "Permission Denied" errors), run this check on the host system:
ls -ltr /volume1/portainer/Plex/media/Check which user and group owns the target folders. The PUID and PGID in docker-compose.yml (default 1000 / 10) must align with the owner of the files on the host system.