The Media Automation Layer
Welcome to Part 4 of my Home Server Chronicles.
In Part 3, I covered the infrastructure backbone (Portainer, VS Code Server, monitoring, and backups). In this part, I'm focusing on the media stack that runs on top of that foundation:
Jellyfin for streaming (~1.4TB movies, ~920GB TV)
Sonarr for TV automation
Radarr for movie automation
Lidarr + Navidrome for music (~165GB)
Audiobookshelf for audiobooks/podcasts
Prowlarr for indexer management
qBittorrent + Gluetun for VPN-tunneled downloads
Jellyseerr for media requests
Bazarr for subtitles
A clean workflow for quality profiles, folder structure, and reliability
Stack Design Goals
My media layer is built around a few strict rules:
Single source of truth for paths across all containers
Predictable naming + folder conventions to avoid broken imports
Minimal manual intervention after initial setup
Safe defaults for updates and restarts
At a high level, every service sits in the same Docker network and shares the same mounted media paths.
Folder Strategy (Most Important)
This is where most self-hosted media stacks break.
If paths differ between Sonarr/Radarr/Jellyfin/download clients, automation fails.
My structure is standardized:
/mnt/internal_ssd/media/
├── incomplete/
├── movies/ # ~1.4TB
├── tv/ # ~920GB
├── music/ # ~165GB
└── audiobooks/
All media lives on the 3.6TB internal NVMe SSD (/mnt/internal_ssd). Container mounts follow the same target path pattern:
movies -> /tv or /movies (consistent across Sonarr/Radarr/Jellyfin)
music -> /music (Lidarr/Navidrome)
audiobooks -> /audiobooks (Audiobookshelf)
This keeps import/move operations deterministic.
Jellyfin Configuration
Jellyfin is my playback and metadata front-end.
Why Jellyfin?
Fully open-source
Hardware acceleration support
Clean multi-user setup
Great plugin ecosystem without lock-in
Compose baseline
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
volumes:
- /home/jay739/docker_services/jellyfin/config:/config
- /home/jay739/docker_services/jellyfin/cache:/cache
- /mnt/internal_ssd/media/movies:/movies
- /mnt/internal_ssd/media/tv:/tv
- /mnt/internal_ssd/media/music:/music
ports:
- "${HOST_IP:-10.0.0.101}:8096:8096"
- "100.89.188.84:8096:8096"
networks:
- media_net
- proxy_net
labels:
- "com.centurylinklabs.watchtower.enable=true"
Practical tuning I use
Library scans scheduled (not constant)
Conservative transcoding defaults
Metadata language and image providers aligned for consistency
Sonarr + Radarr + Lidarr
These services run the acquisition and organization loop.
Sonarr (TV)
Profiles per show type (anime / standard)
Root folder: /tv
Imports from download client via hardlinks when possible
Radarr (Movies)
Separate quality profiles for archive vs high-priority titles
Root folder: /movies
Custom formats kept simple to reduce bad matches
Lidarr (Music)
Root folder: /music
Automation enabled but with stricter matching to avoid noisy releases
Prowlarr (Indexer Management)
Centralized indexer configuration shared across all *arr apps
Eliminates per-app indexer setup — add once, sync everywhere
Bazarr (Subtitles)
Automated subtitle downloads for TV and movies
Multiple subtitle providers configured for fallback
qBittorrent + Gluetun (VPN Downloads)
All download traffic routed through Gluetun VPN container
qBittorrent container shares Gluetun's network stack
Ensures download traffic is always VPN-tunneled
Navidrome (Music Streaming)
Dedicated music streaming server (separate from Lidarr's acquisition role)
Subsonic API compatible — works with mobile apps
Reads directly from /mnt/internal_ssd/media/music
Audiobookshelf
Audiobook and podcast management
Reads from /mnt/internal_ssd/media/audiobooks
Jellyseerr (Media Requests)
Clean request UI for users to request movies/TV shows
Integrates with Sonarr/Radarr for automated fulfillment
Automation Flow
My end-to-end flow is:
User requests via Jellyseerr (or directly in Sonarr/Radarr/Lidarr)
Prowlarr provides indexer results to the *arr app
Grab request sent to qBittorrent (routed through Gluetun VPN)
Download completes, *arr app imports + renames + moves into final library folder
Bazarr fetches subtitles for the new content
Jellyfin scans and exposes content for streaming
If paths are aligned, this is mostly hands-off.
Reliability & Operations
A few operational practices make this stable long-term:
restart: unless-stopped on all media services
Health checks and uptime alerts from monitoring stack
Staggered updates (don't update all services at once)
Backup of app configs and DB files before upgrades
For maintenance windows, I update automation services first, then Jellyfin after validation.
Monitoring Signals I Watch
From my monitoring layer, I care most about:
Disk usage growth trend (especially downloads)
IO wait spikes during imports
Container restarts/failures
Stream/transcode load during peak usage
These signals tell me when to clean up, rebalance storage, or adjust quality profiles.
Common Pitfalls I Avoid
Different host/container path mapping per service
Aggressive quality profiles that cause constant upgrades
Overly broad indexer filters
Running too many major updates in one maintenance cycle
Simple configurations outperform clever-but-fragile setups.
Outcomes So Far
This media stack gives me:
Reliable automated acquisition and organization
Clean libraries in Jellyfin with minimal manual work
Predictable operations and easier troubleshooting
A setup that scales with my broader home lab architecture
What's Next
In Part 5 , I'll cover the productivity and photos layer:
Nextcloud — Personal cloud storage
Immich — Self-hosted Google Photos alternative (~401GB)
Paperless-NGX — Document management and OCR
Vaultwarden — Password management
Home Assistant — Home automation
Part 3 ← Infrastructure Layer
Part 5 → Productivity Layer
— Jayakrishna