Bridge networks are host-local, so the two stacks don't actually share one; Unpackerr's hardcoded host IP is the correct cross-host approach, not a workaround pending removal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| README.md | ||
arrframework
Overview and combined reference for the self-hosted media automation stack. This repo doesn't run anything itself — it documents how mediaserver, mediastack, and recyclarr fit together, and holds a snapshot of their configs.
The three repos
| Repo | Role |
|---|---|
mediastack |
Core *arr stack: indexers, download automation, requests, dashboard |
mediaserver |
Media playback + post-processing: transcoding, streaming, download client, monitoring |
recyclarr |
Config-as-code for Sonarr/Radarr quality profiles & custom formats (TRaSH Guides sync) |
mediastack and recyclarr run together on one host (a container/LXC); mediaserver runs on a separate VM. Within a single host's compose stack, services share a bridge network (named mediastack) and can reach each other by container name (e.g. sonarr, prowlarr). Across hosts, though, bridge networks aren't shared — mediaserver's services reach mediastack's services over the host network via IP address (e.g. Unpackerr's 10.10.88.11 URLs), not by container name.
Architecture / request flow
Seerr (5055) ──requests──▶ Sonarr (8989) / Radarr (7878) / Lidarr (8686)
│ metadata & search
▼
Prowlarr (9696) ──indexers──▶ FlareSolverr (8191, cf-bypass)
│ sends grab to
▼
SABnzbd (8080) ──downloads land in /mnt/samba/downloads
│
▼
Unpackerr (extracts archives, notifies *arr apps)
│
▼
Sonarr/Radarr import ──▶ /mnt/unifipronas/{movies,series,anime,music}
│
▼
Bazarr (6767, subtitles)
│
▼
Tdarr (8265, transcode/optimize on library)
│
▼
Jellyfin (8096, host network, plays final files)
Homarr (7575) — dashboard over everything
Recyclarr — periodically pushes TRaSH quality profiles/custom formats into Sonarr & Radarr
Watchtower — auto-updates containers nightly (04:00), disabled via label on *arr apps
node-exporter (9100) — host metrics for Prometheus, present in both stacks
mediastack — indexers, *arr apps, requests, dashboard
File: mediastack/docker-compose.yml, env: mediastack/.env.example
Services (all on the mediastack bridge network, config in /mnt/appdata/<service>):
- homarr (
:7575) — dashboard, readsdocker.sock(ro) to show container status. - flaresolverr (
:8191, localhost-only) — Cloudflare bypass proxy used by Prowlarr. - prowlarr (
:9696) — indexer manager, feeds Sonarr/Radarr/Lidarr. Depends on flaresolverr being healthy. - sonarr (
:8989) — TV/anime management. Media at/mnt/unifipronas/{series,anime}, downloads at/mnt/samba/downloads. - radarr (
:7878) — movie management. Media at/mnt/unifipronas/movies. - lidarr (
:8686) — music management. Media at/mnt/unifipronas/music. - bazarr (
:6767) — subtitle fetching for Sonarr/Radarr libraries. - seerr (
:5055) — request UI (Jellyseerr/Overseerr-style), talks to Sonarr/Radarr. - recyclarr — no ports, config synced from
/mnt/appdata/recyclarr; see the recyclarr section below. - watchtower — auto-updates images nightly,
com.centurylinklabs.watchtower.enable=falselabel opts the *arr apps out (so recyclarr-managed config doesn't get clobbered by surprise upgrades). - node-exporter (
:9100) — Prometheus host metrics.
Env vars needed (.env, copy from .env.example):
PUID=1000
PGID=1000
TZ=Etc/UTC
HOMARR_SECRET_KEY= # random secret for homarr encryption
SONARR_API_KEY= # from Sonarr → Settings → General, also used by recyclarr
RADARR_API_KEY= # from Radarr → Settings → General, also used by recyclarr
Bring up: docker compose up -d from mediastack/.
mediaserver — transcoding, streaming, download client, monitoring
File: mediaserver/docker-compose.yml, env: mediaserver/.env.example
- tdarr_master (
:8265, web UI/API:8266) — transcode scheduling. Uses Intel/AMD GPU passthrough (/dev/dri) for hardware transcode (ffmpegVersion=7, iHD driver). - tdarr_node — worker attached to
tdarr_masterover themediastacknetwork; also has GPU access. Waits for master to be healthy. - sabnzbd (
:8080) — Usenet download client; downloads land in/mnt/samba/downloads. - unpackerr — watches SABnzbd's completed downloads, extracts archives, and notifies Sonarr/Radarr/Lidarr to import. Talks to the *arr apps by host IP (
10.10.88.11), not container name — this is the correct approach here, since the *arr apps run on a different host (themediastackcontainer/LXC) and Docker bridge networks don't span hosts. Switching to container-name DNS would require adding real cross-host networking (e.g. Tailscale, a macvlan/overlay network, or a reverse proxy). - jellyfin (
:8096,network_mode: host) — media server/player, hardware-accelerated (iHD,/dev/dri,video/992groups). - watchtower — same nightly auto-update pattern as in
mediastack(separate instance, no cross-stack shared state). - node-exporter (
:9100) — same as mediastack's; note running both stacks together would port-conflict on 9100 — only run one node-exporter per host.
Env vars needed (.env, copy from .env.example):
TZ=America/New_York
SONARR_API_KEY=
RADARR_API_KEY=
LIDARR_API_KEY=
Bring up: docker compose up -d from mediaserver/. There's a known TODO in the compose file: volume paths mix two conventions (/home/resixh/appdata/... per-app config vs /mnt/... shared media/cache) — intentionally left alone to avoid breaking running state; consolidate only during a planned maintenance window.
recyclarr — quality profile / custom format sync
Config-only repo (no compose file of its own — it runs as the recyclarr service inside mediastack's compose, image ghcr.io/recyclarr/recyclarr:8).
recyclarr.yml— main config. For each ofsonarr.seriesandradarr.movies:base_url(http://sonarr:8989,http://radarr:7878— container-name DNS on themediastacknetwork) andapi_key(via!env_var SONARR_API_KEY/RADARR_API_KEY).quality_definition— min/preferred/max size thresholds per resolution/source tier (720p–2160p, HDTV/WEBDL/WEBRip/Bluray).quality_profiles— either pulled wholesale from TRaSH Guides viatrash_id(e.g."HD Series (EN+NL)","HD Movies (EN+NL)","4K Movies (EN+NL)"), or defined fully inline for custom needs (anime dual-audio/dub profiles with hand-tunedupgrade/quality_sort/qualities).custom_formats— score assignments per profile, referencing formats bytrash_id: real TRaSH guide hex IDs (commented with their human name, e.g.418f50b10f1907201b6cfdf881f467b7 # Anime Dual Audio) or local IDs (cf-block-german, etc.) matching files undercustom-formats/.
settings.yml— declaresresource_providerspointing recyclarr at/config/custom-formats/{radarr,sonarr}inside the container so the local JSON custom formats get loaded alongside TRaSH's.custom-formats/{radarr,sonarr}/*.json— locally-authored custom formats not in the official TRaSH guide:anime-dual-audio.json,block-{french,german,portuguese,spanish}.json. Each hastrash_id,name,specifications(e.g.LanguageSpecification,ReleaseTitleSpecification). If a format should apply to both apps, matching files exist under bothcustom-formats/radarr/andcustom-formats/sonarr/.
What it actually does: on each run, recyclarr sync pushes the quality definitions, quality profiles, and custom-format scores from these YAML/JSON files into the live Sonarr/Radarr instances via their API — so profile tuning is version-controlled here instead of clicked through the web UI.
Validate without touching the live instances:
recyclarr config validate # schema-check recyclarr.yml, no network needed
recyclarr sync --preview # dry-run against Sonarr/Radarr, shows planned changes
Requires SONARR_API_KEY / RADARR_API_KEY env vars and network access to a running Sonarr/Radarr.
Putting it all together: deployment order
mediastack: set.env, rundocker compose up -d. Wait for Sonarr/Radarr to come up, grab their API keys from Settings → General.recyclarr: drop the API keys into the environment therecyclarrservice inmediastackreads from (same.env), then either let its container run on schedule ordocker compose run --rm recyclarr synconce to seed profiles/formats.mediaserver: set.envwith the same Sonarr/Radarr/Lidarr keys (used by Unpackerr), rundocker compose up -don themediaserverVM. Sonarr/Radarr/Lidarr run on the separatemediastackhost, so Unpackerr reaches them by host IP (10.10.88.11) rather than container name — that's expected given the two-host topology, not a placeholder.- Point Prowlarr (in
mediastack) at your indexers, and Sonarr/Radarr/Lidarr at Prowlarr and at SABnzbd (inmediaserver) as the download client. - Confirm both stacks'
node-exporterisn't double-bound on:9100— only run one.
Shared conventions across all three
PUID/PGID=1000,TZset per-service via env.- Media library root:
/mnt/unifipronas/{movies,series,anime,music}(mediastack) — notemediaserver's Tdarr/Jellyfin instead use/mnt/unifipronas/jelly/{movies,series,anime,music}, a different path for the same content; worth confirming these actually point at the same data or are two separate mount conventions before assuming parity. - Downloads land in
/mnt/samba/downloads, shared between SABnzbd, Unpackerr, and the *arr apps. - Per-app config under
/mnt/appdata/<service>(mediastack) or/home/resixh/appdata/<service>(mediaserver) — again two different conventions, not yet reconciled. - Watchtower auto-updates nightly at 04:00 in both stacks; *arr apps opt out via
com.centurylinklabs.watchtower.enable=falseso recyclarr-managed state isn't disrupted by a surprise version bump mid-cycle.