2026-02-27 00:55:03 +00:00
2026-02-27 00:55:03 +00:00
2026-02-27 00:55:03 +00:00

Media Stack Kubernetes Deployment

A complete self-hosted media solution deployed on Kubernetes.

Stack Components

Component Purpose Port Image
Jellyfin Media server + Live TV frontend 8096 jellyfin/jellyfin:10.11.5
Sonarr TV show management 8989 lscr.io/linuxserver/sonarr:latest
Radarr Movie management 7878 lscr.io/linuxserver/radarr:latest
Lidarr Music management 8686 lscr.io/linuxserver/lidarr:latest
Prowlarr Indexer management 9696 lscr.io/linuxserver/prowlarr:latest
qBittorrent Download client 8080 lscr.io/linuxserver/qbittorrent:latest
Dispatcharr IPTV/M3U proxy for live TV 9191 ghcr.io/dispatcharr/dispatcharr:latest

Network Configuration

  • Network: 10.0.0.0/24
  • K8s Control Plane: 10.0.0.69 (k8scontrol)
  • K8s Workers: 10.0.0.70-73 (k8sworker1-3)
  • NFS Server: 10.0.0.230
  • Kubernetes Version: 1.35

NFS Share Structure

Your NFS server (10.0.0.230) should have these directories:

/srv/nfs/media/
├── config/
│   ├── jellyfin/
│   ├── sonarr/
│   ├── radarr/
│   ├── lidarr/
│   ├── prowlarr/
│   ├── qbittorrent/
│   └── dispatcharr/
├── downloads/
│   ├── complete/
│   └── incomplete/
├── media/
│   ├── movies/
│   ├── tv/
│   └── music/
└── transcode/

Pre-requisites

1. Configure NFS Server (10.0.0.230)

# On NFS server
sudo mkdir -p /srv/nfs/media/{config/{jellyfin,sonarr,radarr,lidarr,prowlarr,qbittorrent,dispatcharr},downloads/{complete,incomplete},media/{movies,tv,music},transcode}

# Set permissions (adjust UID/GID as needed - 1000:1000 is common)
sudo chown -R 1000:1000 /srv/nfs/media

# Add to /etc/exports
echo "/srv/nfs/media 10.0.0.0/24(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports

# Apply exports
sudo exportfs -ra

2. Install NFS Client on K8s Nodes

# On each worker node (k8sworker1-3)
sudo apt-get update && sudo apt-get install -y nfs-common

3. Verify NFS Connectivity

# From any worker node
showmount -e 10.0.0.230

Deployment

Manual Deployment (in order)

Without VPN:

# Create namespace
kubectl apply -f base/namespace.yaml

# Create storage
kubectl apply -f base/nfs-storage.yaml

# Create config
kubectl apply -f base/configmap.yaml

# Create PVCs
kubectl apply -f base/pvcs.yaml

# Deploy applications
kubectl apply -f base/jellyfin.yaml
kubectl apply -f base/sonarr.yaml
kubectl apply -f base/radarr.yaml
kubectl apply -f base/lidarr.yaml
kubectl apply -f base/prowlarr.yaml
kubectl apply -f base/qbittorrent.yaml
kubectl apply -f base/dispatcharr.yaml

With VPN (after configuring VPN credentials in base/vpn/mullvad-secret.yaml):

Download a .conf file from your Mullvad account and update the private key and wireguard address in base/vpn/mullvad-secret.yaml)

# Create namespace
kubectl apply -f base/namespace.yaml

# Create storage
kubectl apply -f base/nfs-storage.yaml

# Create config
kubectl apply -f base/configmap.yaml

# Create VPN-specific resources
kubectl apply -f base/vpn/gluetun-config.yaml
kubectl apply -f base/vpn/qbittorrent-init-configmap.yaml
kubectl apply -f base/vpn/mullvad-secret.yaml

# Create PVCs
kubectl apply -f base/pvcs.yaml

# Deploy non-VPN applications
kubectl apply -f base/jellyfin.yaml
kubectl apply -f base/sonarr.yaml
kubectl apply -f base/radarr.yaml
kubectl apply -f base/lidarr.yaml

# Deploy VPN-enabled applications
kubectl apply -f base/vpn/prowlarr-vpn.yaml
kubectl apply -f base/vpn/qbittorrent-vpn.yaml
kubectl apply -f base/vpn/dispatcharr-vpn.yaml

Automated deployment

A deploy.sh script can be used as such:

Without VPN:

./deploy.sh

With VPN (recommended for qBittorrent, Prowlarr, Dispatcharr):

./deploy.sh --vpn

Optionally to change the VPN location amend SERVER_CITIES in the base/vpn/gluetun-config.yaml file.

To enable port forwarding:

  1. Go to your mullvad.net account and navigate to account/ports
  2. Add a port for your wireguard key
  3. Amend the FIREWALL_VPN_INPUT_PORTS value to your port in the base/vpn/gluetun-config.yaml file
  4. Configure qBittorrent to point to your client

Accessing Services

After deployment, services are available via NodePort:

Service URL
Jellyfin http://10.0.0.70:30096
Sonarr http://10.0.0.70:30989
Radarr http://10.0.0.70:30878
Lidarr http://10.0.0.70:30686
Prowlarr http://10.0.0.70:30696
qBittorrent http://10.0.0.70:30080
Dispatcharr http://10.0.0.70:30191

Replace 10.0.0.70 with any worker node IP

Post-Deployment Configuration

1. qBittorrent

  • Check container logs for temporary password: kubectl logs -n media deployment/qbittorrent
  • Login and change password immediately
  • Set download paths to /downloads/complete and /downloads/incomplete

2. Prowlarr

  • Add your indexers
  • Connect to Sonarr, Radarr, and Lidarr via Settings → Apps
  • Use internal service names: http://sonarr:8989, http://radarr:7878, http://lidarr:8686

3. Sonarr/Radarr/Lidarr

  • Add qBittorrent as download client: http://qbittorrent:8080
  • Set media library paths: /media/tv, /media/movies, /media/music
  • Configure quality profiles and root folders

4. Jellyfin

  • Run initial setup wizard
  • Add media libraries pointing to /media/movies, /media/tv, /media/music
  • For Live TV: Settings → Live TV → Add Tuner Device → HDHomeRun
    • Use Dispatcharr's HDHomeRun URL: http://dispatcharr:9191

5. Dispatcharr

  • Add your M3U playlist sources
  • Configure EPG sources
  • Enable channels you want to watch

Useful Commands

# Restart a deployment
kubectl rollout restart deployment/jellyfin -n media

# Scale down all (for maintenance)
kubectl scale deployment --all -n media --replicas=0

# Scale back up
kubectl scale deployment --all -n media --replicas=1

Troubleshooting

Permission Denied Errors

  • Ensure PUID/PGID in configmap matches NFS share ownership
  • Check that no_root_squash is set in NFS exports

Upgrading

To upgrade images:

# Update a single deployment
kubectl set image deployment/jellyfin -n media jellyfin=jellyfin/jellyfin:NEW_VERSION

# Or edit the YAML and reapply
kubectl apply -f base/jellyfin.yaml

Cleanup

# Delete everything
kubectl delete namespace media

# This will remove all deployments, services, and PVCs
# PVs and NFS data will remain
Description
No description provided
Readme 46 KiB