The Stack That Changed Everything
I knew Plex existed. I’d used it casually — point it at a folder, it plays on the TV. Fine.
Then a friend mentioned Sonarr and Radarr. "You tell it what shows you want, it finds them, downloads them, names them, puts them where Plex expects them."
I went down the rabbit hole that weekend. Prowlarr for indexers. Bazarr for subtitles. Overseerr for requests. qBittorrent as the downloader. The *Arr stack, or as I like to call it "The pirate stack" :)) — an entire automated pipeline where the only manual step is "I want to watch this" and maybe you don’t even need to do that much. Just add the shows to the watchlist in Plex and it gets handled.
Six months later, the stack runs on a GTX 1060 in a separate physical machine, mounts media via NFS from a ZFS pool on another node, and serves Plex to two households. The title of this article says "The Plex Pass Journey and the Hardlink Lie." The reality is exactly that — I ended up with Plex Pass and without hardlinks. The title isn’t clickbait. It’s a confession.
The Architecture (Recap)
Infra Node (Proxmox + ZFS)
└── /tank/media (RAID-Z) ← NFS export
Smart Node (Debian 12 bare metal, GTX 1060)
├── Docker + Portainer
│ ├── qBittorrent → downloads to /mnt/temp (local NVMe)
│ ├── Prowlarr → indexers (public + private)
│ ├── Sonarr → TV automation
│ ├── Radarr → Movie automation
│ ├── Bazarr → Subtitle automation
│ ├── Overseerr → Request portal (me + family)
│ └── Plex → Streaming (GTX 1060 NVENC with Plex Pass)
│
└── NFS mount: /tank/media from Infra Node
Key constraint: The *Arr stack runs on Smart Node. Media library lives on Infra Node’s ZFS. Smart Node mounts /tank/media via NFS. All *Arr writes go over the network.
The *Arr Stack: How It Fits Together
Prowlarr: The Indexer Brain
Prowlarr sits in the middle. It aggregates indexers — public (1337x, Nyaa, etc.) and private trackers — and feeds them to Sonarr and Radarr.
Why both public and private? Public indexers are fine for popular content. Private trackers give you retention, quality control, and speed for older/niche content. Prowlarr normalizes them into a single interface for Sonarr/Radarr.
Pro tip: Set up Prowlarr first. Add indexers, test them, tag them (e.g., "tv", "movie", "anime"). Then Sonarr/Radarr just say "use indexers tagged ‘tv’" — no per-app indexer config.
qBittorrent: The Downloader
Runs on Smart Node’s local NVMe (/mnt/temp). Why local? NFS writes during download are unreliable — network hiccups, latency, partial files. Local NVMe is fast and atomic.
Settings that matter:
- Download folder:
/mnt/temp/incomplete - Completed folder:
/mnt/temp/completed - Categories: "tv", "movie" (matches Sonarr/Radarr category mapping)
- Hardlinks: disabled in qBittorrent (handled downstream)
Sonarr + Radarr: The Brains
Quality profiles: This is where you define "what good looks like."
- HDTV-1080p for TV (remux preferred, then WEBDL, then HDTV)
- Bluray-1080p for Movies (remux preferred, then WEBDL, then Bluray)
- Custom formats: HDR, DV, Atmos, etc. — score them, not require them
Indexer settings: Use Prowlarr. Tag indexers "tv" / "movie". Sonarr uses "tv", Radarr uses "movie".
Import logic: This is where it gets interesting.
The Hardlink Dilemma (And Why It Doesn’t Work For Me)
How it should work: qBittorrent downloads to /mnt/temp. Sonarr/Radarr import via hardlink — same inode, zero disk space, instant. File stays in qBittorrent for seeding. Library gets a reference. Everyone wins.
Why it doesn’t work for me: The *Arr stack’s "hardlink" import option requires source and destination on the same filesystem. My setup:
qBittorrent downloads → /mnt/temp (Smart Node's local NVMe)
Library → /tank/media (Infra Node's ZFS, mounted via NFS)
Different filesystems = no hardlinks. The *Arr stack detects this and falls back to copy + delete. Which means:
- Read from NVMe
- Write over NFS to ZFS
- Delete from NVMe
- Double I/O, double time, double wear
The workaround I’d use if I could: Move qBittorrent downloads to a ZFS dataset on Infra Node, mount it on Smart Node. Then hardlinks work. But that means NFS writes during download — which I avoided for reliability.
What I actually do: Copy + delete. Manual cleanup when /mnt/temp fills up. Not elegant. Honest.
The Boot Race: When Smart Node Beats Infra Node
The problem: Power outage. Both nodes reboot. Smart Node (bare metal Debian) boots in ~30 seconds. Infra Node (Proxmox + ZFS + VMs) takes ~3 minutes — ZFS pool import, VM startup, Samba/NFS services.
Smart Node’s media stack starts via Docker Compose / systemd. Sonarr, Radarr, Plex, qBittorrent all try to reach /tank/media via NFS. The share doesn’t exist yet. Containers crash, enter restart loops, Sonarr marks series as "unavailable," Plex shows "media not found."
The symptom: After every power outage, I’d SSH into Smart Node, see containers restarting, manually wait for Infra Node, then restart the media stack. Sometimes Plex would lose its library scan position. Sometimes Sonarr would re-download episodes it thought were missing.
The fix: Systemd drop-in units for the media stack containers with After=network-online.target and a custom oneshot service that waits for the NFS mount.
# /etc/systemd/system/wait-for-nfs.service
[Unit]
Description=Wait for Infra Node NFS share
DefaultDependencies=no
After=network-online.target
Before=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/wait-for-nfs.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
#!/bin/bash
# /usr/local/bin/wait-for-nfs.sh
SHARED_HOST="infra-node"
SHARED_PATH="/tank/media"
MOUNT_POINT="/tank/media"
TIMEOUT=180
INTERVAL=5
echo "Waiting for $SHARED_HOST:$SHARED_PATH to be available..."
for ((i=0; i<TIMEOUT; i+=INTERVAL)); do
if showmount -e "$SHARED_HOST" 2>/dev/null | grep -q "$SHARED_PATH"; then
echo "NFS share detected, attempting mount..."
if mount "$MOUNT_POINT" 2>/dev/null; then
echo "Mount successful"
exit 0
fi
fi
sleep "$INTERVAL"
done
echo "ERROR: Timeout waiting for NFS share"
exit 1
Then add to each media stack container’s systemd unit (or Docker Compose with depends_on + condition: service_healthy):
# docker-compose.yml snippet
services:
sonarr:
depends_on:
wait-for-nfs:
condition: service_completed_successfully
Result: Smart Node boots, media stack waits. Infra Node finishes, exports NFS, wait-for-nfs succeeds, media stack starts clean. Zero manual intervention after power loss.
Lesson: In a multi-node setup, boot order matters. Don’t assume the storage node will be ready. Make the compute node wait explicitly.
Bazarr: Subtitles on Autopilot
Bazarr watches Sonarr/Radarr for new episodes/movies, searches configured subtitle providers (OpenSubtitles, Subscene, etc.), downloads the best match, and places it next to the media file.
My config:
- Languages: English, Romanian
- Preferred providers: OpenSubtitles (with VIP for rate limits), Subscene
- Auto-download on new episode/movie: yes
- Upgrade existing: only if better score (hi-res, forced subs)
The NFS caveat: Bazarr writes subtitle files over NFS. Occasional permission hiccups — usually fixed by ensuring the Docker container runs with the same UID/GID as the Samba share on Infra Node.
Overseerr: The Request Portal
Who uses it: Me + family. No friends, no public signup.
Why not just tell me? Because "add this show" via phone while on the couch > SSH into server > run commands > wait > check Plex.
Setup:
- Connected to Sonarr, Radarr, Plex
- 4K profile enabled (separate quality profile for 4K requests)
Auto-approve for me, manual for family - Notifications via n8n → Telegram (so I know when something’s grabbed)
The "4K problem": Family member requests 4K. My internet upload can’t stream 4K remux remotely. Solution: separate 4K quality profile that only grabs WEBDL/WEBRIP, not remux. Overseerr maps requests to the right profile.
The Plex Pass Journey (The Title Is a Lie)
Phase 1: No Plex Pass. Direct Play on LAN. Remote streaming = CPU transcoding on Smart Node’s Ryzen 5600. Quality: meh. Buffering: frequent. CPU: 100%.
Phase 2: Plex Remote Watch. $2/mo. Allows remote access without port forwarding. Still CPU transcoding. Slightly better.
Phase 3: Plex Pass. $5/mo (or $120 lifetime). GPU transcoding unlocked.
GTX 1060 NVENC + Plex Pass = hardware transcoding.
- CPU drops from 100% to 5%
- 1080p transcode: smooth, multiple concurrent streams
- 4K → 1080p transcode: works (NVENC supports 4K decode, 1080p encode)
- Multiple concurrent remote streams: no sweat
The cost: $120 lifetime. Worth it the first time remote streaming "just worked" on a hotel WiFi.
The Media Flow (End to End)
1. Family member requests "The Bear S03" on Overseerr (phone)
2. Overseerr → Sonarr (auto-approved for me, manual for family)
3. Sonarr → Prowlarr → indexers → finds release
4. Sonarr → qBittorrent (category: tv) → downloads to /mnt/temp/incomplete
5. qBittorrent completes → moves to /mnt/temp/completed
6. Sonarr imports: COPY (not hardlink) /mnt/temp/completed/The.Bear.S03E01.mkv → /tank/media/TV/The Bear/Season 03/The Bear S03E01.mkv (over NFS)
7. Bazarr detects new episode → downloads English + Romanian subs → places next to file
8. Plex scans library → picks up new episode + subs
9. Family member opens Plex on TV → plays → GTX 1060 transcodes if needed
10. Manual cleanup: when /mnt/temp hits 80%, I delete completed downloads
What I’d Change
| Thing | Current | Would Change |
|---|---|---|
| Hardlinks | Copy+delete over NFS | Move qBittorrent to ZFS dataset on Infra Node, mount on Smart Node → true hardlinks |
| Deletion | Manual cleanup | Automated: *Arr "delete after import" + qBittorrent "remove on seed ratio" |
| NVMe wear | Downloads + copy = 2x writes | Hardlinks = 1x write |
| NFS reliability | Occasional hiccups | Local ZFS mount (VirtIO-FS) if Smart Node ran Proxmox |
The Honest Summary
The stack works. Automated, reliable, serves two households. Family member requests a show, it appears on Plex. I barely touch it.
The compromises are real:
- No hardlinks = double I/O, manual cleanup
- NFS over network = occasional permission hiccups, latency on library scans
- GTX 1060 6 GB = no AV1 encode, 4K remux transcode not possible
- Plex Pass = recurring cost (or $120 lifetime)
Would I do it again? Yes. The alternative — manually downloading, renaming, organizing, subtitle hunting — is the stone age. The *Arr stack is the kind of automation that makes you wonder how you lived without it.
Next Week
"Remote Access Without Open Ports: Cloudflare Tunnels + Twingate (And Why Your TV Hates Both)" — zero open ports, Cloudflare Tunnels for public ingress, Twingate for private infra access, split tunneling that actually works, and why Philips TV and Google Chromecast are separate network topology problems.
Currently running: Infra Node (Proxmox 8.2, ZFS 2.2, 6 TB RAID-Z, 16 GB RAM) + Smart Node (Debian 12, Docker, GTX 1060, 32 GB RAM, Ollama, Hermes Agent). Zero cloud subscriptions.