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.
- 29 services: *arr suite, downloaders, media servers, utilities
- Profiles:
ai(ollama + perplexica),ddns(cloudflare-ddns) - Trash Guides recyclarr config template included
- 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-toolkitfor Plex/Jellyfin hardware transcoding and ollama
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 -dTraefik 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:
- DNS: a wildcard record
*.yourdomain(andyourdomain) pointing at this host. Skip if you run theddnsprofile and the host IP is dynamic. - A Cloudflare API token with
Zone:DNS:Editfor 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.
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.
| 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).
torrent (qbittorrent) shares the vpn (gluetun) network namespace, so all
its traffic exits through the tunnel. The default provider is ProtonVPN
WireGuard:
- At https://account.proton.me/u/0/vpn/WireGuard generate a config with NAT-PMP (Port Forwarding) enabled — needed for the port-forward sync.
- Copy the
PrivateKeyintoPROTONVPN_WIREGUARD_PRIVATE_KEY. 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.
- Uses host networking (required for remote access / NAT-PMP).
PLEX_CLAIMis only needed for a brand-new server claim (https://www.plex.tv/claim/); leave blank afterwards.- Add
${DATA_ROOT}/mediaas your library inside Plex. - Hardware transcoding needs an NVIDIA GPU; on CPU-only hosts remove
runtime: nvidiaand theNVIDIA_*env lines (same for jellyfin).
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 syncProfiles 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.
# Dynamic DNS updater (dynamic-IP hosts)
docker compose --profile ddns up -d
# Local AI search (ollama + perplexica; GPU recommended)
docker compose --profile ai up -dplex, 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.
- The Traefik dashboard (
--api.insecure=true) is unauthenticated on:8080. Fine on a trusted LAN; on an internet-facing host remove that flag and the8080:8080publish. searxng,perplexica,jellyfin, andnextdash(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.
docker compose pull && docker compose up -d
docker system prune # optional cleanup