Skip to content

About

Docker Compose home media automation: *arr suite, Plex/Jellyfin, usenet+torrent behind VPN, Traefik TLS

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

media-stack

A complete self-hosted home media automation stack, wired end to end with Docker Compose: request a movie/series, the *arr suite finds and downloads it over usenet (behind VPN for torrents), organizes it into your library, and Plex/Jellyfin serve it. Traefik fronts everything with automatic Let's Encrypt TLS.

All state (config, downloads, media) lives under one DATA_ROOT directory, so the whole stack is backup- and migration-friendly.

Contents

  • 29 services: *arr suite, downloaders, media servers, utilities
  • Profiles: ai (ollama + perplexica), ddns (cloudflare-ddns)
  • Trash Guides recyclarr config template included

Requirements

  • Docker 24+ with Compose v2 (docker compose version)
  • A registered domain (called ${DOMAIN} below) on Cloudflare
  • Router port-forwards: 80/tcp + 443/tcp to this host (Traefik), 32400/tcp if you want Plex remote access
  • Optional: NVIDIA GPU + nvidia-container-toolkit for Plex/Jellyfin hardware transcoding and ollama

Quickstart

cp .env.example .env
# Edit .env: DOMAIN, ACME_EMAIL, CLOUDFLARE_DNS_TOKEN,
# PROTONVPN_WIREGUARD_PRIVATE_KEY, GLUETUN_API_KEY, PLEX_CLAIM, ROMM_* ...
docker compose up -d

Traefik obtains a wildcard cert for *.yourdomain via the Cloudflare DNS-01 challenge, so no per-service DNS or cert work is needed for new services. You need:

  1. DNS: a wildcard record *.yourdomain (and yourdomain) pointing at this host. Skip if you run the ddns profile and the host IP is dynamic.
  2. A Cloudflare API token with Zone:DNS:Edit for the zone → CLOUDFLARE_DNS_TOKEN.

Services are then at https://<name>.yourdomain (table below).

First-run setup inside each app (API keys, connecting the *arrs to sabnzbd/qbittorrent as download clients, Plex library paths) follows each app's normal UI wizard; all apps share the paths under ${DATA_ROOT} so the mappings line up.

Storage layout (DATA_ROOT, default ./data)

data/
├── appdata/<service>/        per-service config + databases
├── downloads/
│   ├── usenet/               sabnzbd complete dir (usenet/incomplete = staging)
│   └── torrents/             qbittorrent download dir
├── media/
│   ├── movies/ tv/ music/    radarr / sonarr / lidarr libraries
│   ├── audiobooks/ ebooks/ podcasts/   audiobookshelf / calibre-web / chaptarr
│   ├── roms/                 romm library
│   └── placeholders/         agregarr
├── sync/
│   ├── nzb/                  drop .nzb files → sabnzbd watch dir
│   └── torrent/              drop .torrent files → qbittorrent watch dir
└── letsencrypt/acme.json     traefik certificates

Pair sync/ with Syncthing (included) to drop files from other devices straight into the download watch folders.

Services

Service URL Host port Purpose
traefik - 80, 443, 8080 Reverse proxy + TLS (:8080 = dashboard)
cloudflare-ddns - - Dynamic DNS (profile ddns)
radarr https://radarr.${DOMAIN} 7878 Movies
sonarr https://sonarr.${DOMAIN} 8989 TV
lidarr https://lidarr.${DOMAIN} 8686 Music
bazarr https://bazarr.${DOMAIN} 6767 Subtitles
seerr https://seerr.${DOMAIN} 5055 Request UI
cleanuparr https://cleanuparr.${DOMAIN} 11011 Queue cleanup
profilarr https://profilarr.${DOMAIN} 6868 Profile management
maintainerr https://maintainerr.${DOMAIN} 6246 Library maintenance
agregarr https://agregarr.${DOMAIN} 7171 Collections
chaptarr https://chaptarr.${DOMAIN} 8789 Audiobooks + ebooks
audiobookshelf https://audiobookshelf.${DOMAIN} 13378 Audiobook/podcast server
calibre-web https://calibre.${DOMAIN} 8083 Ebook server
romm https://romm.${DOMAIN} 8090 Game library
recyclarr - (CLI) - Quality-profile sync
sabnzbd https://sabnzbd.${DOMAIN} 8080 Usenet downloader
qbittorrent (torrent) https://torrent.${DOMAIN} 8181 Torrents (via VPN)
gluetun (vpn) - - WireGuard VPN gateway
plex https://app.plex.tv or http://<host>:32400 32400 (host net) Media server
jellyfin https://jellyfin.${DOMAIN} 8096 Media server
tautulli https://tautulli.${DOMAIN} 8182 Plex watch stats
syncthing https://sync.${DOMAIN} 8384, 22000, 21027 File sync
nextdash https://dash.${DOMAIN} - Bookmark dashboard
searxng https://search.${DOMAIN} 8082 Meta-search
searxng-valkey - - searxng rate-limiter store
ollama - 11434 LLM runtime (profile ai)
perplexica https://perplexica.${DOMAIN} 3002 AI search UI (profile ai)

https://* URLs work once Traefik is up with the wildcard cert. Ports in the table are container defaults; only the host side can be remapped via env (e.g. TAUTULLI_PORT).

VPN (torrent traffic)

torrent (qbittorrent) shares the vpn (gluetun) network namespace, so all its traffic exits through the tunnel. The default provider is ProtonVPN WireGuard:

  1. At https://account.proton.me/u/0/vpn/WireGuard generate a config with NAT-PMP (Port Forwarding) enabled — needed for the port-forward sync.
  2. Copy the PrivateKey into PROTONVPN_WIREGUARD_PRIVATE_KEY.
  3. openssl rand -base64 36 → GLUETUN_API_KEY (secures gluetun's control server, used by the qBittorrent port-sync mod).

Gluetun supports many other providers (mullvad, nordvpn, surfshark, ...): change VPN_SERVICE_PROVIDER and the matching vars in compose/downloaders.yaml — see the gluetun wiki.

Plex

  • Uses host networking (required for remote access / NAT-PMP).
  • PLEX_CLAIM is only needed for a brand-new server claim (https://www.plex.tv/claim/); leave blank afterwards.
  • Add ${DATA_ROOT}/media as your library inside Plex.
  • Hardware transcoding needs an NVIDIA GPU; on CPU-only hosts remove runtime: nvidia and the NVIDIA_* env lines (same for jellyfin).

Recyclarr (quality profiles)

mkdir -p "${DATA_ROOT}/appdata/recyclarr/configs"
cp config-templates/recyclarr/recyclarr.yml       "${DATA_ROOT}/appdata/recyclarr/configs/"
cp config-templates/recyclarr/secrets.yml.example "${DATA_ROOT}/appdata/recyclarr/configs/secrets.yml"
# edit secrets.yml with your Radarr/Sonarr API keys
docker compose run --rm recyclarr sync

Profiles come from Trash Guides by trash_id. The recyclarr image tag is pinned (8.8.0): its config model is version-coupled, do not float it casually.

Optional profiles

# Dynamic DNS updater (dynamic-IP hosts)
docker compose --profile ddns up -d

# Local AI search (ollama + perplexica; GPU recommended)
docker compose --profile ai up -d

GPU notes

plex, jellyfin, and ollama carry runtime: nvidia. On hosts without an NVIDIA GPU + nvidia-container-toolkit, delete those lines (and the NVIDIA_* env vars) or the containers will fail to start. Everything else is CPU-only.

Security notes

  • The Traefik dashboard (--api.insecure=true) is unauthenticated on :8080. Fine on a trusted LAN; on an internet-facing host remove that flag and the 8080:8080 publish.
  • searxng, perplexica, jellyfin, and nextdash (token-protected writes only) have no SSO. If you expose them beyond your LAN, put your own forward-auth middleware in front of them.
  • All secrets live in .env / secrets.yml, both gitignored.

Updating

docker compose pull && docker compose up -d
docker system prune   # optional cleanup

License

MIT

About

Docker Compose home media automation: *arr suite, Plex/Jellyfin, usenet+torrent behind VPN, Traefik TLS

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors