The jellyfin-config Schedule had no PVC selector and the k8up chart runs
skipWithoutAnnotation=false, so restic swept in every PVC in the namespace
(media, transcode scratch, redis, per-pod cache, CNPG data) — RWO volumes
also fail to mount while in use. k8up 4.10.0's Schedule CRD has no positive
PVC selector, so exclude every non-config PVC by annotation.
- Annotate media-tv, media-movies, transcode and redis-data PVCs with
k8up.io/backup: "false".
- Annotate the per-pod cache volumeClaimTemplate the same way.
- Propagate the annotation to the CNPG data PVCs via inheritedMetadata
(postgres has its own barmanObjectStore backup).
- Leave jellyfin-config unannotated so it remains the only backup target.
Protect the jellyfin-config PVC (library metadata, plugins, config XML)
with daily restic backups to the new Ceph RGW config-backup bucket.
- Add a k8up.io Schedule: daily backup (02:00), weekly prune (Sun 03:00,
keep 14 daily/8 weekly/12 monthly) and weekly check (Sun 04:00)
- Source S3 creds from the cephrgw BucketAccess Secret and the restic
repo password from Vault via a VaultStaticSecret (kv path
kubernetes/namespace/jellyfin/default/k8up-restic)
- Add the namespace VaultAuth (default role/SA) VSO needs to sync it
- Mount the reflected vault-ca-cert into the restic pods so restic trusts
the internal unkin.net CA on s3.ceph.unkin.net
- Wire the new files into the jellyfin kustomization
Give the jellyfin backup user a second bucket so k8up can store restic
backups of the jellyfin-config PVC alongside the existing CNPG barman
bucket (one user, two buckets).
- Add a jellyfin-config-backup Bucket owned by the existing
cnpg-jellyfin-backup ObjectStoreUser
- Add a read-write BucketAccess emitting jellyfin-config-backup-s3 with
the S3 creds k8up consumes
- Wire the new file into the jellyfin kustomization
Separate tv and movies onto their own volumes so sonarr/radarr can each
mount and manage their library individually later, and move media storage
off cephfs-raid6-retain onto cephfs-raid5-delete.
- Remove single jellyfin-media PVC (RWX, cephfs-raid6-retain)
- Add jellyfin-media-tv and jellyfin-media-movies PVCs (RWX, cephfs-raid5-delete, 500Gi each, expandable)
- Mount tv read-only at /media/tv and movies read-only at /media/movies in the statefulset
- Update kustomization resources to reference the two new PVCs
The build repo's release pipeline pushes the runtime image to the
artifactapi local docker registry, not Gitea. Point the StatefulSet at
artifactapi.k8s.syd1.au.unkin.net/docker-internal/jellyfin-ha:v0.1.0 so
the deploy pulls the image the build actually produces. Pull is anonymous
in-cluster (no imagePullSecret), matching every other docker-internal
workload in the estate (encapi, pdbmux, bind-operator, cephrgw, age-api).
The pod already requests gpu.intel.com/i915 and joins the render/video
groups, but Jellyfin transcodes in software until hardware acceleration
is turned on in its encoding config, which the fork does not template.
Seed /config/config/encoding.xml from the init container so VAAPI on the
injected Intel render node (/dev/dri/renderD128) is active on first boot:
HardwareAccelerationType vaapi, EnableHardwareEncoding, h264/hevc decode,
tonemapping left off. Rename the init container to inject-config and write
each seed file only when absent, so admin changes persisted to the shared
RWX /config are never clobbered on restart.
Turn the single-replica jellyfin-ha app into a proper high-availability
deployment so the fork's Redis-coordinated distributed transcoding and
PostgreSQL main database can actually be exercised.
- Replace the Deployment with a 2-replica StatefulSet for stable pod
identity; set JELLYFIN_INSTANCE_ID from metadata.name (the fork's Redis
transcode-lease owner id), add soft podAntiAffinity and a PDB
minAvailable 1.
- Move the main Jellyfin DB to PostgreSQL via a CloudNativePG trio
(3-instance Cluster, PgBouncer Pooler, Ceph RGW barman backups) mirroring
the litellm pattern; an init container writes database.xml selecting the
fork's Jellyfin-PostgreSQL provider and the DSN is composed from the
CNPG-generated app secret pointed at the pooler.
- Share /config on an RWX cephfs PVC across replicas; keep /cache per-pod
via a volumeClaimTemplate.
- Fix the transcode mount to the fork's real path /config/transcodes on the
RWX PVC (raid5) so a surviving pod can resume the segments of the pod it
takes over.
- Add Intel iGPU hardware transcoding via the gpu.intel.com/i915 device
plugin resource plus render/video supplemental groups.
- Switch the Service to sessionAffinity ClientIP to reduce transcode churn.
- Disable UDP auto-discovery. Library scans still run on every replica; a
single-scanner leader election is a planned follow-up.
Deploys the jellyfin-ha fork (git.unkin.net/unkin/jellyfin-ha) to au-syd1
via ArgoCD, under a dedicated media AppProject/ApplicationSet rather than
extending platform.
- New media AppProject + media-apps ApplicationSet (watches
apps/overlays/*/jellyfin); registered in the argocd kustomizations
- apps/base/jellyfin: namespace, deployment (single replica to start),
service, in-namespace Redis (transcode session store), gateway + httproute
at jellyfin.k8s.syd1.au.unkin.net
- Storage: RWO config (cephrbd), RWX transcode scratch and RWX media
library (cephfs) per the HA fork's pod-takeover requirement
- au-syd1 overlay