diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml
index b701d98..0443162 100644
--- a/charts/jellyfin/Chart.yaml
+++ b/charts/jellyfin/Chart.yaml
@@ -9,10 +9,27 @@ keywords:
- media
- self-hosted
version: 2.6.0
+version: 3.0.0
appVersion: 10.11.2
annotations:
artifacthub.io/changes: |
+ - kind: deprecated
+ description: Deprecate initContainers parameter in favor of extraInitContainers for consistency (will be removed after 2030)
+ - kind: fixed
+ description: Fix extraInitContainers not working (was using wrong parameter name in template)
- kind: added
description: Add support for Gateway API HTTPRoute resource
- kind: added
+ description: Add envFrom support to load environment variables from ConfigMap or Secret
+ - kind: added
+ description: Add NOTES.txt with helpful post-installation information and deprecation warnings
+ - kind: added
+ description: Add startup probe to prevent pod restarts during slow initial startup with large media libraries
+ - kind: added
+ description: Add persistence.cache configuration for dedicated cache volume support
+ - kind: added
+ description: Add comprehensive IPv6 and dual-stack networking documentation
+ - kind: changed
+ description: Improve health probe configuration examples for IPv6 compatibility
+ - kind: changed
description: Add revisionHistoryLimit parameter to control rollback history retention (defaults to 3 instead of Kubernetes default 10)
diff --git a/charts/jellyfin/README.md b/charts/jellyfin/README.md
index ff6e753..d5d788d 100644
--- a/charts/jellyfin/README.md
+++ b/charts/jellyfin/README.md
@@ -1,6 +1,7 @@
# jellyfin
  
+  
A Helm chart for Jellyfin Media Server
@@ -73,7 +74,7 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml
| jellyfin.command | list | `[]` | Custom command to use as container entrypoint. |
| jellyfin.enableDLNA | bool | `false` | Enable DLNA. Requires host network. See: https://jellyfin.org/docs/general/networking/dlna.html |
| jellyfin.env | list | `[]` | Additional environment variables for the container. |
-| livenessProbe | object | `{"initialDelaySeconds":10,"tcpSocket":{"port":"http"}}` | Configure liveness probe for Jellyfin. |
+| livenessProbe | object | `{"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":10}` | Configure liveness probe for Jellyfin. Uses httpGet for compatibility with both IPv4 and IPv6. |
| metrics | object | `{"enabled":false,"serviceMonitor":{"enabled":false,"interval":"30s","labels":{},"metricRelabelings":[],"namespace":"","path":"/metrics","port":8096,"relabelings":[],"scheme":"http","scrapeTimeout":"30s","targetLabels":[],"tlsConfig":{}}}` | Configuration for metrics collection and monitoring |
| metrics.enabled | bool | `false` | Enable or disable metrics collection |
| metrics.serviceMonitor | object | `{"enabled":false,"interval":"30s","labels":{},"metricRelabelings":[],"namespace":"","path":"/metrics","port":8096,"relabelings":[],"scheme":"http","scrapeTimeout":"30s","targetLabels":[],"tlsConfig":{}}` | Configuration for the Prometheus ServiceMonitor |
@@ -91,6 +92,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 |
@@ -111,14 +119,14 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml
| podPrivileges.hostPID | bool | `false` | Enable hostPID namespace. Allows pod to see processes on the host. |
| podSecurityContext | object | `{}` | Security context for the pod. |
| priorityClassName | string | `""` | Define a priorityClassName for the pod. |
-| readinessProbe | object | `{"initialDelaySeconds":10,"tcpSocket":{"port":"http"}}` | Configure readiness probe for Jellyfin. |
+| readinessProbe | object | `{"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":10}` | Configure readiness probe for Jellyfin. Uses httpGet for compatibility with both IPv4 and IPv6. |
| replicaCount | int | `1` | Number of Jellyfin replicas to start. Should be left at 1. |
| resources | object | `{}` | Resource requests and limits for the Jellyfin container. |
| runtimeClassName | string | `""` | Define a custom runtimeClassName for the pod. |
| securityContext | object | `{}` | Security context for the container. |
| service.annotations | object | `{}` | Annotations for the service. |
-| service.ipFamilies | list | `[]` | Supported IP families (IPv4, IPv6). |
-| service.ipFamilyPolicy | string | `""` | Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/ |
+| service.ipFamilies | list | `[]` | Supported IP families (IPv4, IPv6). Examples: IPv4 only: ["IPv4"] IPv6 only: ["IPv6"] Dual-stack (IPv4 primary): ["IPv4", "IPv6"] Dual-stack (IPv6 primary): ["IPv6", "IPv4"] Note: When using IPv6, ensure your health checks are compatible (consider using httpGet instead of tcpSocket) |
+| service.ipFamilyPolicy | string | `""` | Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/ Options: SingleStack, PreferDualStack, RequireDualStack For IPv6-only clusters, use "SingleStack" with ipFamilies: ["IPv6"] For dual-stack, use "PreferDualStack" or "RequireDualStack" with ipFamilies: ["IPv4", "IPv6"] or ["IPv6", "IPv4"] |
| service.labels | object | `{}` | Labels for the service. |
| service.loadBalancerClass | string | `""` | Class of the LoadBalancer. |
| service.loadBalancerIP | string | `""` | Specific IP address for the LoadBalancer. |
@@ -191,3 +199,32 @@ extraVolumeMounts:
- name: hwa
mountPath: /dev/dri
```
+
+## IPv6 Configuration
+
+This chart supports IPv6 and dual-stack networking configurations out of the box. Health probes use httpGet by default for compatibility with both IPv4 and IPv6.
+
+### IPv6-only Configuration
+
+For IPv6-only clusters:
+
+```yaml
+service:
+ ipFamilyPolicy: SingleStack
+ ipFamilies:
+ - IPv6
+```
+
+### Dual-stack Configuration
+
+For dual-stack clusters (both IPv4 and IPv6):
+
+```yaml
+service:
+ ipFamilyPolicy: PreferDualStack # or RequireDualStack
+ ipFamilies:
+ - IPv4
+ - IPv6 # First family in the list is the primary
+```
+
+For more information about Kubernetes dual-stack networking, see:
diff --git a/charts/jellyfin/README.md.gotmpl b/charts/jellyfin/README.md.gotmpl
index bbcd888..be3cf9e 100644
--- a/charts/jellyfin/README.md.gotmpl
+++ b/charts/jellyfin/README.md.gotmpl
@@ -112,3 +112,32 @@ extraVolumeMounts:
- name: hwa
mountPath: /dev/dri
```
+
+## IPv6 Configuration
+
+This chart supports IPv6 and dual-stack networking configurations out of the box. Health probes use httpGet by default for compatibility with both IPv4 and IPv6.
+
+### IPv6-only Configuration
+
+For IPv6-only clusters:
+
+```yaml
+service:
+ ipFamilyPolicy: SingleStack
+ ipFamilies:
+ - IPv6
+```
+
+### Dual-stack Configuration
+
+For dual-stack clusters (both IPv4 and IPv6):
+
+```yaml
+service:
+ ipFamilyPolicy: PreferDualStack # or RequireDualStack
+ ipFamilies:
+ - IPv4
+ - IPv6 # First family in the list is the primary
+```
+
+For more information about Kubernetes dual-stack networking, see:
diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt
index 4d9ef7a..126f16b 100644
--- a/charts/jellyfin/templates/NOTES.txt
+++ b/charts/jellyfin/templates/NOTES.txt
@@ -1,9 +1,103 @@
-Thank you for installing {{ .Chart.Name }}.
+{{- if .Values.initContainers }}
+#############################################################################
+## ⚠️ WARNING ⚠️ ##
+## ##
+## The 'initContainers' parameter is DEPRECATED! ##
+## Please migrate to 'extraInitContainers' instead. ##
+## ##
+## Support for 'initContainers' will be removed after 2030. ##
+## ##
+## Migration guide: ##
+## Old: initContainers: [...] ##
+## New: extraInitContainers: [...] ##
+## ##
+#############################################################################
+
+{{- end }}
+Thank you for installing {{ .Chart.Name }}!
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
+ $ helm status {{ .Release.Name }} --namespace {{ .Release.Namespace }}
+ $ helm get all {{ .Release.Name }} --namespace {{ .Release.Namespace }}
+
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+ {{- range .paths }}
+
+Jellyfin is available at:
+ http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+ {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+
+Get the Jellyfin URL by running:
+
+ export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "jellyfin.fullname" . }})
+ export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+ echo "Jellyfin URL: http://$NODE_IP:$NODE_PORT"
+
+{{- else if contains "LoadBalancer" .Values.service.type }}
+
+Get the Jellyfin URL by running:
+
+ NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+ You can watch the status by running:
+ kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "jellyfin.fullname" . }}
+
+ export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "jellyfin.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+ echo "Jellyfin URL: http://$SERVICE_IP:{{ .Values.service.port }}"
+
+{{- else if contains "ClusterIP" .Values.service.type }}
+
+Get the Jellyfin URL by running:
+
+ export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "jellyfin.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+ export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+ echo "Jellyfin URL: http://127.0.0.1:8080"
+ kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+
+{{- end }}
+
+{{- if .Values.persistence.config.enabled }}
+
+Configuration is persisted to:
+{{- if .Values.persistence.config.existingClaim }}
+ Existing PVC: {{ .Values.persistence.config.existingClaim }}
+{{- else }}
+ PVC: {{ include "jellyfin.fullname" . }}-config
+{{- end }}
+{{- else }}
+
+WARNING: Configuration persistence is disabled!
+ Data will be lost when the pod is restarted.
+ Enable persistence.config.enabled to persist your configuration.
+{{- end }}
+
+{{- if .Values.persistence.media.enabled }}
+
+Media is {{ if eq .Values.persistence.media.type "hostPath" }}mounted from host path{{ else if eq .Values.persistence.media.type "pvc" }}persisted to PVC{{ else }}stored in emptyDir{{ end }}:
+{{- if eq .Values.persistence.media.type "hostPath" }}
+ Host path: {{ .Values.persistence.media.hostPath }}
+{{- else if eq .Values.persistence.media.type "pvc" }}
+ {{- if .Values.persistence.media.existingClaim }}
+ Existing PVC: {{ .Values.persistence.media.existingClaim }}
+ {{- else }}
+ PVC: {{ include "jellyfin.fullname" . }}-media
+ {{- end }}
+{{- end }}
+{{- else }}
+
+WARNING: Media persistence is disabled!
+ Your media files will be lost when the pod is restarted.
+ Enable persistence.media.enabled to persist your media.
+{{- end }}
+
+For more information and documentation:
+ Repository: https://github.com/jellyfin/jellyfin-helm
+ Jellyfin Docs: https://jellyfin.org/docs/
$ helm status {{ .Release.Name }}
$ helm get all {{ .Release.Name }}
@@ -53,6 +147,74 @@ To access Jellyfin, you can use port-forwarding:
Then visit http://localhost:{{ .Values.service.port }} in your browser.
{{- end }}
+{{- if .Values.jellyfin.envFrom }}
+
+Environment Configuration:
+ Loading environment variables from {{ len .Values.jellyfin.envFrom }} source(s):
+{{- range .Values.jellyfin.envFrom }}
+ {{- if .configMapRef }}
+ - ConfigMap: {{ .configMapRef.name }}{{ if .configMapRef.optional }} (optional){{ end }}{{ if .prefix }} with prefix: {{ .prefix }}{{ end }}
+ {{- else if .secretRef }}
+ - Secret: {{ .secretRef.name }}{{ if .secretRef.optional }} (optional){{ end }}{{ if .prefix }} with prefix: {{ .prefix }}{{ end }}
+ {{- end }}
+{{- end }}
+{{- end }}
+
+{{- if .Values.startupProbe }}
+{{- $startupTimeout := mul (default 10 .Values.startupProbe.periodSeconds) (default 30 .Values.startupProbe.failureThreshold) }}
+
+Startup Configuration:
+ Startup Probe: Enabled
+ Max Startup Time: {{ $startupTimeout }}s ({{ div $startupTimeout 60 }} minutes)
+
+ The startup probe gives Jellyfin enough time to initialize, especially
+ with large media libraries. After startup succeeds, liveness and
+ readiness probes take over for health monitoring.
+
+ {{- if gt $startupTimeout 600 }}
+ ⚠ NOTE: Startup timeout is very long ({{ div $startupTimeout 60 }}+ minutes).
+ Consider optimizing your media library or reducing failureThreshold.
+ {{- 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
+{{- if or .Values.service.ipFamilyPolicy .Values.service.ipFamilies }}
+
+Service IP Configuration:
+{{- if .Values.service.ipFamilyPolicy }}
+ IP Family Policy: {{ .Values.service.ipFamilyPolicy }}
+{{- end }}
+{{- if .Values.service.ipFamilies }}
+ IP Families: {{ .Values.service.ipFamilies | join ", " }}
+ {{- if eq (index .Values.service.ipFamilies 0) "IPv6" }}
+ ⚠ NOTE: IPv6 primary - ensure your probes use httpGet instead of tcpSocket for compatibility
+ {{- end }}
+ {{- if has "IPv6" .Values.service.ipFamilies }}
+ {{- if not .Values.service.ipFamilyPolicy }}
+ ⚠ WARNING: ipFamilies includes IPv6 but ipFamilyPolicy is not set
+ {{- end }}
+ {{- end }}
+{{- end }}
+{{- end }}
Deployment Configuration:
Revision History Limit: {{ .Values.revisionHistoryLimit }}
{{- $limit := int .Values.revisionHistoryLimit }}
diff --git a/charts/jellyfin/templates/deployment.yaml b/charts/jellyfin/templates/deployment.yaml
index 0c4ea9a..6f4b6ae 100644
--- a/charts/jellyfin/templates/deployment.yaml
+++ b/charts/jellyfin/templates/deployment.yaml
@@ -62,6 +62,10 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
+ {{- with .Values.jellyfin.envFrom }}
+ envFrom:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
{{- with .Values.jellyfin.env }}
env:
{{- toYaml . | nindent 12 }}
@@ -111,6 +115,10 @@ spec:
hostPort: 1900
protocol: UDP
{{- end }}
+ {{- with .Values.startupProbe }}
+ startupProbe:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
@@ -122,15 +130,24 @@ spec:
name: config
- mountPath: /media
name: media
+ - mountPath: /cache
+ name: cache
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
- {{- with .Values.initContainers }}
+ {{- if or .Values.initContainers .Values.extraInitContainers }}
+ # Support both initContainers (deprecated) and extraInitContainers for backward compatibility.
+ # TODO: Remove initContainers support after 2030
initContainers:
- {{- toYaml . | nindent 6 }}
+ {{- if .Values.initContainers }}
+ {{- toYaml .Values.initContainers | nindent 6 }}
+ {{- end }}
+ {{- if .Values.extraInitContainers }}
+ {{- toYaml .Values.extraInitContainers | nindent 6 }}
+ {{- end }}
{{- end }}
volumes:
- name: config
@@ -155,6 +172,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/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
diff --git a/charts/jellyfin/tests/envfrom_test.yaml b/charts/jellyfin/tests/envfrom_test.yaml
new file mode 100644
index 0000000..a5f7165
--- /dev/null
+++ b/charts/jellyfin/tests/envfrom_test.yaml
@@ -0,0 +1,122 @@
+suite: test envFrom
+templates:
+ - deployment.yaml
+tests:
+ - it: should not have envFrom by default
+ asserts:
+ - isNull:
+ path: spec.template.spec.containers[0].envFrom
+
+ - it: should add envFrom from ConfigMap
+ set:
+ jellyfin.envFrom:
+ - configMapRef:
+ name: jellyfin-config
+ asserts:
+ - isNotNull:
+ path: spec.template.spec.containers[0].envFrom
+ - lengthEqual:
+ path: spec.template.spec.containers[0].envFrom
+ count: 1
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[0].configMapRef.name
+ value: jellyfin-config
+
+ - it: should add envFrom from Secret
+ set:
+ jellyfin.envFrom:
+ - secretRef:
+ name: jellyfin-secrets
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[0].secretRef.name
+ value: jellyfin-secrets
+
+ - it: should support multiple envFrom sources
+ set:
+ jellyfin.envFrom:
+ - configMapRef:
+ name: jellyfin-config
+ - secretRef:
+ name: jellyfin-secrets
+ - configMapRef:
+ name: jellyfin-extra-config
+ asserts:
+ - lengthEqual:
+ path: spec.template.spec.containers[0].envFrom
+ count: 3
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[0].configMapRef.name
+ value: jellyfin-config
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[1].secretRef.name
+ value: jellyfin-secrets
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[2].configMapRef.name
+ value: jellyfin-extra-config
+
+ - it: should support optional ConfigMap
+ set:
+ jellyfin.envFrom:
+ - configMapRef:
+ name: optional-config
+ optional: true
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[0].configMapRef.optional
+ value: true
+
+ - it: should support optional Secret
+ set:
+ jellyfin.envFrom:
+ - secretRef:
+ name: optional-secret
+ optional: true
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[0].secretRef.optional
+ value: true
+
+ - it: should support prefix for ConfigMap
+ set:
+ jellyfin.envFrom:
+ - configMapRef:
+ name: my-config
+ prefix: CONFIG_
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[0].prefix
+ value: CONFIG_
+
+ - it: should support prefix for Secret
+ set:
+ jellyfin.envFrom:
+ - secretRef:
+ name: my-secret
+ prefix: SECRET_
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[0].prefix
+ value: SECRET_
+
+ - it: should work alongside regular env variables
+ set:
+ jellyfin.env:
+ - name: CUSTOM_VAR
+ value: custom-value
+ jellyfin.envFrom:
+ - configMapRef:
+ name: jellyfin-config
+ asserts:
+ - isNotNull:
+ path: spec.template.spec.containers[0].env
+ - isNotNull:
+ path: spec.template.spec.containers[0].envFrom
+ - contains:
+ path: spec.template.spec.containers[0].env
+ content:
+ name: CUSTOM_VAR
+ value: custom-value
+ - equal:
+ path: spec.template.spec.containers[0].envFrom[0].configMapRef.name
+ value: jellyfin-config
diff --git a/charts/jellyfin/tests/init_containers_test.yaml b/charts/jellyfin/tests/init_containers_test.yaml
new file mode 100644
index 0000000..7ea5259
--- /dev/null
+++ b/charts/jellyfin/tests/init_containers_test.yaml
@@ -0,0 +1,119 @@
+suite: test init containers
+templates:
+ - deployment.yaml
+tests:
+ - it: should not have init containers by default
+ asserts:
+ - isNull:
+ path: spec.template.spec.initContainers
+
+ - it: should add extraInitContainers when specified
+ set:
+ extraInitContainers:
+ - name: init-config
+ image: busybox:1.35
+ command: ['sh', '-c', 'echo Initializing']
+ asserts:
+ - isNotNull:
+ path: spec.template.spec.initContainers
+ - lengthEqual:
+ path: spec.template.spec.initContainers
+ count: 1
+ - equal:
+ path: spec.template.spec.initContainers[0].name
+ value: init-config
+ - equal:
+ path: spec.template.spec.initContainers[0].image
+ value: busybox:1.35
+
+ - it: should support multiple extraInitContainers
+ set:
+ extraInitContainers:
+ - name: init-1
+ image: busybox:1.35
+ - name: init-2
+ image: alpine:3.18
+ asserts:
+ - lengthEqual:
+ path: spec.template.spec.initContainers
+ count: 2
+ - equal:
+ path: spec.template.spec.initContainers[0].name
+ value: init-1
+ - equal:
+ path: spec.template.spec.initContainers[1].name
+ value: init-2
+
+ - it: should preserve full container spec in extraInitContainers
+ set:
+ extraInitContainers:
+ - name: init-permissions
+ image: busybox:1.35
+ command: ['sh', '-c']
+ args: ['chown -R 1000:1000 /config']
+ volumeMounts:
+ - name: config
+ mountPath: /config
+ securityContext:
+ runAsUser: 0
+ asserts:
+ - equal:
+ path: spec.template.spec.initContainers[0].command[0]
+ value: sh
+ - equal:
+ path: spec.template.spec.initContainers[0].args[0]
+ value: 'chown -R 1000:1000 /config'
+ - equal:
+ path: spec.template.spec.initContainers[0].volumeMounts[0].name
+ value: config
+ - equal:
+ path: spec.template.spec.initContainers[0].securityContext.runAsUser
+ value: 0
+
+ # Deprecated initContainers (backward compatibility)
+ - it: should still support deprecated initContainers parameter
+ set:
+ initContainers:
+ - name: legacy-init
+ image: busybox:1.35
+ asserts:
+ - lengthEqual:
+ path: spec.template.spec.initContainers
+ count: 1
+ - equal:
+ path: spec.template.spec.initContainers[0].name
+ value: legacy-init
+
+ - it: should merge initContainers and extraInitContainers
+ set:
+ initContainers:
+ - name: legacy-init
+ image: busybox:1.35
+ extraInitContainers:
+ - name: new-init
+ image: alpine:3.18
+ asserts:
+ - lengthEqual:
+ path: spec.template.spec.initContainers
+ count: 2
+ - equal:
+ path: spec.template.spec.initContainers[0].name
+ value: legacy-init
+ - equal:
+ path: spec.template.spec.initContainers[1].name
+ value: new-init
+
+ - it: should prioritize extraInitContainers over initContainers
+ set:
+ initContainers:
+ - name: old-way
+ image: busybox:1.35
+ extraInitContainers:
+ - name: new-way
+ image: alpine:3.18
+ asserts:
+ - contains:
+ path: spec.template.spec.initContainers
+ content:
+ name: new-way
+ image: alpine:3.18
diff --git a/charts/jellyfin/tests/service_ipv6_test.yaml b/charts/jellyfin/tests/service_ipv6_test.yaml
new file mode 100644
index 0000000..5dee731
--- /dev/null
+++ b/charts/jellyfin/tests/service_ipv6_test.yaml
@@ -0,0 +1,128 @@
+suite: test service ipv6 and dual-stack
+templates:
+ - service.yaml
+tests:
+ - it: should not set ipFamilyPolicy by default
+ asserts:
+ - isNull:
+ path: spec.ipFamilyPolicy
+
+ - it: should not set ipFamilies by default
+ asserts:
+ - isNull:
+ path: spec.ipFamilies
+
+ - it: should set ipFamilyPolicy when specified
+ set:
+ service.ipFamilyPolicy: PreferDualStack
+ asserts:
+ - equal:
+ path: spec.ipFamilyPolicy
+ value: PreferDualStack
+
+ - it: should support SingleStack policy
+ set:
+ service.ipFamilyPolicy: SingleStack
+ asserts:
+ - equal:
+ path: spec.ipFamilyPolicy
+ value: SingleStack
+
+ - it: should support RequireDualStack policy
+ set:
+ service.ipFamilyPolicy: RequireDualStack
+ asserts:
+ - equal:
+ path: spec.ipFamilyPolicy
+ value: RequireDualStack
+
+ - it: should set ipFamilies for IPv4 only
+ set:
+ service.ipFamilies:
+ - IPv4
+ asserts:
+ - equal:
+ path: spec.ipFamilies[0]
+ value: IPv4
+
+ - it: should set ipFamilies for IPv6 only
+ set:
+ service.ipFamilies:
+ - IPv6
+ asserts:
+ - equal:
+ path: spec.ipFamilies[0]
+ value: IPv6
+
+ - it: should set ipFamilies for dual-stack IPv4 primary
+ set:
+ service.ipFamilies:
+ - IPv4
+ - IPv6
+ asserts:
+ - equal:
+ path: spec.ipFamilies[0]
+ value: IPv4
+ - equal:
+ path: spec.ipFamilies[1]
+ value: IPv6
+
+ - it: should set ipFamilies for dual-stack IPv6 primary
+ set:
+ service.ipFamilies:
+ - IPv6
+ - IPv4
+ asserts:
+ - equal:
+ path: spec.ipFamilies[0]
+ value: IPv6
+ - equal:
+ path: spec.ipFamilies[1]
+ value: IPv4
+
+ - it: should work with PreferDualStack and both families
+ set:
+ service.ipFamilyPolicy: PreferDualStack
+ service.ipFamilies:
+ - IPv4
+ - IPv6
+ asserts:
+ - equal:
+ path: spec.ipFamilyPolicy
+ value: PreferDualStack
+ - equal:
+ path: spec.ipFamilies[0]
+ value: IPv4
+ - equal:
+ path: spec.ipFamilies[1]
+ value: IPv6
+
+ - it: should work with RequireDualStack and both families
+ set:
+ service.ipFamilyPolicy: RequireDualStack
+ service.ipFamilies:
+ - IPv6
+ - IPv4
+ asserts:
+ - equal:
+ path: spec.ipFamilyPolicy
+ value: RequireDualStack
+ - equal:
+ path: spec.ipFamilies[0]
+ value: IPv6
+ - equal:
+ path: spec.ipFamilies[1]
+ value: IPv4
+
+ - it: should work with SingleStack IPv6 only
+ set:
+ service.ipFamilyPolicy: SingleStack
+ service.ipFamilies:
+ - IPv6
+ asserts:
+ - equal:
+ path: spec.ipFamilyPolicy
+ value: SingleStack
+ - equal:
+ path: spec.ipFamilies[0]
+ value: IPv6
diff --git a/charts/jellyfin/tests/startup_probe_test.yaml b/charts/jellyfin/tests/startup_probe_test.yaml
new file mode 100644
index 0000000..ee7430f
--- /dev/null
+++ b/charts/jellyfin/tests/startup_probe_test.yaml
@@ -0,0 +1,92 @@
+suite: test startup probe
+templates:
+ - deployment.yaml
+tests:
+ - it: should have startup probe by default
+ asserts:
+ - isNotNull:
+ path: spec.template.spec.containers[0].startupProbe
+
+ - it: should use tcpSocket by default
+ asserts:
+ - isNotNull:
+ path: spec.template.spec.containers[0].startupProbe.tcpSocket
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.tcpSocket.port
+ value: http
+
+ - it: should have default timing values
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds
+ value: 0
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.periodSeconds
+ value: 10
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.failureThreshold
+ value: 30
+
+ - it: should allow custom initialDelaySeconds
+ set:
+ startupProbe.initialDelaySeconds: 5
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds
+ value: 5
+
+ - it: should allow custom periodSeconds
+ set:
+ startupProbe.periodSeconds: 15
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.periodSeconds
+ value: 15
+
+ - it: should allow custom failureThreshold
+ set:
+ startupProbe.failureThreshold: 60
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.failureThreshold
+ value: 60
+
+ - it: should support httpGet probe
+ set:
+ startupProbe:
+ httpGet:
+ path: /health
+ port: http
+ initialDelaySeconds: 0
+ periodSeconds: 10
+ failureThreshold: 30
+ asserts:
+ - isNotNull:
+ path: spec.template.spec.containers[0].startupProbe.httpGet
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.httpGet.path
+ value: /health
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.httpGet.port
+ value: http
+
+ - it: should support custom port
+ set:
+ startupProbe.tcpSocket.port: 9000
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.tcpSocket.port
+ value: 9000
+
+ - it: should calculate correct timeout window
+ set:
+ startupProbe.periodSeconds: 10
+ startupProbe.failureThreshold: 30
+ asserts:
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.periodSeconds
+ value: 10
+ - equal:
+ path: spec.template.spec.containers[0].startupProbe.failureThreshold
+ value: 30
+ # Total startup time allowed: 10 * 30 = 300 seconds (5 minutes)
diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml
index d6c6c3d..fe9826f 100644
--- a/charts/jellyfin/values.yaml
+++ b/charts/jellyfin/values.yaml
@@ -91,8 +91,17 @@ service:
# -- Service type (ClusterIP, NodePort, or LoadBalancer).
type: ClusterIP
# -- Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/
+ # Options: SingleStack, PreferDualStack, RequireDualStack
+ # For IPv6-only clusters, use "SingleStack" with ipFamilies: ["IPv6"]
+ # For dual-stack, use "PreferDualStack" or "RequireDualStack" with ipFamilies: ["IPv4", "IPv6"] or ["IPv6", "IPv4"]
ipFamilyPolicy: ""
# -- Supported IP families (IPv4, IPv6).
+ # Examples:
+ # IPv4 only: ["IPv4"]
+ # IPv6 only: ["IPv6"]
+ # Dual-stack (IPv4 primary): ["IPv4", "IPv6"]
+ # Dual-stack (IPv6 primary): ["IPv6", "IPv4"]
+ # Note: When using IPv6, ensure your health checks are compatible (consider using httpGet instead of tcpSocket)
ipFamilies: []
# -- Specific IP address for the LoadBalancer.
loadBalancerIP: ""
@@ -158,21 +167,39 @@ resources: {}
# cpu: 100m
# memory: 128Mi
-# -- Configure liveness probe for Jellyfin.
-livenessProbe:
- initialDelaySeconds: 10
+# -- Configure startup probe for Jellyfin.
+# This probe gives Jellyfin enough time to start, especially with large media libraries.
+# After the startup probe succeeds once, liveness and readiness probes take over.
+startupProbe:
tcpSocket:
port: http
+ initialDelaySeconds: 0
+ periodSeconds: 10
+ failureThreshold: 30
+ # With these defaults, Jellyfin has up to 5 minutes (30 * 10s) to start.
+ # Adjust failureThreshold if you have very large media libraries or slow storage.
+
+# -- Configure liveness probe for Jellyfin.
+# This probe is disabled during startup (startup probe handles initial checks).
+# Uses httpGet for compatibility with both IPv4 and IPv6.
+livenessProbe:
+ httpGet:
+ path: /health
+ port: http
+ initialDelaySeconds: 10
# successThreshold: 1
# failureThreshold: 3
# timeoutSeconds: 1
# periodSeconds: 10
# -- Configure readiness probe for Jellyfin.
+# This probe is disabled during startup (startup probe handles initial checks).
+# Uses httpGet for compatibility with both IPv4 and IPv6.
readinessProbe:
- initialDelaySeconds: 10
- tcpSocket:
+ httpGet:
+ path: /health
port: http
+ initialDelaySeconds: 10
# successThreshold: 1
# failureThreshold: 3
# timeoutSeconds: 1
@@ -216,7 +243,20 @@ jellyfin:
command: []
# -- Additional arguments for the entrypoint command.
args: []
+ # -- Load environment variables from ConfigMap or Secret.
+ # See: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables
+ # Example:
+ # envFrom:
+ # - configMapRef:
+ # name: jellyfin-config
+ # - secretRef:
+ # name: jellyfin-secrets
+ envFrom: []
# -- Additional environment variables for the container.
+ # Example:
+ # env:
+ # - name: JELLYFIN_CACHE_DIR
+ # value: /cache
env: []
persistence:
@@ -247,6 +287,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:
@@ -279,8 +335,22 @@ metrics:
# -- Target labels to add to the scraped metrics
targetLabels: []
-# -- additional init containers to run inside the pod.
+# -- DEPRECATED: Use extraInitContainers instead. Will be removed after 2030.
+# @deprecated - This parameter is deprecated, use extraInitContainers instead
+initContainers: []
+
+# -- Additional init containers to run inside the pod.
+# Init containers run before the main application container starts.
+# See: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
+# Example:
+# extraInitContainers:
+# - name: init-config
+# image: busybox:1.35
+# command: ['sh', '-c', 'echo "Initializing..." && sleep 5']
+# volumeMounts:
+# - name: config
+# mountPath: /config
extraInitContainers: []
-
+
# -- additional sidecar containers to run inside the pod.
extraContainers: []