echo6-docs/projects/arr-stack-runbook.md
Matt Johnson e9231ac24a Migration: consolidate Echo6 docs to cortex with full infrastructure cleanup sync
- Documents recent infrastructure cleanup (8 CTs destroyed, 35 DNS records removed, Headscale cleanup)
- Adds 24 new runbooks covering Authentik, PeerTube, Meshtastic, RECON, Proxmox, Mailcow, Internet Archive, GPU routing
- Adds project documentation for headscale, vaultwarden, peertube, matrix, mmud, advbbs, arr stack
- Updates services.md, environment.md, caddy.md, authentik.md to match live infrastructure
- Removes 4 deprecated runbook duplicates (canonical versions live in projects/)
- Adds .gitignore for binary archives and editor temp files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 06:02:16 +00:00

9.1 KiB

CC Runbook: Build ARR Media Stack on Proxmox media Node

Objective

Build a complete media automation stack on the Proxmox node media inside a single Ubuntu VM called arr. Each service runs in its own Docker container with a shared bridge network for inter-service communication. All services are exposed on the VM's LAN IP on their respective ports.

Services:

  • Jellyfin (media server, software transcoding — no GPU)
  • Jellyseer (request management)
  • Sonarr (TV automation)
  • Radarr (Movie automation)
  • Prowlarr (indexer manager)
  • SABnzbd (Usenet download client)

Phase 0: SSH Prereq Check

CRITICAL — Do this first. Do not skip.

ssh media "echo 'SSH OK to media node'"

If this fails, stop and fix SSH access before proceeding. Use sshpass or key auth per ~/.ssh/config. Cortex is the management host — all commands originate from here.


Phase 1: Create Ubuntu VM on media

  1. SSH to media Proxmox node.
  2. Find the next available VMID: pvesh get /cluster/nextid
  3. Download Ubuntu 24.04 cloud image if not already cached:
    • URL: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
    • Store in appropriate Proxmox storage.
  4. Create a VM named arr with:
    • Network: bridged to the LAN bridge (likely vmbr0)
    • Resource allocation: Decide based on the combined needs of all six services. Jellyfin (software transcoding) and SABnzbd (decompression) are the heaviest. Sonarr/Radarr/Prowlarr/Jellyseer are lightweight. Size the VM accordingly — suggest at minimum 4 cores and 8GB RAM, but use your judgment.
    • Disk: 30GB for OS + container configs (media lives on NFS)
    • Cloud-init configured with:
      • Default user: zvx
      • SSH key from cortex (discover from ~/.ssh/id_rsa.pub or equivalent)
      • Networking: DHCP or static — check the pattern of other VMs on this node and match it
  5. Start the VM, wait for boot, discover and record its LAN IP.
  6. Verify SSH from cortex → arr VM works.

Phase 2: Base System Setup on arr VM

SSH into the arr VM:

  1. apt update && apt upgrade -y
  2. Install Docker + Docker Compose via the official Docker apt repo for Ubuntu.
  3. Install NFS client: apt install -y nfs-common
  4. Install Tailscale and join the tailnet:
    • curl -fsSL https://tailscale.com/install.sh | sh
    • tailscale up — use an auth key if available. Check how other VMs joined (look at Headscale config if self-hosted).
    • Record the Tailscale IP of the arr VM.
  5. Discover appropriate PUID/PGID:
    • Mount the NFS share temporarily and ls -ln to check file ownership.
    • If no files exist, create a media user/group (e.g., PUID=1000, PGID=1000) and ensure NFS permissions align.

Phase 3: NFS Mount

  1. Discover the NFS server:
    • The NFS export is /export/arr, accessible from 100.64.0.0/10 (Tailscale) and 192.168.1.0/24 (LAN).
    • Find the NFS server IP by checking:
      • /etc/fstab on other VMs on this node
      • showmount -e <candidate IPs> on LAN
      • Proxmox storage config: pvesm status or /etc/pve/storage.cfg
  2. mkdir -p /mnt/arr
  3. mount -t nfs <NFS_SERVER>:/export/arr /mnt/arr
  4. Create subdirectories if they don't exist:
    mkdir -p /mnt/arr/{movies,tv,downloads,downloads/complete,downloads/incomplete}
    
  5. Set ownership to discovered PUID:PGID on all subdirs.
  6. Add to /etc/fstab for persistence:
    <NFS_SERVER>:/export/arr /mnt/arr nfs defaults,_netdev 0 0
    
  7. Verify: umount /mnt/arr && mount -a && ls /mnt/arr

Phase 4: Docker Containers

Setup

mkdir -p /opt/arr/{jellyfin,jellyseer,sonarr,radarr,prowlarr,sabnzbd}

Create a Docker bridge network for inter-service communication:

docker network create arr-net

Container Deployment

Deploy each service as its own standalone container. All containers join arr-net. All get TZ=America/Boise and the discovered PUID/PGID.

Decide per-container resource limits (CPU shares, memory limits) based on service needs:

  • Heavy: Jellyfin (transcoding), SABnzbd (decompression) — allocate more CPU/RAM
  • Medium: Sonarr, Radarr — moderate
  • Light: Prowlarr, Jellyseer — minimal

Use lightweight images (hotio where available, official otherwise).

Jellyfin

  • Image: jellyfin/jellyfin:latest
  • Container name: jellyfin
  • Port: 8096:8096
  • Volumes:
    • /opt/arr/jellyfin/config:/config
    • /mnt/arr/movies:/data/movies:ro
    • /mnt/arr/tv:/data/tv:ro
  • Network: arr-net
  • Restart: unless-stopped

Jellyseer

  • Image: fallenbagel/jellyseer:latest
  • Container name: jellyseer
  • Port: 5055:5055
  • Volumes:
    • /opt/arr/jellyseer/config:/app/config
  • Network: arr-net
  • Restart: unless-stopped

Sonarr

  • Image: ghcr.io/hotio/sonarr:latest
  • Container name: sonarr
  • Port: 8989:8989
  • Volumes:
    • /opt/arr/sonarr/config:/config
    • /mnt/arr:/data
  • Network: arr-net
  • Restart: unless-stopped

Radarr

  • Image: ghcr.io/hotio/radarr:latest
  • Container name: radarr
  • Port: 7878:7878
  • Volumes:
    • /opt/arr/radarr/config:/config
    • /mnt/arr:/data
  • Network: arr-net
  • Restart: unless-stopped

Prowlarr

  • Image: ghcr.io/hotio/prowlarr:latest
  • Container name: prowlarr
  • Port: 9696:9696
  • Volumes:
    • /opt/arr/prowlarr/config:/config
  • Network: arr-net
  • Restart: unless-stopped

SABnzbd

  • Image: ghcr.io/hotio/sabnzbd:latest
  • Container name: sabnzbd
  • Port: 8080:8080
  • Volumes:
    • /opt/arr/sabnzbd/config:/config
    • /mnt/arr/downloads:/data/downloads
  • Network: arr-net
  • Restart: unless-stopped

Volume Mapping Design

Sonarr and Radarr both map /mnt/arr:/data so hardlinks/atomic moves work between /data/downloads/complete and /data/movies or /data/tv without cross-filesystem copies. This is critical for avoiding double disk usage.

Verify

All six containers are running: docker ps Curl each service on localhost to confirm they respond on their expected ports.


Phase 5: Authentik OIDC Setup

Discovery: Find the Authentik instance.

  • Check Caddy config on utility for an existing Authentik route (likely auth.echo6.co or authentik.echo6.co).
  • Discover the Authentik API URL and obtain/create an API token from Authentik's docker-compose environment or admin API.

Jellyfin OIDC

  1. Create OAuth2/OpenID Provider in Authentik:
    • Name: jellyfin, Client type: Confidential
    • Redirect URI: https://jellyfin.echo6.co/sso/OID/redirect/Authentik
    • Scopes: openid profile email
    • Signing key: use existing or create
  2. Create Application: Name Jellyfin, slug jellyfin, attach provider.
  3. Record Client ID + Secret.
  4. Install SSO-Auth plugin in Jellyfin and configure with Authentik OIDC details (discovery URL, client ID, secret).

Jellyseer OIDC

  1. Create OAuth2/OpenID Provider in Authentik:
    • Name: jellyseer, Client type: Confidential
    • Redirect URI: https://requests.echo6.co/api/v1/auth/oidc-callback (verify actual callback path from Jellyseer docs)
    • Scopes: openid profile email
  2. Create Application: Name Jellyseer, slug jellyseer, attach provider.
  3. Record Client ID + Secret.
  4. Configure Jellyseer OIDC via its settings.

Phase 6: Caddy Reverse Proxy on utility

SSH to utility. Discover the Caddyfile location and how Caddy is managed (docker, systemd, etc.).

Add entries using the Tailscale IP of the arr VM as the upstream:

jellyfin.echo6.co {
    reverse_proxy <ARR_TAILSCALE_IP>:8096
}

requests.echo6.co {
    reverse_proxy <ARR_TAILSCALE_IP>:5055
}

Do NOT expose Sonarr, Radarr, Prowlarr, or SABnzbd via Caddy. Those are internal-only, accessible via Tailscale or LAN.

Reload Caddy.


Phase 7: GoDaddy DNS

Discovery: Check if GoDaddy API key/secret exists on cortex or utility. Look at how existing echo6.co subdomains are configured for the pattern.

Create A records (via API if available, otherwise output for manual creation):

Type Name Value TTL
A jellyfin Public IP of Caddy/utility (discover) 600
A requests Public IP of Caddy/utility (discover) 600

These are publicly exposed WITHOUT Tailscale. Caddy handles TLS via Let's Encrypt. The upstream uses the Tailscale IP but DNS points to the public-facing Caddy IP.


Phase 8: Validation

  1. From arr VM, curl all six services on localhost (ports 8096, 5055, 8989, 7878, 9696, 8080)
  2. curl -sI https://jellyfin.echo6.co → 200 with valid TLS
  3. curl -sI https://requests.echo6.co → 200 with valid TLS
  4. Authentik OIDC login works for both Jellyfin and Jellyseer
  5. NFS persists after reboot: reboot, wait, df -h /mnt/arr
  6. All containers auto-start after reboot: docker ps shows all six running

Important Notes

  • Do NOT configure Prowlarr indexers, Sonarr/Radarr API connections, or SABnzbd Usenet provider credentials. That will be done in a separate prompt.
  • All discovery steps are intentional — do not hardcode IPs or paths. Find them dynamically from the running infrastructure.
  • If any phase fails, stop and report the error. Do not skip phases.