diff --git a/kubernetes/app/jellyfin/deployment.yaml b/kubernetes/app/jellyfin/deployment.yaml new file mode 100644 index 0000000..7a52cd3 --- /dev/null +++ b/kubernetes/app/jellyfin/deployment.yaml @@ -0,0 +1,73 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jellyfin + namespace: jellyfin + labels: + app: jellyfin +spec: + replicas: 0 + strategy: + type: Recreate + selector: + matchLabels: + app: jellyfin + template: + metadata: + labels: + app: jellyfin + spec: + containers: + - name: jellyfin + image: jellyfin/jellyfin:10.11.6 + env: + - name: JELLYFIN_PublishedServerUrl + value: https://${JELLYFIN_HOST} + ports: + - containerPort: 8096 + name: http + protocol: TCP + - containerPort: 7359 + name: discovery + protocol: UDP + hostPort: 7359 + - containerPort: 1900 + name: dlna + protocol: UDP + hostPort: 1900 + resources: + limits: + gpu.intel.com/i915: "1" + livenessProbe: + httpGet: + port: 8096 + path: /health + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 5 + readinessProbe: + httpGet: + port: 8096 + path: /health + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + volumeMounts: + - name: config + mountPath: /config + - name: cache + mountPath: /cache + - name: media + mountPath: /media + subPath: complete + volumes: + - name: config + persistentVolumeClaim: + claimName: jellyfin-config + - name: cache + persistentVolumeClaim: + claimName: jellyfin-cache + - name: media + persistentVolumeClaim: + claimName: jellyfin-media diff --git a/kubernetes/app/jellyfin/ingress.yaml b/kubernetes/app/jellyfin/ingress.yaml new file mode 100644 index 0000000..beb214c --- /dev/null +++ b/kubernetes/app/jellyfin/ingress.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: jellyfin + namespace: jellyfin + annotations: + cert-manager.io/cluster-issuer: letsencrypt +spec: + tls: + - hosts: + - ${JELLYFIN_HOST} + secretName: jellyfin-tls + rules: + - host: ${JELLYFIN_HOST} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: jellyfin + port: + number: 8096 diff --git a/kubernetes/app/jellyfin/namespace.yaml b/kubernetes/app/jellyfin/namespace.yaml new file mode 100644 index 0000000..9b897dd --- /dev/null +++ b/kubernetes/app/jellyfin/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: jellyfin + labels: + pod-security.kubernetes.io/enforce: privileged diff --git a/kubernetes/app/jellyfin/networkpolicy.yaml b/kubernetes/app/jellyfin/networkpolicy.yaml new file mode 100644 index 0000000..7545038 --- /dev/null +++ b/kubernetes/app/jellyfin/networkpolicy.yaml @@ -0,0 +1,47 @@ +# Default deny all ingress in the jellyfin namespace +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-ingress + namespace: jellyfin +spec: + podSelector: {} + policyTypes: + - Ingress +--- +# Allow Traefik ingress controller to reach Jellyfin +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-controller + namespace: jellyfin +spec: + podSelector: + matchLabels: + app: jellyfin + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: traefik +--- +# Allow UDP discovery and DLNA from local network +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-udp-discovery + namespace: jellyfin +spec: + podSelector: + matchLabels: + app: jellyfin + policyTypes: + - Ingress + ingress: + - ports: + - port: 7359 + protocol: UDP + - port: 1900 + protocol: UDP diff --git a/kubernetes/app/jellyfin/pv-media.yaml b/kubernetes/app/jellyfin/pv-media.yaml new file mode 100644 index 0000000..932a406 --- /dev/null +++ b/kubernetes/app/jellyfin/pv-media.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: jellyfin-media-nfs +spec: + capacity: + storage: 1Ti + accessModes: + - ReadWriteMany + storageClassName: "" + persistentVolumeReclaimPolicy: Retain + mountOptions: + - hard + - nointr + nfs: + server: synology.storage.lviv + path: ${MEDIA_NFS_PATH} diff --git a/kubernetes/app/jellyfin/pvc.yaml b/kubernetes/app/jellyfin/pvc.yaml new file mode 100644 index 0000000..a69b3ac --- /dev/null +++ b/kubernetes/app/jellyfin/pvc.yaml @@ -0,0 +1,43 @@ +--- +# Jellyfin config (includes metadata) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: jellyfin-config + namespace: jellyfin +spec: + accessModes: + - ReadWriteOnce + storageClassName: nfs-synology-ssd + resources: + requests: + storage: 30Gi +--- +# Jellyfin cache (transcoding temp files etc.) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: jellyfin-cache + namespace: jellyfin +spec: + accessModes: + - ReadWriteOnce + storageClassName: nfs-synology-ssd + resources: + requests: + storage: 20Gi +--- +# Shared media library (NFS) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: jellyfin-media + namespace: jellyfin +spec: + accessModes: + - ReadWriteMany + storageClassName: "" + volumeName: jellyfin-media-nfs + resources: + requests: + storage: 1Ti diff --git a/kubernetes/app/jellyfin/service.yaml b/kubernetes/app/jellyfin/service.yaml new file mode 100644 index 0000000..d713203 --- /dev/null +++ b/kubernetes/app/jellyfin/service.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: jellyfin + namespace: jellyfin +spec: + selector: + app: jellyfin + ports: + - name: http + port: 8096 + targetPort: 8096 + protocol: TCP diff --git a/kubernetes/config/cluster-vars.sops.yaml b/kubernetes/config/cluster-vars.sops.yaml index b37712d..241bbaf 100644 --- a/kubernetes/config/cluster-vars.sops.yaml +++ b/kubernetes/config/cluster-vars.sops.yaml @@ -12,6 +12,7 @@ stringData: MEDIA_NFS_PATH: ENC[AES256_GCM,data:BSDMu0n2Vx4koYBIMF8=,iv:c9kGdcTxObNaaaTzEhSRkyHvo+dxSN+o+96n9UqJieU=,tag:W9+MbyAuK85xajjwntRi0Q==,type:str] IMMICH_HOST: ENC[AES256_GCM,data:KnzX89wzQvb5Pa/MqX4YiHZ0JS5geA==,iv:05jEIwQEjJnvZ1Ot33Lkfs1TB3L/mwX5dqaTfsugcx4=,tag:LFzOLZSqHQ58bL3oVvGM9g==,type:str] IMMICH_UPLOAD_NFS_PATH: ENC[AES256_GCM,data:l8F1AkmhGkNxo29X5UER,iv:Z/u0yLNv5ClQu44lPPzGIB2bEsADFCD/mCd+Kw8kuhc=,tag:a8QGaUEYF3iJbZKcAiRKUg==,type:str] + JELLYFIN_HOST: ENC[AES256_GCM,data:88I8uzcJa/VwsWOJDe69bUsdGbXzTIGI,iv:TWIALVMMDV9VV7iz0OMsVJ8Cvh13VI54KmACR2utlJI=,tag:yDx1vGk/WfFXaQrnLbhLVA==,type:str] sops: age: - recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc @@ -23,7 +24,7 @@ sops: LzhUN3Z4cExIL1IyS3ZCNWh5aWpLbDgKQ7c3MmLykA00NaLoctKVDfJvPqTqh3Ia cDZJUc6jYJXOJYM6YYyZOYcCL2z8V2RpIfA9sPg8PB2eiipZxjk+Cg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-22T16:16:12Z" - mac: ENC[AES256_GCM,data:i8QGecMBJwUwb9dki6ZLFM+4cNPjSLtI31eeiGBqu5dqyeX/e60FprfIHBHfdiastFDDrCZ4SRLd49KKBocGsX3QbX/uk+8rmtHC83ACp6iYiv3kGUS+3u6OrRB/HzXqKiDI5w8cN1lWWUSFHelcXsPoWAv9jUNi1qEBaKAjmnk=,iv:gATtJAJacVoRAsYXhknZkWzASq6aMfjX10oHg3GEVuI=,tag:ekZMb7g0hD1KdjW5u85kkA==,type:str] + lastmodified: "2026-02-22T19:33:02Z" + mac: ENC[AES256_GCM,data:z+++1wWcsW2/UwEofY1OAnDWqDED4jkb5DMHaIJHFn623SaA7o6ed3fbgOWABgWI2O+OcQExxcaighSgx/6e2qHwNgpoNg/1FnsNtSBfufNyVz78Lg8wU9ipQ1tT9Ms24vEMOWbsM2Nekb8s2Co6XCdP+18SFpcjEdh1Du++wgc=,iv:dSczLKU2slxRrguwNraEDmohWr9Ya8iWYBDziiBmAWI=,tag:pKv/WbiUUtI60sX1Qoy0kA==,type:str] encrypted_regex: ^(data|stringData|email)$ version: 3.11.0