docker-compose.yml
Rama main · réplica de lectura del repositorio original.
services:
gluetun:
image: qmcgaw/gluetun
container_name: gluetun
labels:
- com.centurylinklabs.watchtower.enable=false
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
# Ports bind to ALL interfaces so Tailnet clients (tailscale0) reach the admin UIs as well as the
# LAN; binding to MEDIA_LAN_IP only breaks Tailscale access. The perimeter relies on UFW off +
# Tailnet Lock on, no public-routable host interface, and admin UI auth where configured. On a
# host with a public interface, add `${MEDIA_LAN_IP}:HOST:CONTAINER` bindings (and one for the
# Tailscale IP).
ports:
- 8090:8090
# No static peer-port publish: inbound peers arrive on the port Proton allocates, which gluetun
# opens in its own firewall.
- 9696:9696
- 8989:8989
- 7878:7878
- 6767:6767
- 8192:8191
dns:
- 8.8.8.8
- 1.1.1.1
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY:?WIREGUARD_PRIVATE_KEY missing — see .env.example}
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESS:?WIREGUARD_ADDRESS missing — see .env.example}
- SERVER_COUNTRIES=${VPN_COUNTRY:-Switzerland}
# Health checks target IPs, not hostnames: gluetun's default targets resolve through its OWN DNS
# at 127.0.0.1:53, so a resolver hiccup fails liveness while the tunnel is healthy. Port 443 still
# does a full TLS handshake, so it stays a real end-to-end check, and tunnel enforcement is
# unaffected (both IPs route via tun0, OUTPUT defaults to DROP, neither is inside
# FIREWALL_OUTBOUND_SUBNETS). The permanent-unhealthy state is believed to be an upstream
# health-checker deadlock (HEALTH_RESTART_VPN never fires again), fixed in v3.41.1. Watchtower
# stays disabled for gluetun (label above): a restart drops the netns for seven containers.
- HEALTH_TARGET_ADDRESSES=1.1.1.1:443,8.8.8.8:443
# ProtonVPN port forwarding (without it qBittorrent is outbound-only). PORT_FORWARD_ONLY is
# REQUIRED: Proton forwards only on its P2P servers and SERVER_COUNTRIES alone does not prefer
# them. A NAT-PMP-ENABLED WIREGUARD KEY IS ALSO REQUIRED (Proton hands the port out over NAT-PMP,
# udp/5351): generate it with NAT-PMP on and Moderate NAT off, on a Standard (not Secure Core)
# server, then RECREATE gluetun, which does not retry a failed allocation. The port is DYNAMIC, so
# no static port belongs anywhere in this file; sync-forwarded-port.py reconciles qBittorrent's
# listen port on a timer.
- VPN_PORT_FORWARDING=on
- PORT_FORWARD_ONLY=on
# gluetun's default :::8000 control server is reachable on the container IP and the auth file
# grants GET /v1/portforward with auth = "none", so pin it to loopback.
- HTTP_CONTROL_SERVER_ADDRESS=127.0.0.1:8000
# LAN + Tailnet CGNAT range (100.64.0.0/10). Inbound to published ports works via conntrack, but
# containers in gluetun's namespace that initiate outbound to Tailnet-only services (e.g. host
# webhooks via 100.x) need this allowlist. Override the LAN part via .env.
- FIREWALL_OUTBOUND_SUBNETS=${FIREWALL_OUTBOUND_SUBNETS:-10.0.0.0/24,100.64.0.0/10}
# Grants the one control-server route sync-forwarded-port.py polls; /gluetun/auth/config.toml is
# gluetun's default path.
volumes:
- ./ops/gluetun/auth-config.toml:/gluetun/auth/config.toml:ro
restart: unless-stopped
networks:
- internal-media
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent-dvr
network_mode: service:gluetun
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
- WEBUI_PORT=8090
volumes:
- ./config/qbittorrent:/config
- ./data/torrents:/downloads
restart: unless-stopped
depends_on:
- gluetun
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr-dvr
network_mode: service:gluetun
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
volumes:
- ./config/prowlarr:/config
restart: unless-stopped
depends_on:
- gluetun
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
network_mode: service:gluetun
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
volumes:
- ./config/sonarr:/config
- ${MEDIA_ROOT:-${MEDIA_STACK_ROOT:-$HOME/media-stack}/data/media}/tv:/tv
- ./data/torrents:/downloads
restart: unless-stopped
depends_on:
- gluetun
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
network_mode: service:gluetun
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
volumes:
- ./config/radarr:/config
- ${MEDIA_ROOT:-${MEDIA_STACK_ROOT:-$HOME/media-stack}/data/media}/movies:/movies
- ./data/torrents:/downloads
restart: unless-stopped
depends_on:
- gluetun
bazarr:
image: lscr.io/linuxserver/bazarr:latest
container_name: bazarr
network_mode: service:gluetun
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
volumes:
- ./config/bazarr:/config
- ${MEDIA_ROOT:-${MEDIA_STACK_ROOT:-$HOME/media-stack}/data/media}/tv:/tv
- ${MEDIA_ROOT:-${MEDIA_STACK_ROOT:-$HOME/media-stack}/data/media}/movies:/movies
restart: unless-stopped
depends_on:
- gluetun
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: flaresolverr-dvr
network_mode: service:gluetun
environment:
- LOG_LEVEL=info
- TZ=${TZ:-UTC}
restart: unless-stopped
depends_on:
- gluetun
unpackerr:
image: golift/unpackerr
container_name: unpackerr
environment:
- UN_SONARR_0_URL=http://localhost:8989
- UN_SONARR_0_API_KEY=${SONARR_API_KEY:?SONARR_API_KEY missing — see .env.example}
- UN_RADARR_0_URL=http://localhost:7878
- UN_RADARR_0_API_KEY=${RADARR_API_KEY:?RADARR_API_KEY missing — see .env.example}
volumes:
- ./data/torrents:/downloads
restart: unless-stopped
network_mode: service:gluetun
depends_on:
- gluetun
watchtower:
# nickfedor/watchtower is the maintained fork: upstream containrrr is abandoned and its Docker API
# client (1.25) is below the daemon's 1.44 minimum.
image: nickfedor/watchtower:latest
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_POLL_INTERVAL=86400
restart: unless-stopped
networks:
- internal-media
tdarr:
container_name: tdarr
image: ghcr.io/haveagitgat/tdarr:latest
restart: unless-stopped
ports:
- 8265:8265
- 8266:8266
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
- serverIP=0.0.0.0
- disableAnalytics=true
volumes:
- ${MEDIA_STACK_ROOT:-$HOME/media-stack}/config/tdarr:/app/server
- ${MEDIA_STACK_ROOT:-$HOME/media-stack}/config/tdarr_configs:/app/configs
- ${MEDIA_ROOT:-${MEDIA_STACK_ROOT:-$HOME/media-stack}/data/media}:/media
networks:
- internal-media
tdarr-node:
container_name: tdarr-node
image: ghcr.io/haveagitgat/tdarr_node:latest
restart: unless-stopped
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
- nodeID=Balls3-NVIDIA
- serverIP=tdarr
- serverPort=8266
- disableAnalytics=true
volumes:
- ${MEDIA_STACK_ROOT:-$HOME/media-stack}/config/tdarr_node:/app/configs
- ${MEDIA_ROOT:-${MEDIA_STACK_ROOT:-$HOME/media-stack}/data/media}:/media
- /tmp:/temp
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities:
- gpu
depends_on:
- tdarr
networks:
- internal-media
jellyfin:
container_name: jellyfin
image: jellyfin/jellyfin:latest
restart: unless-stopped
ports:
- 8096:8096
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
# Default to the LAN IP: a placeholder tailnet name would advertise a non-resolvable host.
- JELLYFIN_PublishedServerUrl=${JELLYFIN_PUBLISHED_URL:-${MEDIA_LAN_IP:-10.0.0.10}}
volumes:
- ${MEDIA_STACK_ROOT:-$HOME/media-stack}/config/jellyfin:/config
- ${MEDIA_ROOT:-${MEDIA_STACK_ROOT:-$HOME/media-stack}/data/media}:/data
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities:
- gpu
networks:
- internal-media
jellyseerr:
image: ghcr.io/seerr-team/seerr:latest
container_name: jellyseerr
restart: unless-stopped
networks:
- internal-media
ports:
- 5055:5055
environment:
- LOG_LEVEL=debug
- TZ=${TZ:-UTC}
volumes:
- ${MEDIA_STACK_ROOT:-$HOME/media-stack}/config/jellyseerr:/app/config
# readarr: removed (upstream abandoned); config remains at ./config/readarr.
kavita:
image: lscr.io/linuxserver/kavita:latest
container_name: kavita
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-UTC}
volumes:
- ./config/kavita:/config
- ${CALIBRE_LIBRARY:-/srv/calibre/library}:/books
restart: unless-stopped
# Direct publish, not via gluetun: a mapping on gluetun would point host:5000 at its empty namespace.
ports:
- 5000:5000
networks:
- internal-media
networks:
internal-media:
driver: bridge