Skip to content

Lackoftactics/uncompressed

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

uncompressed

My arr stack. Hardened Docker Compose config for Jellyfin + Sonarr/Radarr + qBittorrent with VPN namespace isolation and zero-trust ingress.

I run this on Unraid. It took a few months to get the networking right — most guides just slap a firewall rule on the VPN and call it a day. I wanted actual isolation, not "it probably works." Here's what I landed on.

My family uses Seerr to request movies/shows and Infuse on Apple TV to watch them.

Jellyfin media library Radarr movie management Sonarr TV show management

Prerequisites

  • Docker + Compose

  • Tailscale account. Open these ports in your Tailscale ACL for the host running this stack: tcp:80, tcp:443 (Traefik, bound to your Tailscale IP) and tcp:8096 (Jellyfin direct, for LAN clients like Infuse / Apple TV). Nothing else is published to the host.

  • ProtonVPN account with WireGuard keys (P2P-enabled servers in NL/CH).

  • Domain on Cloudflare DNS with a scoped API token (not the Global API Key). Create the token at dash.cloudflare.com → My Profile → API Tokens with these permissions on the target zone:

    • Zone → Zone → Read
    • Zone → DNS → Edit

    This is the minimum required for the ACME DNS-01 challenge. See .env.example for the full variable list.

Quick Start

cp .env.example .env              # fill in secrets, domain, Tailscale IP, WG keys, CF token
ln -s ../.env arr/.env            # each compose stack reads its own .env
ln -s ../.env infra/.env
docker network create traefik_proxy

cd infra && docker compose up -d  # traefik first
cd ../arr && docker compose up -d # media pipeline (9 containers)

Each compose file declares env_file: ./.env, resolved relative to its own directory — so arr/docker-compose.yml needs arr/.env. Symlinking keeps one source of truth at the repo root.

Networking & Security

This is the part that's actually interesting. The services themselves are standard — the value is in how they're wired together.

VPN namespace isolation — qBittorrent doesn't just "use" the VPN. It runs inside gluetun's network namespace (network_mode: service:gluetun), meaning it shares gluetun's entire network stack. The container has no network interface of its own. An init script (10-config.sh) additionally forces BIND_TO_INTERFACE: tun0 as defense in depth. If the VPN drops, there is no path for traffic to take — it's a kernel boundary, not a firewall rule that could be misconfigured.

No published ports, with one exception — only Jellyfin publishes :8096 to the host so LAN clients (Infuse, Apple TV) can hit it directly. Everything else is reachable only through Traefik over the Docker network — there's no way to hit Sonarr/Radarr/Prowlarr/etc. by going to host:port and bypassing TLS + security headers.

Tailscale-only ingress — Traefik binds to ${TAILSCALE_IP}:443, not 0.0.0.0:443. You must be on the Tailscale mesh to reach any service. No ports face the public internet. HTTPS certs are auto-renewed via Cloudflare DNS challenge.

Three isolated networkstraefik_proxy for HTTPS ingress, arr_internal (marked internal: true) for service-to-service, vpn_network for tunnel traffic. Port forwarding from ProtonVPN is automatic — gluetun gets the forwarded port and pushes it to qBittorrent's API.

Architecture

Media architecture — dockerized solution

Cloudflare is used only as the ACME DNS-01 challenge target for cert renewal — control plane, not in the user-traffic path.

What's in the stack

arr/ — Gluetun, qBittorrent, Jellyfin, Sonarr, Radarr, Prowlarr, Seerr, Bazarr, Autoheal. Every container has an endpoint-specific health check. Autoheal restarts anything that fails. depends_on: service_healthy prevents cascading startup issues.

infra/ — Traefik v2.10.7 reverse proxy with auto HTTPS via Cloudflare DNS challenge.

License

MIT

About

My arr stack. Hardened Docker Compose config for Jellyfin + Sonarr/Radarr + qBittorrent with VPN namespace isolation and zero-trust ingress.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages