Files
torrent-stack/media.yml
T

518 lines
14 KiB
YAML
Raw Normal View History

# =============================================================================
# Torrent Stack (media) - VPN, *arr stack & media tooling
#
# Split from compose.yml on 2026-09-11 - verbatim service blocks.
# Shared top-level networks:/volumes: are repeated in each subfile so it also
# works standalone (docker compose -f media.yml up -d). Identical copies merge
# cleanly under compose.yml's `include:` with no change to the live config.
# Keep ./ paths, container names and /mnt data as-is.
# =============================================================================
networks:
#caddy_net:
# external: true
tdarr-net:
driver: bridge
default:
name: portainer_network
volumes:
#caddy_config:
#caddy_data:
# external: true
#myspeed:
# external: true
portainer_data:
name: portainer_data
redis-data:
driver: local
services:
# ---------------------------------------------------------------------------
# VPN & Download
# ---------------------------------------------------------------------------
gluetun:
image: ${GLUETUN_IMAGE}
container_name: gluetun
env_file:
- .env
- ./env/.gluetun.env
cap_add:
- NET_ADMIN
ports:
- 8877:8000/tcp
- ${QBITTORRENT_PORT}:${QBITTORRENT_PORT}
- ${PROWLARR_PORT}:${PROWLARR_PORT}
- ${SONARR_PORT}:${SONARR_PORT}
- ${RADARR_PORT}:${RADARR_PORT}
- ${LIDARR_PORT}:${LIDARR_PORT}
- ${BAZARR_PORT}:${BAZARR_PORT}
volumes:
- ./gluetun-data:/tmp/gluetun:rw
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=wireguard
- HTTP_CONTROL_SERVER=ON
- WIREGUARD_MTU=1280
- VPN_PORT_FORWARDING=on
- VPN_PORT_FORWARDING_PROVIDER=protonvpn
- SERVER_COUNTRIES=Netherlands
- NETWORK_LOCAL_ADDRESSES=192.168.50.0/24,172.16.0.0/12
- FIREWALL_OUTBOUND_SUBNETS=192.168.50.0/24
- HEALTH_SERVER_ADDRESS=0.0.0.0:8877
- HEALTH_TARGET_ADDRESSES=${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}
- TZ=${TIMEZONE}
- DNS_UPSTREAM_RESOLVER_TYPE=doh
healthcheck:
test:
[
"CMD-SHELL",
"wget -qO- '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m30s
timeout: 15s
retries: 3
start_period: 45s
security_opt:
- no-new-privileges:true
mem_limit: 256m
cpus: 1.0
devices:
- /dev/net/tun:/dev/net/tun
restart: no
qbittorrent:
image: ${QBITTORRENT_IMAGE}
container_name: qbittorrent
network_mode: "container:gluetun"
depends_on:
gluetun:
condition: service_healthy
volumes:
- ./qbittorrent:/config
- ${DISK1}:${DISK1}
- ${DISK2}:${DISK2}
- ./gluetun-data:/tmp/gluetun
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- TZ=${TIMEZONE}
- WEBUI_PORT=${QBITTORRENT_PORT}
labels:
- "autoheal=true"
healthcheck:
test:
[
"CMD-SHELL",
"curl -sf '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m
timeout: 10s
retries: 3
start_period: 30s
security_opt:
- no-new-privileges:true
mem_limit: 512m
cpus: 1.0
restart: unless-stopped
port-updater:
image: curlimages/curl:8.21.0
container_name: port-updater
network_mode: "container:gluetun"
depends_on:
gluetun:
condition: service_healthy
volumes:
- ./gluetun-data:/tmp/gluetun:ro
entrypoint: ["/bin/sh", "-c"]
command:
- |
trap 'exit 0' SIGTERM;
while true; do
while [ ! -s /tmp/gluetun/forwarded_port ]; do
echo "Waiting for Gluetun to provide a port..."
sleep 5
done
read -r PORT_VAL < /tmp/gluetun/forwarded_port;
CLEAN_PORT=$$(echo "$$PORT_VAL" | tr -d '\r\n ');
if [ -n "$$CLEAN_PORT" ]; then
echo "Updating qBit to port: $$CLEAN_PORT";
sleep 10;
curl -s -X POST -d "json={\"listen_port\":$$CLEAN_PORT}" http://localhost:${QBITTORRENT_PORT}/api/v2/app/setPreferences;
sleep 3600 & wait $$!;
else
sleep 10
fi
done
labels:
- "autoheal=true"
healthcheck:
test:
[
"CMD-SHELL",
"curl -sf '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m
timeout: 10s
retries: 3
start_period: 30s
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
mem_limit: 64m
cpus: 0.25
restart: unless-stopped
# ---------------------------------------------------------------------------
# *arr Stack
# ---------------------------------------------------------------------------
prowlarr:
image: ${PROWLARR_IMAGE}
container_name: prowlarr
network_mode: "container:gluetun"
depends_on:
gluetun:
condition: service_healthy
volumes:
- ./prowlarr:/config
- ./notify.sh:/notify.sh:ro
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- TZ=${TIMEZONE}
labels:
- "autoheal=true"
healthcheck:
test:
[
"CMD-SHELL",
"curl -sf '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m
timeout: 10s
retries: 3
start_period: 30s
security_opt:
- no-new-privileges:true
mem_limit: 256m
cpus: 1.0
restart: unless-stopped
sonarr:
image: ${SONARR_IMAGE}
container_name: sonarr
network_mode: "container:gluetun"
depends_on:
gluetun:
condition: service_healthy
volumes:
- ./sonarr:/config
- ${DISK1}:${DISK1}
- ${DISK2}:${DISK2}
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- TZ=${TIMEZONE}
labels:
- "autoheal=true"
healthcheck:
test:
[
"CMD-SHELL",
"curl -sf '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m
timeout: 10s
retries: 3
start_period: 60s
security_opt:
- no-new-privileges:true
mem_limit: 256m
cpus: 1.0
restart: unless-stopped
radarr:
image: ${RADARR_IMAGE}
container_name: radarr
network_mode: "container:gluetun"
depends_on:
gluetun:
condition: service_healthy
volumes:
- ./radarr:/config
- ${DISK1}:${DISK1}
- ${DISK2}:${DISK2}
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- TZ=${TIMEZONE}
labels:
- "autoheal=true"
healthcheck:
test:
[
"CMD-SHELL",
"curl -sf '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m
timeout: 10s
retries: 3
start_period: 30s
security_opt:
- no-new-privileges:true
mem_limit: 256m
cpus: 1.0
restart: unless-stopped
lidarr:
image: ${LIDARR_IMAGE}
container_name: lidarr
network_mode: "container:gluetun"
depends_on:
gluetun:
condition: service_healthy
volumes:
- ./lidarr:/music
- ./lidarr:/config
- ${DISK1}:${DISK1}
- ${DISK2}:${DISK2}
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- TZ=${TIMEZONE}
labels:
- "autoheal=true"
healthcheck:
test:
[
"CMD-SHELL",
"curl -sf '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m
timeout: 10s
retries: 3
start_period: 30s
security_opt:
- no-new-privileges:true
mem_limit: 256m
cpus: 1.0
restart: unless-stopped
bazarr:
image: ${BAZARR_IMAGE}
container_name: bazarr
network_mode: "container:gluetun"
depends_on:
gluetun:
condition: service_healthy
volumes:
- ./bazarr:/config
- ${DISK1}:${DISK1}
- ${DISK2}:${DISK2}
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- TZ=${TIMEZONE}
labels:
- "autoheal=true"
healthcheck:
test:
[
"CMD-SHELL",
"curl -sf '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m
timeout: 10s
retries: 3
start_period: 30s
security_opt:
- no-new-privileges:true
mem_limit: 256m
cpus: 1.0
restart: unless-stopped
# ---------------------------------------------------------------------------
# Media
# ---------------------------------------------------------------------------
tautulli:
image: ${TAUTULLI_IMAGE}
container_name: tautulli
#networks:
# - caddy_net
ports:
- ${TAUTULLI_PORT}:${TAUTULLI_PORT}
volumes:
- ./tautulli:/config
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- TZ=${TIMEZONE}
security_opt:
- no-new-privileges:true
mem_limit: 256m
cpus: 1.0
restart: unless-stopped
tdarr:
image: ${TDARR_IMAGE}
container_name: tdarr
networks:
- tdarr-net
ports:
- ${TDARR_PORT}:${TDARR_PORT}
- ${TDARR_NODE_PORT}:${TDARR_NODE_PORT}
volumes:
- ./tdarr/server:/app/server
- ./tdarr/configs:/app/configs
- ./tdarr/temp:/temp
- ${DISK1}:${DISK1}
- ${DISK2}:${DISK2}
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- TZ=${TIMEZONE}
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- UMASK_SET=002
- serverIP=0.0.0.0
security_opt:
- no-new-privileges:true
mem_limit: 512m
cpus: 2.0
restart: unless-stopped
tdarr-node:
image: ${TDARR_NODE_IMAGE}
container_name: tdarr-node
networks:
- tdarr-net
volumes:
- ./tdarr/configs:/app/configs
- ./tdarr/logs:/app/logs
- ./tdarr/temp:/temp
- ${DISK1}:${DISK1}
- ${DISK2}:${DISK2}
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- TZ=${TIMEZONE}
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
- nodeID=${TDARR_NODE_ID}
- nodeIP=0.0.0.0
- serverIP=tdarr
- serverPort=${TDARR_NODE_PORT}
security_opt:
- no-new-privileges:true
mem_limit: 4g
cpus: 4.0
devices:
- /dev/dri:/dev/dri
restart: unless-stopped
seerr:
image: ${SEERR_IMAGE}
container_name: seerr
# networks:
# - caddy_net
ports:
- ${SEERR_PORT}:${SEERR_PORT}
volumes:
- ./seerr:/app/config
#- /etc/localtime:/etc/localtime:ro
#- /etc/timezone:/etc/timezone:ro
environment:
- LOG_LEVEL=info
- TZ=${TIMEZONE}
- PUID=${HOST_PUID:-1000}
- PGID=${HOST_PGID:-1000}
labels:
- "autoheal=true"
init: true
working_dir: "/app"
healthcheck:
test:
[
"CMD-SHELL",
"wget -qO- '${WEB_PROTOCOL}://${EXTERNAL_IPV4}:${PLEX_PFWD_PORT}/identity' | grep -q 'MediaContainer' || exit 1",
]
interval: 1m
timeout: 10s
retries: 3
start_period: 60s
security_opt:
- no-new-privileges:true
mem_limit: 256m
cpus: 1.0
restart: unless-stopped
spotizerr:
image: spotizerrphoenix/spotizerr
user: "1000:1000" # Spotizerr user:group ids
volumes:
# Ensure these directories and the .cache file exist and are writable by the container user
- ./spotizerr:/app/data # data directory, contains config, creds, watch, history
- ${DISK1}/media/Music/spotizerr:/app/downloads # downloads directory, contains downloaded files
- ./spotizerr/logs:/app/logs # logs directory, contains logs
- ./spotizerr/.cache:/app/.cache # cache file
ports:
# Port to expose the app on
- 7171:7171
container_name: spotizerr
restart: unless-stopped
env_file:
# Ensure you have a .env file in the root of the project, with the correct values
- ./.env
depends_on:
- redis
redis:
image: redis:alpine
container_name: spotizerr-redis
restart: unless-stopped
env_file:
- .env
volumes:
- redis-data:/data
command: sh -c 'redis-server --requirepass "$REDIS_PASSWORD" --appendonly yes'
# Anubis anti-bot proxy for Seerr (uncomment to enable):
#seerr-anubis:
# image: ghcr.io/techarohq/anubis:latest
# container_name: seerr-anubis
# networks:
# - caddy_net
# environment:
# - BIND=:55055
# - TARGET=http://seerr:${SEERR_PORT}
# security_opt:
# - no-new-privileges:true
# mem_limit: 128m
# cpus: 0.5
# restart: unless-stopped