From 35f39bf92c1e158a379622fb8d6d27e7eaad58ef Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 5 Nov 2025 23:26:58 +0300 Subject: [PATCH 1/2] feat(persistence): add cache volume persistence support Add dedicated cache volume configuration to reduce wear on SD cards and improve performance by allowing cache to be stored on separate storage. Changes: - Add persistence.cache configuration in values.yaml with support for: - PVC (default type when enabled) - hostPath for direct host mount - emptyDir (default when disabled) - Add cache volume mount at /cache in deployment - Add cache PersistentVolumeClaim template - Support for existing PVC via existingClaim - Configurable size (default 10Gi), accessMode, storageClass, and annotations - Update Chart version from 2.5.0 to 2.5.1 - Add Artifact Hub changelog annotation The cache volume is disabled by default to maintain backward compatibility. Users can enable it via: ```yaml persistence: cache: enabled: true type: pvc size: 10Gi ``` This addresses the use case mentioned in #62 where users want to avoid SD card wear by storing cache on separate persistent storage. Fixes: #62 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 6 ++++- charts/jellyfin/README.md | 9 +++++++- charts/jellyfin/templates/deployment.yaml | 17 ++++++++++++++ .../templates/persistentVolumeClaim.yaml | 23 +++++++++++++++++++ charts/jellyfin/values.yaml | 16 +++++++++++++ 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..6f24db1 100644 --- a/charts/jellyfin/Chart.yaml +++ b/charts/jellyfin/Chart.yaml @@ -8,5 +8,9 @@ keywords: - jellyfin - media - self-hosted -version: 2.5.0 +version: 2.5.1 appVersion: 10.11.2 +annotations: + artifacthub.io/changes: | + - kind: added + description: Add persistence.cache configuration for dedicated cache volume support diff --git a/charts/jellyfin/README.md b/charts/jellyfin/README.md index 10952b6..8b18a7a 100644 --- a/charts/jellyfin/README.md +++ b/charts/jellyfin/README.md @@ -1,6 +1,6 @@ # jellyfin -![Version: 2.5.0](https://img.shields.io/badge/Version-2.5.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 10.11.2](https://img.shields.io/badge/AppVersion-10.11.2-informational?style=flat-square) +![Version: 2.5.1](https://img.shields.io/badge/Version-2.5.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 10.11.2](https://img.shields.io/badge/AppVersion-10.11.2-informational?style=flat-square) A Helm chart for Jellyfin Media Server @@ -87,6 +87,13 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml | metrics.serviceMonitor.tlsConfig | object | `{}` | TLS configuration for scraping metrics | | nameOverride | string | `""` | Override the default name of the chart. | | nodeSelector | object | `{}` | Node selector for pod scheduling. | +| persistence.cache.accessMode | string | `"ReadWriteOnce"` | PVC specific settings, only used if type is 'pvc'. | +| persistence.cache.annotations | object | `{}` | Custom annotations to be added to the PVC | +| persistence.cache.enabled | bool | `false` | set to false to use emptyDir | +| persistence.cache.hostPath | string | `""` | Path on the host node for cache storage, only used if type is 'hostPath'. | +| persistence.cache.size | string | `"10Gi"` | | +| persistence.cache.storageClass | string | `""` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. | +| persistence.cache.type | string | `"pvc"` | Type of volume for cache storage (pvc, hostPath, emptyDir). If 'enabled' is false, 'emptyDir' is used regardless of this setting. | | persistence.config.accessMode | string | `"ReadWriteOnce"` | | | persistence.config.annotations | object | `{}` | Custom annotations to be added to the PVC | | persistence.config.enabled | bool | `true` | set to false to use emptyDir | diff --git a/charts/jellyfin/templates/deployment.yaml b/charts/jellyfin/templates/deployment.yaml index c235001..3e1c0ec 100644 --- a/charts/jellyfin/templates/deployment.yaml +++ b/charts/jellyfin/templates/deployment.yaml @@ -119,6 +119,8 @@ spec: name: config - mountPath: /media name: media + - mountPath: /cache + name: cache {{- with .Values.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -152,6 +154,21 @@ spec: {{- else }} emptyDir: {} {{- end }} + - name: cache + {{- if .Values.persistence.cache.enabled }} + {{- if eq .Values.persistence.cache.type "pvc" }} + persistentVolumeClaim: + claimName: {{ if .Values.persistence.cache.existingClaim }}{{ .Values.persistence.cache.existingClaim }}{{- else }}{{ template "jellyfin.fullname" . }}-cache{{- end }} + {{- else if eq .Values.persistence.cache.type "hostPath" }} + hostPath: + path: {{ .Values.persistence.cache.hostPath }} + type: Directory + {{- else }} + emptyDir: {} + {{- end }} + {{- else }} + emptyDir: {} + {{- end }} {{- with .Values.volumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/jellyfin/templates/persistentVolumeClaim.yaml b/charts/jellyfin/templates/persistentVolumeClaim.yaml index 709436d..64ad819 100644 --- a/charts/jellyfin/templates/persistentVolumeClaim.yaml +++ b/charts/jellyfin/templates/persistentVolumeClaim.yaml @@ -43,3 +43,26 @@ spec: storageClassName: {{ . | quote }} {{- end }} {{- end }} + +{{- if and .Values.persistence.cache.enabled (eq .Values.persistence.cache.type "pvc") (not .Values.persistence.cache.existingClaim) }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "jellyfin.fullname" . }}-cache + labels: + {{- include "jellyfin.labels" . | nindent 4 }} + {{- with .Values.persistence.cache.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.cache.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.cache.size | quote }} + {{- with .Values.persistence.cache.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} +{{- end }} diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..d4fdf8a 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -222,6 +222,22 @@ persistence: storageClass: '' ## -- Use an existing PVC for this mount # existingClaim: '' + cache: + # -- set to false to use emptyDir + enabled: false + # -- Type of volume for cache storage (pvc, hostPath, emptyDir). If 'enabled' is false, 'emptyDir' is used regardless of this setting. + type: pvc + # -- Path on the host node for cache storage, only used if type is 'hostPath'. + hostPath: "" + # -- PVC specific settings, only used if type is 'pvc'. + accessMode: ReadWriteOnce + size: 10Gi + # -- Custom annotations to be added to the PVC + annotations: {} + # -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. + storageClass: '' + ## -- Use an existing PVC for this mount + # existingClaim: '' # -- Configuration for metrics collection and monitoring metrics: From 91d50ab9184354efdf2cead53b6c1a46515d1503 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:54:22 +0300 Subject: [PATCH 2/2] test(persistence): add comprehensive cache volume tests Add helm-unittest tests for dedicated cache volume persistence: - Default emptyDir behavior - PVC creation when enabled - hostPath configuration - Volume mount verification - PVC size, access mode, storage class configuration - Annotations support - Existing claim usage - Type-specific behavior (pvc vs hostPath vs emptyDir) Add NOTES.txt with persistence information: - Show config, media, and cache volume types - Display cache volume details when enabled - Benefits explanation for cache volume - Warning for hostPath usage Tests ensure cache persistence works correctly for all scenarios. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 47 +++++ .../tests/cache_persistence_test.yaml | 160 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt create mode 100644 charts/jellyfin/tests/cache_persistence_test.yaml diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..4853179 --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,47 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} + +{{- if .Values.ingress.enabled }} + +Jellyfin is available via Ingress at: +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ (index .paths 0).path }} +{{- end }} +{{- else }} + +To access Jellyfin, you can use port-forwarding: + + $ kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "jellyfin.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} + +Then visit http://localhost:{{ .Values.service.port }} in your browser. +{{- end }} + +Persistence Configuration: + Config: {{ if .Values.persistence.config.enabled }}{{ .Values.persistence.config.type | default "pvc" }}{{ if .Values.persistence.config.existingClaim }} (existing: {{ .Values.persistence.config.existingClaim }}){{ end }}{{ else }}emptyDir{{ end }} + Media: {{ if .Values.persistence.media.enabled }}{{ .Values.persistence.media.type | default "pvc" }}{{ if .Values.persistence.media.existingClaim }} (existing: {{ .Values.persistence.media.existingClaim }}){{ end }}{{ else }}emptyDir{{ end }} + Cache: {{ if .Values.persistence.cache.enabled }}{{ .Values.persistence.cache.type | default "pvc" }}{{ if .Values.persistence.cache.existingClaim }} (existing: {{ .Values.persistence.cache.existingClaim }}){{ end }}{{ else }}emptyDir{{ end }} + +{{- if and .Values.persistence.cache.enabled (eq .Values.persistence.cache.type "pvc") }} +Cache Volume: + Dedicated cache volume is enabled for improved performance. + Size: {{ .Values.persistence.cache.size }} + Access Mode: {{ .Values.persistence.cache.accessMode }} + {{- if .Values.persistence.cache.storageClass }} + Storage Class: {{ .Values.persistence.cache.storageClass }} + {{- end }} + + Benefits: + - Faster transcoding and metadata operations + - Separate cache lifecycle from config/media + - Can use faster storage class (SSD) for cache +{{- end }} + +{{- if and .Values.persistence.cache.enabled (eq .Values.persistence.cache.type "hostPath") }} +⚠ WARNING: Using hostPath for cache - ensure {{ .Values.persistence.cache.hostPath }} exists on the node +{{- end }} diff --git a/charts/jellyfin/tests/cache_persistence_test.yaml b/charts/jellyfin/tests/cache_persistence_test.yaml new file mode 100644 index 0000000..fafcf9e --- /dev/null +++ b/charts/jellyfin/tests/cache_persistence_test.yaml @@ -0,0 +1,160 @@ +suite: test cache persistence +templates: + - deployment.yaml + - persistentVolumeClaim.yaml +tests: + # Deployment volume mount tests + - it: should mount cache as emptyDir by default + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + emptyDir: {} + + - it: should mount cache PVC when enabled + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + persistentVolumeClaim: + claimName: RELEASE-NAME-jellyfin-cache + + - it: should mount cache from hostPath when configured + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: hostPath + persistence.cache.hostPath: /mnt/jellyfin-cache + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + hostPath: + path: /mnt/jellyfin-cache + + - it: should mount cache at correct path + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: cache + mountPath: /cache + + # PVC tests + - it: should not create cache PVC by default + template: persistentVolumeClaim.yaml + asserts: + - hasDocuments: + count: 2 # config and media only + + - it: should create cache PVC when enabled + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + asserts: + - hasDocuments: + count: 3 # config, media, and cache + - contains: + path: metadata.name + content: RELEASE-NAME-jellyfin-cache + + - it: should not create cache PVC when type is hostPath + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: hostPath + asserts: + - hasDocuments: + count: 2 # config and media only + + - it: should not create cache PVC when type is emptyDir + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: emptyDir + asserts: + - hasDocuments: + count: 2 + + - it: should set cache PVC size correctly + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.size: 20Gi + asserts: + - equal: + path: spec.resources.requests.storage + value: 20Gi + + - it: should set cache PVC access mode + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.accessMode: ReadWriteMany + asserts: + - contains: + path: spec.accessModes + content: ReadWriteMany + + - it: should set cache PVC storage class + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.storageClass: fast-ssd + asserts: + - equal: + path: spec.storageClassName + value: fast-ssd + + - it: should add annotations to cache PVC + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.annotations: + my-annotation: my-value + asserts: + - equal: + path: metadata.annotations.my-annotation + value: my-value + + - it: should use existing claim when specified + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.existingClaim: my-existing-cache-claim + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + persistentVolumeClaim: + claimName: my-existing-cache-claim + + - it: should not create PVC when existing claim used + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.existingClaim: my-existing-cache-claim + asserts: + - hasDocuments: + count: 2 # config and media only, not cache