Introduction
Sometime ago I was running Plex Media Server running on my Synology DS1817+. After an update, the WebUI and my AndroidTV App stopped displaying my media. At this point, I started looking for other methods of hosting my videos. I moved to Emby, but wanted to move and utilize Free and Open Source Software. I present JellyFin!
Jellyfin became my top choice because it has similar features found in Plex while also being opensource. One of my requirements was running the software on capable hardware to stream my 4K Blu-Rays across my network which meant transcoding for my TVs. Luckily the softare can utilize both CPU and GPU transcoding. Another self-imposed requirement was running via Podman and having a configuration thats portable and repeatable, so that I could easily rebuild/redeploy the server at any moment (another benefit is running on OpenShift).
Utilizing Podman allows me to use a Pod configuration YAML to build my Jellyfin container. The full YAML can be found at the end of this post. In the next section, I’ll be covering the important bits to running JellyFin on my server.
Configuration
To start building our pod manifest for Podman we start with defining a Kubernetes pod.
Pod configuration:
apiVersion: v1
kind: Pod
metadata:
labels:
app: jellyfin
name: jellyfin
spec:
restartPolicy: Always
containers:
- name: server
image: docker.io/jellyfin/jellyfin:latest
ports:
- containerPort: 8096
hostPort: 8096
protocol: TCP
- containerPort: 8920
hostPort: 8920
protocol: TCP
To ensure Jellyfin bootstraps and start correctly we need a few environment variables.
Environment, Container:
spec:
containers:
- name: server
image: docker.io/jellyfin/jellyfin:latest
env:
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
value: "1"
- name: JELLYFIN_MEDIA_DIR
value: /media
- name: JELLYFIN_LOG_DIR
value: /config/log
- name: JELLYFIN_CONFIG_DIR
value: /config/config
- name: JELLYFIN_WEB_DIR
value: /jellyfin/jellyfin-web
- name: LC_ALL
value: en_US.UTF-8
- name: LANG
value: en_US.UTF-8
- name: JELLYFIN_CACHE_DIR
value: /cache
- name: JELLYFIN_FFMPEG
value: /usr/lib/jellyfin-ffmpeg/ffmpeg
- name: LANGUAGE
value: en_US:en
- name: JELLYFIN_DATA_DIR
value: /config
I keep my Media on a Synology DS1817+, and serve the media via NFS mount to my server.
Media Location, Volume:
spec:
volumes:
- hostPath:
path: /mnt/nfs/media
type: Directory
name: mnt-nfs-media
The following is used to tell the container on how to mount the NFS mount.
Media Location, Container Mount:
spec:
containers:
- name: server
image: docker.io/jellyfin/jellyfin:latest
volumeMounts:
- mountPath: /media
name: mnt-nfs-media-host-0
- mountPath: /cache
name: jellyfin-cache-pvc
- mountPath: /config
name: jellyfin-config-pvc
Podman also allows use of the nVidia GPUs by passing the following Environment variables.
nVidia Container Enablement:
spec:
containers:
- name: server
image: docker.io/jellyfin/jellyfin:latest
env:
- name: NVIDIA_VISIBLE_DEVICES
value: all
- name: NVIDIA_DRIVER_CAPABILITIES
value: all
Conclusion
As long as the media is hosted, using a containerized Jellyfin provides the flexabilty of moving the service where you can host it. The caution is the "platform" should be powerful enough to stream or transcode the media you want to watch. A Raspberry Pi 4 should be able to stream 1080p; my Synology DS1817+ could (sometimes) stream/transcode 4K video, but not well most of the times. Currently, I’m running my Jellyfin pod on a Dell R720 w/ nVidia Tesla M40 GPU, but due to a few quirks with the nVidia driver, I have disabled nVidia CUDA transcoding. The following code is the full original YAML manifest that I’ve used.
Full Configuration
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-3.4.1-dev
apiVersion: v1
kind: Pod
metadata:
labels:
app: jellyfin
name: jellyfin
spec:
containers:
- command:
- /jellyfin/jellyfin
env:
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
value: "1"
- name: JELLYFIN_MEDIA_DIR
value: /media
- name: JELLYFIN_LOG_DIR
value: /config/log
- name: JELLYFIN_CONFIG_DIR
value: /config/config
- name: JELLYFIN_WEB_DIR
value: /jellyfin/jellyfin-web
- name: LC_ALL
value: en_US.UTF-8
- name: LANG
value: en_US.UTF-8
- name: JELLYFIN_CACHE_DIR
value: /cache
- name: JELLYFIN_FFMPEG
value: /usr/lib/jellyfin-ffmpeg/ffmpeg
- name: LANGUAGE
value: en_US:en
- name: JELLYFIN_DATA_DIR
value: /config
- name: NVIDIA_VISIBLE_DEVICES
value: all
- name: NVIDIA_DRIVER_CAPABILITIES
value: all
image: docker.io/jellyfin/jellyfin:latest
name: server
ports:
- containerPort: 8096
hostPort: 8096
protocol: TCP
- containerPort: 8920
hostPort: 8920
protocol: TCP
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- CAP_MKNOD
- CAP_AUDIT_WRITE
privileged: false
readOnlyRootFilesystem: false
seLinuxOptions: {}
tty: true
volumeMounts:
- mountPath: /media
name: mnt-nfs-media-host-0
- mountPath: /cache
name: jellyfin-cache-pvc
- mountPath: /config
name: jellyfin-config-pvc
workingDir: /
dnsConfig: {}
restartPolicy: Always
volumes:
- hostPath:
path: /mnt/nfs/media
type: Directory
name: mnt-nfs-media-host-0
- name: jellyfin-cache-pvc
persistentVolumeClaim:
claimName: jellyfin-cache
- name: jellyfin-config-pvc
persistentVolumeClaim:
claimName: jellyfin-config