Jellyfin with Intel Quick Sync
A rootless Jellyfin media server with hardware transcoding on an Intel iGPU.
- Standalone container
- Rootless: systemctl --user
- Tested with podman 5.0 and newer
Last updated
See where this fits on the homelab connections map, with every port it uses.
In short: Jellyfin runs rootless with AddDevice=/dev/dri/renderD128, which is all an Intel iGPU needs for Quick Sync and VA-API transcoding. Config and cache get their own volumes, so you can throw the cache away, and the media folders are mounted read-only. A health check on /health restarts it when it hangs, and Caddy serves it as https://jellyfin.example.com.
The quadlet files
Every quadlet file below passes podman's quadlet generator and
systemd-analyze verify on podman 5.0 and the current release. Personal values
are placeholders: replace example.com, /srv/... paths and the
time zone with your own.
jellyfin.container
Save as ~/.config/containers/systemd/jellyfin.container
[Unit]
Description=Jellyfin media server
StartLimitIntervalSec=0
After=network-online.target
[Container]
Image=docker.io/jellyfin/jellyfin:latest
ContainerName=jellyfin
AutoUpdate=registry
# Intel Quick Sync and VA-API hardware transcoding through the render node.
AddDevice=/dev/dri/renderD128
Environment=TZ=Etc/UTC
# All addresses: TV apps on the LAN connect directly, Caddy through the LAN address.
PublishPort=8096:8096
Volume=jellyfin.volume:/config:Z
Volume=jellyfin-cache.volume:/cache:Z
# Media read-only: Jellyfin never needs to write to the library.
Volume=/srv/media/movies:/media/movies:ro
Volume=/srv/media/tv:/media/tv:ro
HealthCmd=curl -fsS -o /dev/null http://127.0.0.1:8096/health
HealthInterval=1m
HealthTimeout=10s
HealthRetries=3
HealthStartPeriod=3m
HealthOnFailure=kill
[Service]
Restart=always
RestartSec=30
# The first start pulls a large image.
TimeoutStartSec=900
[Install]
WantedBy=default.target
jellyfin.volume
Save as ~/.config/containers/systemd/jellyfin.volume
[Unit]
Description=Jellyfin config: settings, users, metadata, library database
[Volume]
VolumeName=jellyfin
jellyfin-cache.volume
Save as ~/.config/containers/systemd/jellyfin-cache.volume
[Unit]
Description=Jellyfin cache: transcodes and image cache (safe to lose)
[Volume]
VolumeName=jellyfin-cache
Caddyfile
Save as /etc/caddy/Caddyfile
jellyfin.example.com {
reverse_proxy 10.0.0.10:8096
}
Why is it built this way?
What it is. Jellyfin is a free media server:
it indexes your films and series and streams them to browsers, phones, TVs and Kodi. It fits
next to the media stack pod, which fills the same
/srv/media folders.
Hardware transcoding, rootless. An Intel iGPU exposes a render node,
/dev/dri/renderD128. AddDevice= passes it into the container, and the
jellyfin-ffmpeg build inside the image brings its own Intel driver. There is no need for
--privileged or a rootful container: the container only needs what your user may
open on the host.
Two volumes on purpose. jellyfin.volume holds users, watch history
and metadata: back it up. jellyfin-cache.volume holds transcodes and thumbnails:
skip it in backups and delete it when it grows.
Caddy in front of Jellyfin
Jellyfin needs nothing special behind Caddy: WebSockets for the remote control and SyncPlay pass
through by default. Set the public URL in the apps to https://jellyfin.example.com.
Build the site block with the Caddyfile generator
or add it to the Caddy example.
Check that it works
systemctl --user status jellyfin.service
curl -s http://127.0.0.1:8096/health; echo
podman healthcheck run jellyfin && echo healthy
# the GPU inside the container: must list the iHD driver
podman exec jellyfin /usr/lib/jellyfin-ffmpeg/vainfo --display drm --device /dev/dri/renderD128
While a video transcodes, intel_gpu_top on the host shows the load on the video
engine. New to rootless services? Start with
rootless podman and
quadlet basics.
How do I start it?
Run these as your normal user, not as root. Quadlet reads ~/.config/containers/systemd/ for user services.
mkdir -p ~/.config/containers/systemd
# the render node must be usable by your user (openSUSE: mode 0666 by default)
ls -l /dev/dri/renderD128
systemctl --user daemon-reload
systemctl --user start jellyfin.service
# open http://<server>:8096, create the admin, add /media/movies and /media/tv
# then Dashboard, Playback, Transcoding: Intel QuickSync (QSV), device /dev/dri/renderD128
loginctl enable-linger "$USER"
Check the result with systemctl --user status and
the logs with journalctl --user -u followed by the
service name. To turn a docker-compose file of your own into files like these, use the
compose to quadlet converter.
Common mistakes
Playback fails with a transcoding error after turning on Quick Sync
Check that the container sees the GPU: podman exec jellyfin /usr/lib/jellyfin-ffmpeg/vainfo --display drm --device /dev/dri/renderD128 must list the iHD driver and profiles. If it says permission denied, the render node is not readable by your user.
Permission denied on /dev/dri/renderD128 in a rootless container
Rootless containers use your user's rights on the host. openSUSE gives render nodes mode 0666; on distributions that use 0660 with group render, add your user to that group and pass it on with GroupAdd=keep-groups (crun only, see man podman-run).
Jellyfin shows no files in a library
Point the library at the path inside the container, /media/movies, not the host path. Check that the host folder is readable by your user; the mount is read-only on purpose.
The disk fills up with transcodes
Transcodes and image cache live in jellyfin-cache.volume. It is safe to delete while Jellyfin is stopped: podman volume rm jellyfin-cache, then start jellyfin.service again.
Frequently asked questions
Quick Sync or VA-API: which one do I pick in Jellyfin?
On Intel iGPUs from 8th generation on, pick Intel QuickSync (QSV): it uses the same render node and is usually faster. VA-API works as a fallback. Enable only the codecs your GPU decodes, as listed by vainfo.
Why is Jellyfin published on all addresses and not only for Caddy?
TV and Kodi apps on the LAN often connect straight to port 8096. Caddy adds HTTPS for browsers and phones. If you only want HTTPS, publish on 127.0.0.1 and the LAN address instead and open no firewall port.
Does Jellyfin need host networking for DLNA?
Only for DLNA discovery, which uses multicast. Most apps connect by address, so this example keeps the normal network. Use Network=host if your TV only finds servers through DLNA.