Prerequisites
A Kubernetes cluster
kubectl configured for your cluster
Longhorn installed on your Kubernetes cluster
An NFS server configured and accessible
Traefik Ingress controller installed in your Kubernetes cluster
PV & PVC
apiVersion : v1
kind : PersistentVolume
metadata :
name : qbitt-download
spec :
capacity :
storage : 400Gi
accessModes :
- ReadWriteOnce
nfs :
path : /volume1/Server/Data/alto/media_nas/qbittorrent/
server : storage.merox.cloud
persistentVolumeReclaimPolicy : Retain
mountOptions :
- hard
- nfsvers=3
storageClassName : ""
---
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : qbitt-download
namespace : media
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 400Gi
volumeName : qbitt-download
storageClassName : ""
apiVersion : v1
kind : PersistentVolume
metadata :
name : jellyfin-videos
spec :
capacity :
storage : 400Gi
accessModes :
- ReadWriteOnce
nfs :
path : /volume1/Server/Data/alto/media_nas
server : storage.merox.cloud
persistentVolumeReclaimPolicy : Retain
mountOptions :
- hard
- nfsvers=3
storageClassName : ""
---
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : jellyfin-videos
namespace : media
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 400Gi
volumeName : jellyfin-videos
storageClassName : ""
Jellyfin
apiVersion : apps/v1
kind : Deployment
metadata :
name : jellyfin
namespace : media
spec :
replicas : 1
selector :
matchLabels :
app : jellyfin
template :
metadata :
labels :
app : jellyfin
spec :
containers :
- name : jellyfin
image : jellyfin/jellyfin
volumeMounts :
- name : config
mountPath : /config
- name : videos
mountPath : /data/videos
ports :
- containerPort : 8096
volumes :
- name : config
persistentVolumeClaim :
claimName : jellyfin-config
- name : videos
persistentVolumeClaim :
claimName : jellyfin-videos
---
apiVersion : traefik.containo.us/v1alpha1
kind : IngressRoute
metadata :
name : jellyfin
namespace : media
annotations :
kubernetes.io/ingress.class : traefik-external
spec :
entryPoints :
- websecure
routes :
- match : Host(`www.jellyfin.merox.cloud`) # change to your domain
kind : Rule
services :
- name : jellyfin
port : 80
- match : Host(`jellyfin.merox.cloud`) # change to your domain
kind : Rule
services :
- name : jellyfin
port : 80
middlewares :
- name : default-headers-jellyfin
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : jellyfin-config
namespace : media
spec :
accessModes :
- ReadWriteOnce
storageClassName : longhorn
resources :
requests :
storage : 10Gi
apiVersion : v1
kind : Service
metadata :
name : jellyfin
namespace : media
spec :
type : ClusterIP
ports :
- port : 80
targetPort : 8096
selector :
app : jellyfin
Sonarr
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : sonarr-config
namespace : media
spec :
accessModes :
- ReadWriteOnce
storageClassName : longhorn
resources :
requests :
storage : 5Gi
apiVersion : apps/v1
kind : Deployment
metadata :
name : sonarr
namespace : media
spec :
replicas : 1
selector :
matchLabels :
app : sonarr
template :
metadata :
labels :
app : sonarr
spec :
containers :
- name : sonarr
image : linuxserver/sonarr
env :
- name : PUID
value : "1057"
- name : PGID
value : "1056"
volumeMounts :
- name : config
mountPath : /config
- name : videos
mountPath : /tv
- name : downloads
mountPath : /downloads
ports :
- containerPort : 8989
volumes :
- name : config
persistentVolumeClaim :
claimName : sonarr-config
- name : videos
persistentVolumeClaim :
claimName : jellyfin-videos
- name : downloads
persistentVolumeClaim :
claimName : qbitt-download
---
apiVersion : traefik.containo.us/v1alpha1
kind : IngressRoute
metadata :
name : sonarr
namespace : media
annotations :
kubernetes.io/ingress.class : traefik-external
spec :
entryPoints :
- websecure
routes :
- match : Host(`www.tv.merox.cloud`) # change to your domain
kind : Rule
services :
- name : sonarr
port : 80
- match : Host(`tv.merox.cloud`) # change to your domain
kind : Rule
services :
- name : sonarr
port : 80
middlewares :
- name : default-headers-jellyfin
apiVersion : v1
kind : Service
metadata :
name : sonarr
namespace : media
spec :
type : ClusterIP
ports :
- port : 80
targetPort : 8989
selector :
app : sonarr
Radarr
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : radarr-config
namespace : media
spec :
accessModes :
- ReadWriteOnce
storageClassName : longhorn
resources :
requests :
storage : 5Gi
apiVersion : apps/v1
kind : Deployment
metadata :
name : radarr
namespace : media
spec :
replicas : 1
selector :
matchLabels :
app : radarr
template :
metadata :
labels :
app : radarr
spec :
# initContainers:
# - name: set-perms
# image: alpine
# command: ['sh', '-c', 'chown -R 1057:1056 /movies']
containers :
- name : radarr
image : linuxserver/radarr
env :
- name : PUID
value : "1057"
- name : PGID
value : "1056"
volumeMounts :
- name : config
mountPath : /config
- name : videos
mountPath : /movies
- name : downloads
mountPath : /downloads
ports :
- containerPort : 7878
volumes :
- name : config
persistentVolumeClaim :
claimName : radarr-config
- name : videos
persistentVolumeClaim :
claimName : jellyfin-videos
- name : downloads
persistentVolumeClaim :
claimName : qbitt-download
``` yaml linenums="1"
---
apiVersion : traefik.containo.us/v1alpha1
kind : IngressRoute
metadata :
name : radarr
namespace : media
annotations :
kubernetes.io/ingress.class : traefik-external
spec :
entryPoints :
- websecure
routes :
- match : Host(`movies.tv.merox.cloud`) # change to your domain
kind : Rule
services :
- name : radarr
port : 80
- match : Host(`movies.merox.cloud`) # change to your domain
kind : Rule
services :
- name : radarr
port : 80
middlewares :
- name : default-headers-jellyfin
``` yaml linenums="1"
apiVersion : v1
kind : Service
metadata :
name : radarr
namespace : media
spec :
type : ClusterIP
ports :
- port : 80
targetPort : 7878
selector :
app : radarr
Jackett
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : jackett-config
namespace : media
spec :
accessModes :
- ReadWriteOnce
storageClassName : longhorn
resources :
requests :
storage : 5Gi
apiVersion : apps/v1
kind : Deployment
metadata :
name : jackett
namespace : media
spec :
replicas : 1
selector :
matchLabels :
app : jackett
template :
metadata :
labels :
app : jackett
spec :
containers :
- name : jackett
image : linuxserver/jackett
env :
- name : PUID
value : "1057" # Ajustează această valoare conform nevoilor tale
- name : PGID
value : "1056" # Ajustează această valoare conform nevoilor tale
volumeMounts :
- name : config
mountPath : /config
ports :
- containerPort : 9117
volumes :
- name : config
persistentVolumeClaim :
claimName : jackett-config
---
apiVersion : traefik.containo.us/v1alpha1
kind : IngressRoute
metadata :
name : jackett
namespace : media
annotations :
kubernetes.io/ingress.class : traefik-external
spec :
entryPoints :
- websecure
routes :
- match : Host(`www.jackett.merox.cloud`) # change to your domain
kind : Rule
services :
- name : jackett
port : 80
- match : Host(`jackett.merox.cloud`) # change to your domain
kind : Rule
services :
- name : jackett
port : 80
middlewares :
- name : default-headers-jellyfin
apiVersion : v1
kind : Service
metadata :
name : jackett
namespace : media
spec :
type : ClusterIP
ports :
- port : 80
targetPort : 9117
selector :
app : jackett
qBittorrent
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : qbitt-config
namespace : media
spec :
accessModes :
- ReadWriteOnce
storageClassName : longhorn
resources :
requests :
storage : 5Gi
apiVersion : apps/v1
kind : Deployment
metadata :
name : qbittorrent
namespace : media
spec :
replicas : 1
selector :
matchLabels :
app : qbittorrent
template :
metadata :
labels :
app : qbittorrent
spec :
containers :
- name : qbittorrent
image : linuxserver/qbittorrent
resources :
limits :
memory : "2Gi"
requests :
memory : "512Mi"
env :
- name : PUID
value : "1057"
- name : PGID
value : "1056"
volumeMounts :
- name : config
mountPath : /config
- name : downloads
mountPath : /downloads
ports :
- containerPort : 8080
volumes :
- name : config
persistentVolumeClaim :
claimName : qbitt-config
- name : downloads
persistentVolumeClaim :
claimName : qbitt-download
---
apiVersion : traefik.containo.us/v1alpha1
kind : IngressRoute
metadata :
name : qbittorrent
namespace : media
annotations :
kubernetes.io/ingress.class : traefik-external
spec :
entryPoints :
- websecure
routes :
- match : Host(`www.qbitt.merox.cloud`) # change to your domain
kind : Rule
services :
- name : qbittorrent
port : 80
- match : Host(`qbitt.merox.cloud`) # change to your domain
kind : Rule
services :
- name : qbittorrent
port : 80
middlewares :
- name : default-headers-jellyfin
apiVersion : v1
kind : Service
metadata :
name : qbittorrent
namespace : media
spec :
type : ClusterIP
ports :
- port : 80
targetPort : 8080
selector :
app : qbittorrent
qBittorrent with Gluetun
apiVersion : apps/v1
kind : Deployment
metadata :
name : qbittorrent
namespace : media
spec :
replicas : 1
selector :
matchLabels :
app : qbittorrent
template :
metadata :
labels :
app : qbittorrent
spec :
containers :
- name : qbittorrent
image : linuxserver/qbittorrent
resources :
limits :
memory : "2Gi"
requests :
memory : "512Mi"
env :
- name : PUID
value : "1057"
- name : PGID
value : "1056"
volumeMounts :
- name : config
mountPath : /config
- name : downloads
mountPath : /downloads
ports :
- containerPort : 8080
- name : gluetun
image : qmcgaw/gluetun
env :
- name : VPNSP
value : "protonvpn"
- name : OPENVPN_USER
valueFrom :
secretKeyRef :
name : protonvpn-secrets
key : PROTONVPN_USER
- name : OPENVPN_PASSWORD
valueFrom :
secretKeyRef :
name : protonvpn-secrets
key : PROTONVPN_PASSWORD
- name : COUNTRY
value : "Germany"
securityContext :
capabilities :
add :
- NET_ADMIN
volumeMounts :
- name : gluetun-config
mountPath : /gluetun
volumes :
- name : config
persistentVolumeClaim :
claimName : qbitt-config
- name : downloads
persistentVolumeClaim :
claimName : qbitt-download
- name : gluetun-config
persistentVolumeClaim :
claimName : gluetun-config
Example
I've chosen to use ProtonVPN due to their security policy and because they do not collect/store data, but also because of the speeds and diverse settings, all at a very good price
March 11, 2024
March 5, 2024