11 KiB
type: reference title: *arr Stack description: The *arr automation stack (Prowlarr, Sonarr, Radarr, Lidarr, Readarr, qBittorrent) — FrankBook-Pro only, every container on the gluetun WireGuard netns (all egress VPN): topology, wiring, auth posture, and gotchas. tags: [arr-stack, prowlarr, sonarr, radarr, lidarr, readarr, qbittorrent, frankbook-pro, dankserver] timestamp: 2026-09-04T00:00:00Z
*arr Stack
The *arr suite is a collection of automated media management tools for Usenet and BitTorrent. It runs in exactly one place: FrankBook-Pro — the Mac mini that fronts this household's downloads — as quadlet units inside its podman machine, with every container (the five arrs AND qBittorrent) sharing the Gluetun WireGuard tunnel's network namespace so all egress exits through the VPN. A short-lived Dankserver compose copy was removed on 2026-09-04 (see below). The stack provides automatic downloading and library management that Copyparty alone does not cover: quality profile enforcement, series/movie discovery, metadata enrichment, and automated file organization.
The core chain is: Prowlarr (indexer API aggregator) → Sonarr/Radarr/Lidarr/Readarr (media-specific managers) → qBittorrent (download client, sharing the Gluetun WireGuard tunnel netns) → Jellyfin (media server scan → auto-refresh).
Service Overview
| Service | Role | Image | Port | Min RAM |
|---|---|---|---|---|
| Prowlarr | Indexer management | lscr.io/linuxserver/prowlarr:latest |
9696 | 256 MB |
| Radarr | Movie management | lscr.io/linuxserver/radarr:latest |
7878 | 512 MB |
| Sonarr | TV show management | lscr.io/linuxserver/sonarr:latest |
8989 | 512 MB |
| Lidarr | Music management | lscr.io/linuxserver/lidarr:latest |
8686 | 256 MB |
| Readarr | Ebook/audiobook management | lscr.io/linuxserver/readarr:amd64-1.0.980 (pinned — LSIO ships no untagged latest for readarr) |
8787 | 256 MB |
| qBittorrent | Torrent download client | lscr.io/linuxserver/qbittorrent:4.6.7 (pinned; see qBittorrent) |
8080 (published by the gluetun unit) | 512 MB |
Jackett and Whisparr were evaluated as optional extras but never deployed (no unit, no vhost, no live container).
Deployment shape (current)
Each service is a quadlet .container unit in this repo under
frankbook-pro/<svc>/quadlet/, installed inside the podman machine at
/home/core/.config/containers/systemd/<svc>.service by the frankbook-pro
handler in scripts/deploy-remote.sh. Every unit runs with
PUID=0 PGID=0 TZ=America/Los_Angeles UMASK_SET=022, mounts its config root at
/Volumes/COMPLETE/<svc>-data:/config, publishes its API port on the host, and
runs Restart=always with TimeoutStartSec=900.
qBittorrent is special: it shares the Gluetun container's network namespace
(Network=gluetun.container) so torrent egress never touches the residential
IP. A netns-sharer publishes no ports of its own — qB's WebUI (8080) and
BitTorrent port (6881 TCP+UDP) are published on the gluetun unit instead.
Dankserver compose copy (removed 2026-09-04)
A copy of the stack lived in dankserver/compose.yaml (/opt/<svc>/config
data roots) from 2026-08-20 to 2026-09-04 and never ran healthy: the config
dirs were created root-owned (root:root 755), so every app died at logger
init (UnauthorizedAccessException: Access to the path '/config/Sentry' is denied) before binding any port — which read as dead ports, not a boot
crash, until podman logs <svc> was checked. After a one-day revival it was
removed by operator decision: *FrankBook-Pro is the sole arr deployment.
Config roots moved to ~/.AGENTTRASH/20260904-dankserver-arr-teardown/ on
Dankserver (recoverable until purged).
Rootless identity model on Dankserver (verified, keep for reference)
- On Dankserver's podman 5.7.0 rootless, a container's default user namespace
maps container root (PUID=0) → host uid 1000 (dank) and container uid
N → host
100000 + N - 1. Verified via/proc/<pid>/uid_map(0 1000 1/1 100000 65536) across plex, tautulli, jellyfin, calibre-web, copyparty, romm. This is why PUID=0 services write asdankand why data roots are owned1000:1000on that host. - Never add
userns_mode: hostto a podman-compose service. podman-compose 1.5.0 runs every compose service inside a pod, and podman rejects the combination (--userns and --pod cannot be set together) — the stack then fails to recreate with no recourse. The default mapping already gives the fleet identity; PUID=0 +1000:1000-owned data roots is the whole recipe. (The compose file briefly carrieduserns_mode: hoston all six arr services on 2026-09-04 — reverted ine454159, same day.) - qBittorrent's first boot (PUID=0, empty config) prints a one-time WebUI
admin password in
podman logs qbittorrent— persist it via the PBKDF2 recipe in qBittorrent before restarting the container.
Access / auth posture
No *arr app sits behind the Anubis guard chain — the stack is exempt by
construction (its own auth), with an exemption comment in the Caddyfile per
vhost. Six TLS vhosts (<svc>.franksdank.cloud) terminate on Caddy on
Dankclaw and route to FrankBook-Pro's published ports
(192.168.0.112:<port>, all exposed by the gluetun unit since the netns
consolidation); the same ports are reachable directly on the LAN.
Auth-config history (verified live 2026-08-29): all five arrs shipped with
AuthenticationMethod=None plus AuthenticationRequired=Enabled. On the
v1 (.NET) apps that combo is self-contradictory — UI Save is permanently
disabled and any settings save throws DryIoc.ScopedItemException inside
AuthorizationMiddleware (500 sprawl on every page after restart). The valid
representation of "no auth" in v1 is absence of the <AuthenticationRequired>
key, not the literal string Disabled — readarr's config (key absent) booted
clean while its three siblings (key=Disabled) 500-sprawled, which is how it
was caught. Fix applied live: key deleted on sonarr/radarr/lidarr/prowlarr,
containers restarted, all five verified serving. Posture = zero-auth arrs on
LAN and WAN; the Anubis wall guards homepage/search/hermes/prometheus/romm/files
but not the arrs — first-account-wins is the current race condition if that
ever flips to External auth (needs an IdP first; auth method choice remains an
operator decision).
Chain wiring (verified live 2026-08-30)
The download chain is wired and test-first verified: indexers → Prowlarr → per-arr qBittorrent clients (category = app name) → import.
- qB webUI auth is ON (
admin+ generated password; verify before writing it into any config — it was generated at first boot, not a default). - Per-arr QBittorrent download clients exist on sonarr/radarr/lidarr
(enabled) and readarr (parked: its qB proxy fails auth live even with
correct creds — known defect of the abandoned upstream; retest needed
after the 2026-09-04 netns move). All four clients were re-pointed to
loopback (
127.0.0.1:8080, same netns) on 2026-09-04 when the arrs joined gluetun's netns — the old10.88.0.1:8080gateway hack routes into the VPN tunnel (AllowedIPs=0.0.0.0/0) and is dead from inside the netns. Verified live:podman exec sonarr curlreaches qB with 200, and a real qB login from inside the netns returns 200. - Prowlarr runs v2.5.2 where apps are first-class rows (
Applicationstable): four Application rows (sonarr/radarr/lidarr/readarr) carried each arr's real baseUrl+apiKey withSyncLevel=FullSyncas of 2026-08-30. Status 2026-09-04: the table is EMPTY again (live API check — 8 rows, all Cardigann/Torznab, zero Application rows; lost with the dankserver config migration). Prowlarr sync is broken until the four rows are rebuilt with baseUrl127.0.0.1:<arr port>(same netns). - Indexers are test-before-enable: EZTV and 1337x stay disabled with reason "blocked by CloudFlare Protection" (this host's egress ASN was CF-blocked at those trackers). 2026-09-04: arr egress moved onto the ProtonVPN tunnel — the parked reason may no longer hold; retest before enabling. Nyaa.si, sukebei, RuTor, Byrutor, YTS passed live tests and are enabled. A failed test = parked with reason, never enabled blind.
- Prowlarr v2 sync model: indexers stay in Prowlarr; arrs search through it and hand off to their own qB download client (category routing). Mappings materialize at grab time — zero mappings right after wiring is expected, not a fault.
Disk layout (current)
/Volumes/COMPLETE <- completed-media volume (Mac volume)
├── sonarr-data:/config <- per-app config roots, one per unit
├── radarr-data:/config
├── lidarr-data:/config
├── readarr-data:/config
├── qbittorrent-data:/config
└── <movie/tv/music/books roots> <- arr import roots live here
/Volumes/INCOMPLETE <- qB in-flight downloads (same path inside qB)
qB mounts both volumes at their identical host paths
(Volume=/Volumes/COMPLETE:/Volumes/COMPLETE,
Volume=/Volumes/INCOMPLETE:/Volumes/INCOMPLETE), so a category's download path
is the same string on every container that touches it — no remapping between
download and import.
Gotchas
- Timezone: every unit runs
TZ=America/Los_Angeles; Sonarr's scheduler is timezone-sensitive — keep them uniform. - File permissions:
UMASK_SET=022on qBittorrent; arr apps run PUID=0 so they can read/move qB downloads. - Network conflicts: *arr apps use many ports. On the machine VM, only the API ports (8989/7878/8686/8787/9696) and gluetun's published 8080 + 6881 are host-published; netns-sharers (qB) publish none of their own.
- Prowlarr API key rotation: If rotated, update it in every arr app (
Applicationstable rows carry the real keys). - qBittorrent WebUI password: generated at first boot, not a default — persist it via the PBKDF2 conf recipe in qBittorrent before any restart.
- Readarr stability: abandoned upstream (retired May 2026); its qB download client is parked pending a successor image. Do not rely on it for mission-critical collections.
Best Practices
- Use tags to route content to specific download clients or categories.
- Enable the Blocklist in arr apps to prevent re-downloading failed torrents.
- Set download limits in qBittorrent to avoid bandwidth saturation.
- Back up arr configs regularly (they are small SQLite DBs + JSON) — each lives at
/Volumes/COMPLETE/<svc>-data/config. - Monitor Prowlarr indexer health — broken indexers hide new content.
- Start with default quality profiles. Add custom formats gradually.