7d4cef51f5
Adds a production-ready Helm chart under deploy/helm/jellyfin-ha/. Motivated by a community request on Reddit: https://www.reddit.com/r/JellyfinCommunity/comments/1rvj17f/jellyfin_ha_on_kubernetes_redisbacked_transcode/oav7mlz/ Features: - StatefulSet with configurable replica count (default 2 for HA) - Redis subchart (in-cluster) wired to ITranscodeSessionStore via Jellyfin__TranscodeStore__RedisConnectionString env var - Supports external Redis via ha.transcodeStore.existingSecret or ha.transcodeStore.redisConnectionString - Optional in-cluster PostgreSQL StatefulSet (experimental, mirrors existing kubernetes/apps/media/jellyfin-postgres.yaml pattern) - RWX config + transcode PVCs (required for multi-pod session takeover) - Per-pod cache via volumeClaimTemplates (RWO) - Optional NFS PV+PVC for media library - Intel QSV / VA-API GPUgit checkout -b feat/helm-chart && git add deploy/ && legit add deploy/ && git commit -m dagit commit -m featss
406 lines
13 KiB
YAML
406 lines
13 KiB
YAML
# Default values for jellyfin-ha.
|
|
# This is a YAML-formatted file.
|
|
|
|
# -- Override the chart name.
|
|
nameOverride: ""
|
|
# -- Override the full resource name prefix.
|
|
fullnameOverride: ""
|
|
|
|
# -- Number of Jellyfin replicas.
|
|
# Set >= 2 to use HA mode. When replicaCount > 1, ha.enabled should be true
|
|
# and a Redis connection must be configured (via redis.enabled or ha.transcodeStore.redisConnectionString).
|
|
replicaCount: 2
|
|
|
|
# -- Container image configuration.
|
|
image:
|
|
repository: "your-registry/jellyfin-ha"
|
|
tag: "latest"
|
|
pullPolicy: IfNotPresent
|
|
|
|
# -- Image pull secrets (e.g. for private ECR registries).
|
|
# Example:
|
|
# - name: ecr-pull-secret
|
|
imagePullSecrets: []
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# HA (High-Availability) configuration
|
|
# ---------------------------------------------------------------------------
|
|
ha:
|
|
# -- Enable HA mode. When true, a Redis connection string is required
|
|
# (either via redis.enabled or ha.transcodeStore.redisConnectionString).
|
|
# When false, NullTranscodeSessionStore is used and behavior is identical
|
|
# to upstream Jellyfin.
|
|
enabled: true
|
|
|
|
transcodeStore:
|
|
# -- StackExchange.Redis connection string.
|
|
# Leave empty to auto-compose from the in-cluster Redis service when redis.enabled=true.
|
|
# Explicit examples:
|
|
# redis:6379
|
|
# redis:6379,password=secret
|
|
# redis.example.com:6380,ssl=true,abortConnect=false
|
|
# sentinel-host:26379,serviceName=mymaster
|
|
redisConnectionString: ""
|
|
|
|
# -- How long (seconds) a pod's transcode lease is valid before another pod may take over.
|
|
leaseDurationSeconds: 30
|
|
|
|
# -- Secret containing the Redis connection string.
|
|
# If set, the connection string is read from this secret instead of the value above.
|
|
# The secret must have a key named by existingSecret.key.
|
|
existingSecret: ""
|
|
existingSecretKey: "connection-string"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# In-cluster Redis (for transcode session store)
|
|
# ---------------------------------------------------------------------------
|
|
redis:
|
|
# -- Deploy an in-cluster Redis instance.
|
|
# Disable and set ha.transcodeStore.redisConnectionString to use an external Redis.
|
|
enabled: true
|
|
|
|
image:
|
|
repository: redis
|
|
tag: "7.4.2-alpine3.21"
|
|
pullPolicy: IfNotPresent
|
|
|
|
# -- Maximum memory for Redis to use.
|
|
maxmemory: "256mb"
|
|
# -- LRU eviction policy when maxmemory is reached.
|
|
maxmemoryPolicy: "allkeys-lru"
|
|
|
|
resources:
|
|
requests:
|
|
cpu: 25m
|
|
memory: 64Mi
|
|
limits:
|
|
cpu: 200m
|
|
memory: 256Mi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Jellyfin application configuration
|
|
# ---------------------------------------------------------------------------
|
|
config:
|
|
# -- The externally-reachable URL Jellyfin reports to clients.
|
|
publishedServerUrl: ""
|
|
|
|
# -- Disable UDP auto-discovery (port 7359).
|
|
# Recommended when running multiple replicas to prevent duplicate discovery responses.
|
|
autoDiscovery: false
|
|
|
|
# -- Jellyfin config directory inside the container.
|
|
# Must differ from the data/root directory to pass Jellyfin's sanity check.
|
|
configDir: "/config/config"
|
|
|
|
# -- Database provider: "SQLite" (default) or "Jellyfin-PostgreSQL" (experimental).
|
|
# When set to "Jellyfin-PostgreSQL", an init container will inject database.xml
|
|
# and the postgresql.enabled section (or an external connection string) must be configured.
|
|
databaseType: "SQLite"
|
|
|
|
# -- Extra environment variables to set on the Jellyfin container.
|
|
# Example:
|
|
# - name: JELLYFIN_Network__BaseUrl
|
|
# value: "/jellyfin"
|
|
extraEnv: []
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PostgreSQL (experimental — only needed when config.databaseType = Jellyfin-PostgreSQL)
|
|
# ---------------------------------------------------------------------------
|
|
postgresql:
|
|
# -- Deploy an in-cluster PostgreSQL instance.
|
|
enabled: false
|
|
|
|
image:
|
|
repository: postgres
|
|
tag: "16.6-alpine3.21"
|
|
pullPolicy: IfNotPresent
|
|
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
cpu: "1"
|
|
memory: 1Gi
|
|
|
|
persistence:
|
|
storageClass: ""
|
|
size: 5Gi
|
|
|
|
# -- Name of an existing secret with PostgreSQL credentials.
|
|
# Required when postgresql.enabled=true. The secret must contain:
|
|
# POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, DATABASE_URL
|
|
# Create it with:
|
|
# kubectl create secret generic jellyfin-postgres-credentials \
|
|
# --from-literal=POSTGRES_USER=jellyfin \
|
|
# --from-literal=POSTGRES_PASSWORD=<password> \
|
|
# --from-literal=POSTGRES_DB=jellyfin \
|
|
# --from-literal=DATABASE_URL="postgresql://jellyfin:<password>@<host>:5432/jellyfin"
|
|
existingSecret: "jellyfin-postgres-credentials"
|
|
|
|
service:
|
|
port: 5432
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GPU / hardware transcoding
|
|
# ---------------------------------------------------------------------------
|
|
gpu:
|
|
# -- Enable Intel QSV / VA-API hardware transcoding.
|
|
# Mounts /dev/dri from the host and sets the required security context.
|
|
enabled: false
|
|
|
|
intel:
|
|
# -- Node affinity label to prefer GPU-capable nodes.
|
|
nodeLabel:
|
|
key: gpu
|
|
value: intel-uhd-630
|
|
|
|
# -- Toleration for the GPU node taint.
|
|
toleration:
|
|
key: gpu
|
|
value: "true"
|
|
effect: NoSchedule
|
|
|
|
# -- Mount /dev/dri from the host (required for VA-API; implies privileged=true).
|
|
mountDri: true
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Persistence
|
|
# ---------------------------------------------------------------------------
|
|
persistence:
|
|
# Config volume — single-writer; RWO is fine for single-replica deployments.
|
|
# For multi-replica: use an RWX storage class (e.g. Longhorn RWX, NFS) or
|
|
# point all pods at an existing shared PVC via existingClaim.
|
|
config:
|
|
# -- Size of the config PVC.
|
|
size: 5Gi
|
|
# -- Storage class. Leave empty to use the cluster default.
|
|
storageClass: ""
|
|
# -- Access mode. Use ReadWriteMany when replicaCount > 1 and sharing one PVC.
|
|
accessMode: ReadWriteMany
|
|
# -- Reuse an existing PVC. When set, no new PVC is created.
|
|
existingClaim: ""
|
|
|
|
# Media volume — read-only mount shared by all pods.
|
|
# Configure one of: existingClaim (for an existing PVC), nfs (to create an NFS PV+PVC),
|
|
# or existingClaim pointing at a pre-created PVC.
|
|
media:
|
|
# -- Reuse an existing media PVC (most common for homelab NFS/Longhorn setups).
|
|
existingClaim: ""
|
|
# -- Create an NFS-backed PV and PVC for the media library.
|
|
nfs:
|
|
enabled: false
|
|
server: "your-nas.local"
|
|
path: "/media"
|
|
size: 1Ti
|
|
storageClass: ""
|
|
|
|
# Transcode volume — MUST be ReadWriteMany when replicaCount > 1 so that
|
|
# a recovering pod can read HLS segments written by the pod it is replacing.
|
|
# When replicaCount=1, ReadWriteOnce is acceptable.
|
|
transcode:
|
|
size: 30Gi
|
|
storageClass: ""
|
|
accessMode: ReadWriteMany
|
|
existingClaim: ""
|
|
|
|
# Per-pod cache volume — local to each pod; always RWO.
|
|
# Created via StatefulSet volumeClaimTemplates (one PVC per pod).
|
|
cache:
|
|
size: 30Gi
|
|
storageClass: ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Service
|
|
# ---------------------------------------------------------------------------
|
|
service:
|
|
type: ClusterIP
|
|
port: 8096
|
|
# -- Annotations for the Service resource.
|
|
annotations: {}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Ingress (standard Kubernetes Ingress)
|
|
# ---------------------------------------------------------------------------
|
|
ingress:
|
|
enabled: false
|
|
# -- Ingress class name (e.g. "nginx", "traefik").
|
|
className: ""
|
|
annotations: {}
|
|
# cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
# nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
|
hosts:
|
|
- host: jellyfin.example.com
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
tls: []
|
|
# - secretName: jellyfin-tls
|
|
# hosts:
|
|
# - jellyfin.example.com
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Traefik IngressRoute (Traefik v3 CRD — used by k3s default ingress)
|
|
# ---------------------------------------------------------------------------
|
|
traefikIngressRoute:
|
|
enabled: false
|
|
entryPoints:
|
|
- websecure
|
|
# -- Hostname for the Traefik routing rule.
|
|
host: "jellyfin.example.com"
|
|
# -- Enable sticky session cookie (recommended for multi-replica Jellyfin).
|
|
sticky:
|
|
enabled: true
|
|
cookieName: "jellyfin-server-id"
|
|
httpOnly: true
|
|
secure: true
|
|
# -- cert-manager Certificate resource for TLS.
|
|
tls:
|
|
enabled: false
|
|
secretName: "jellyfin-tls"
|
|
clusterIssuer: "letsencrypt-prod"
|
|
dnsNames: []
|
|
# - jellyfin.example.com
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Resource requests and limits for the Jellyfin container
|
|
# ---------------------------------------------------------------------------
|
|
resources:
|
|
requests:
|
|
cpu: 500m
|
|
memory: 1Gi
|
|
limits:
|
|
cpu: "4"
|
|
memory: 4Gi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Liveness and readiness probes
|
|
# ---------------------------------------------------------------------------
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: http
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
timeoutSeconds: 5
|
|
failureThreshold: 3
|
|
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: http
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
failureThreshold: 3
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Security context
|
|
# ---------------------------------------------------------------------------
|
|
# Container-level security context.
|
|
securityContext:
|
|
# -- Set to true only when GPU passthrough via /dev/dri is required.
|
|
# privileged=true is required for DRM ioctls (VA-API). Omit (false) for
|
|
# software-only transcoding.
|
|
privileged: false
|
|
# -- UID for the Jellyfin process. Use 10010 to match the svc-jellyfin NAS account
|
|
# when NFS root_squash is enabled.
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
|
|
# Pod-level security context.
|
|
podSecurityContext:
|
|
# -- fsGroup ensures mounted volumes are group-writable.
|
|
fsGroup: 1000
|
|
# -- Additional groups for /dev/dri access (video=44, render=109 or 991).
|
|
supplementalGroups: []
|
|
# - 44 # video
|
|
# - 109 # render (legacy)
|
|
# - 991 # render (Debian 13 trixie)
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Service account
|
|
# ---------------------------------------------------------------------------
|
|
serviceAccount:
|
|
create: false
|
|
name: ""
|
|
annotations: {}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pod Disruption Budget
|
|
# ---------------------------------------------------------------------------
|
|
podDisruptionBudget:
|
|
enabled: true
|
|
minAvailable: 1
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pod anti-affinity (spread replicas across nodes for node-level HA)
|
|
# ---------------------------------------------------------------------------
|
|
podAntiAffinity:
|
|
enabled: true
|
|
# -- "preferred" won't block scheduling if nodes are insufficient.
|
|
# Use "required" to enforce strict cross-node placement.
|
|
type: preferred
|
|
weight: 100
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Prometheus ServiceMonitor
|
|
# Note: Jellyfin has no native /metrics endpoint. This ServiceMonitor is
|
|
# included for future use (e.g. if you add a sidecar exporter) or for
|
|
# blackbox-style readiness monitoring. Disable if not using kube-prometheus-stack.
|
|
# ---------------------------------------------------------------------------
|
|
serviceMonitor:
|
|
enabled: false
|
|
# -- Scrape interval.
|
|
interval: "30s"
|
|
# -- Scrape path (Jellyfin does not expose Prometheus metrics natively).
|
|
path: /metrics
|
|
# -- Additional labels to add to the ServiceMonitor (e.g. to match a Prometheus release label).
|
|
additionalLabels: {}
|
|
# release: kube-prometheus-stack
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Runtime config (jellyfin.runtimeconfig.json)
|
|
# Set dotnet runtime switches here if needed. Leave empty for defaults.
|
|
# ---------------------------------------------------------------------------
|
|
runtimeConfig:
|
|
enabled: false
|
|
# -- Raw JSON content for jellyfin.runtimeconfig.json.
|
|
# See jellyfin-runtimeconfig ConfigMap in the existing manifests for an example.
|
|
json: |
|
|
{
|
|
"configProperties": {}
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Extra Kubernetes resources
|
|
# ---------------------------------------------------------------------------
|
|
# -- Additional volumes to attach to the Jellyfin pod.
|
|
extraVolumes: []
|
|
# - name: my-extra-config
|
|
# configMap:
|
|
# name: my-configmap
|
|
|
|
# -- Additional volume mounts for the Jellyfin container.
|
|
extraVolumeMounts: []
|
|
# - name: my-extra-config
|
|
# mountPath: /etc/my-config
|
|
|
|
# -- Additional init containers.
|
|
extraInitContainers: []
|
|
|
|
# -- Annotations to add to the StatefulSet.
|
|
annotations: {}
|
|
# -- Annotations to add to individual pods.
|
|
podAnnotations: {}
|
|
# -- Labels to add to the StatefulSet.
|
|
labels: {}
|
|
# -- Labels to add to individual pods.
|
|
podLabels: {}
|
|
|
|
# -- Update strategy for the StatefulSet.
|
|
updateStrategy:
|
|
type: RollingUpdate
|