Skip to main content

Jellyfin

Jellyfin is a fully open-source, fork-of-Emby media server: video, music, and photo streaming to any device, with no account, no telemetry, and no licence wall. In this homelab it sits on top of an NFS share from TrueNAS (read-only media library) and a Longhorn-encrypted PVC (storage component) for its own config and metadata, exposed via Envoy Gateway on jellyfin.web.kueber.eu.

Why Jellyfin

  • No account wall. Plex requires a Plex.tv account and brokers connections through their servers; Jellyfin is fully local and works without any external service.
  • Truly free. Plex Pass and Emby Premiere paywall features (hardware transcoding, mobile sync, live TV) that Jellyfin ships in the base build.
  • Client ecosystem is wide enough. Native apps for Android TV, iOS, Android, web, plus first-class support from Infuse, Findroid, Streamyfin, and (via jellycon) Kodi.
  • Plays well with the storage layout. TrueNAS exports the media share over NFS, Jellyfin mounts it read-only, and writes only its config + metadata into the cluster — keeps the per-app PVC small and easy to back up.

Alternatives considered

OptionHostedSelf-hostedWhy not
PlexpartialAccount required; Plex Pass paywalls hardware transcoding
EmbyClosed-source after the Jellyfin fork; key features need Premiere
Netflix / streaming servicesLibrary churn; nothing offline; no control over codecs or quality

Installation

The Jellyfin install is straightforward — one Deployment, two volumes, one HTTPRoute — but every bit of it leans on a primitive defined elsewhere:

  • Single Deployment in the jellyfin namespace, image pinned by digest. Renovate keeps the tag fresh.
  • Storage split across two volumes. A 150Gi ReadWriteOnce PVC on the longhorn-encrypted storage class (storage resource) for Jellyfin's own config and metadata, annotated k8up.io/backup: "true" so the backups component picks it up. The media library itself is an NFS PV pointing at TrueNAS, mounted read-only.
  • Routing via HTTPRoute to the public Envoy Gateway listener — TLS terminated at the edge by cert-manager on the gateway level, not in the pod.
  • No in-cluster database. Jellyfin keeps its state in an embedded SQLite next to the config PVC, so there's no CNPG dependency.
  • Image: linuxserver.io build. The container expects PUID/PGID to align ownership of the NFS mount and the config PVC; that means it runs as runAsUser: 0 with a chown at startup rather than as a non-root user. It's the pragmatic trade for the linuxserver build's PUID/PGID handling.

Administration

  • Logging in for the first time. The first browser hit on a fresh deploy launches the setup wizard — pick a username, point Jellyfin at the NFS-mounted media paths, and let it scan. The scan is the long step on first run.
  • Adding a library. Admin Dashboard → Libraries → Add Media Library. Paths are the in-container mount points (/data/movies, /data/shows), not the host paths.
  • Hardware transcoding. Talos exposes the integrated Intel GPU via the intel-gpu-plugin DaemonSet; Jellyfin's Playback → Hardware acceleration picks it up as VA-API. Soft-fallback to software transcoding if the GPU isn't scheduled on the node.
  • SSO is not wired up. Jellyfin's own user store handles auth. The SSO-Auth plugin is an option for delegating to Keycloak, but the value is marginal for the handful of accounts on this server.
  • Upgrades. Digest-pinned image; Renovate auto-merges patch + digest bumps per the Renovate policy. Minor/major bumps need a human read — releases occasionally include schema migrations on the first start after upgrade.
  • Backups. k8up.io/backup: "true" on the config PVC is the only knob; the backups component schedules the restic snapshot to Hetzner S3. The media library on TrueNAS is not backed up via k8up — see Topics → Backups end-to-end for the full storage-tier story.

Usage

  • Browser, mobile apps, and TV apps all just point at https://jellyfin.web.kueber.eu.
  • Direct-play whenever possible. Most modern clients (Infuse, Streamyfin, the Android TV app) handle H.264 / H.265 / AV1 natively, so the server hands the file off as-is. Only the lowest-common-denominator clients trigger transcoding.
  • Offline downloads. The official mobile apps support sync to local storage; useful before flights, less so day-to-day.
  • Metadata. Jellyfin pulls from TheMovieDB / TheTVDB at scan time and caches in the config PVC. Re-scanning a library re-fetches missing metadata but does not overwrite manual edits.

Cluster Deployment

Jellyfin — Talos cluster

Cluster-specific notes only. General product info, "why we use it", and alternatives live in docusaurus/docs/apps/jellyfin.mdx.

Deviations from defaults

Defaults live in docusaurus/docs/apps/jellyfin.mdx — document anything this cluster does differently here, with a one-line reason.

Kubernetes Metadata
  • Image: linuxserver/jellyfin:10.11.11@sha256:c123ef2f82195e377d8ca3df275f13f9dfa5c6e61b195958662b42b5e54362ab
Rendered manifests (kustomize build)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kustomize.toolkit.fluxcd.io/force: enabled
labels:
app: jellyfin
name: jellyfin
namespace: jellyfin
spec:
replicas: 1
selector:
matchLabels:
app: jellyfin
homepage: active
ingress: public
strategy:
rollingUpdate: null
type: Recreate
template:
metadata:
labels:
app: jellyfin
homepage: active
ingress: public
spec:
containers:
- env:
- name: PUID
value: '0'
- name: GUID
value: '0'
image: linuxserver/jellyfin:10.11.11@sha256:c123ef2f82195e377d8ca3df275f13f9dfa5c6e61b195958662b42b5e54362ab
name: jellyfin
ports:
- containerPort: 8096
name: web
protocol: TCP
readinessProbe:
failureThreshold: 1
httpGet:
path: /
port: 8096
initialDelaySeconds: 2
periodSeconds: 3
successThreshold: 1
timeoutSeconds: 2
resources:
limits:
gpu.intel.com/i915: 1
memory: 4162Mi
requests:
cpu: 135m
gpu.intel.com/i915: 1
memory: 4162Mi
volumeMounts:
- mountPath: /config
name: jellyfin-config
- mountPath: /data
name: jellyfin-data
securityContext:
fsGroup: 104
seccompProfile:
type: RuntimeDefault
volumes:
- name: jellyfin-config
persistentVolumeClaim:
claimName: jellyfin-config
- name: jellyfin-data
persistentVolumeClaim:
claimName: jellyfin-truenas-nfs-videos