Phase 1/2 of the podman standardization plan: service pages updated to the quadlet reality (units, Containers data roots, env-file secret convention, the v1-generator findings each caught live), Storage Policy documents the two rootless ownership regimes and the merge-move migration, Deployment Flow's service-manager table no longer claims dankserver media is bare inventory-only. Bundle change log entry added.
7.4 KiB
Jellyfin
Jellyfin is a free, open-source media server and suite of applications for organizing, managing, and streaming personal media collections (movies, TV shows, music, photos) to networked devices. It is a community fork of Emby, written in C# (.NET), and uses FFmpeg for transcoding.
Host: Dankserver — Hostname: jellyfin.dankserver.local — Docs: https://jellyfin.org
Facts
| Item | Value |
|---|---|
| Latest version | Check https://jellyfin.org/downloads |
| Image | jellyfin/jellyfin:latest (Docker Hub) or ghcr.io/jellyfin/jellyfin |
| Base | Debian-based |
| Primary port | 8096 (HTTP, web UI + API) |
| HTTPS port | 8920 (optional) |
| Client discovery | 7359/udp |
| DLNA | 1900/udp (optional) |
| GPU passthrough | /dev/dri (Intel QSV / AMD VA-API) or NVIDIA NVENC |
| License | MIT |
Architecture
graph LR
U[User] --> C[Caddy :80/:443]
C --> J[Jellyfin :8096]
J --> Lib[/media/movies]
J --> Lib2[/media/shows]
J --> Lib3[/media/music]
J --> Conf[Jellyfin config]
J --> Cache[Jellyfin cache]
Docker Compose (reference — retired 2026-09-01; live mounts use /home/dank/Containers/jellyfin, see Deployment below)
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
user: "1000:1000"
ports:
- "8096:8096/tcp"
- "8920:8920/tcp"
- "7359:7359/udp"
- "1900:1900/udp"
environment:
- JELLYFIN_PublishedServerUrl=http://192.168.0.235:8096
- DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true
volumes:
- /opt/jellyfin/config:/config
- /opt/jellyfin/cache:/cache
- /mnt/media/movies:/data/movies:ro
- /mnt/media/shows:/data/shows:ro
devices:
- /dev/dri:/dev/dri
group_add:
- "992" # Host render group GID — verify with `getent group render`
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
interval: 30s
timeout: 10s
retries: 3
Caddy Configuration
jellyfin.dankserver.local {
reverse_proxy 127.0.0.1:8096
transport http {
versions h2c 1.1
}
}
Set Base URL to / (or the path you configure) in Jellyfin Dashboard → Networking. Add 127.0.0.1 to Known Proxies.
Storage
| Mount | Purpose | Size Estimate |
|---|---|---|
/home/dank/Containers/jellyfin/config |
SQLite database, settings, cached images | 10–100 GB |
/home/dank/Containers/jellyfin/cache |
Transcoding segments, extracted posters | Self-cleaning per session |
/mnt/entropy/Movies, /mnt/entropy/TVShows |
Media, read-only binds (:ro) — no automated write path |
— |
The database must be on local storage (SSD/NVMe preferred). Media libraries can be on NFS/mergerfs. If media is on NFS, set DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true.
Deployment (Quadlet)
Since 2026-09-01: dankserver/jellyfin/quadlet/jellyfin.container →
jellyfin.service (generator-enabled), deployed with
./scripts/deploy.sh dankserver --service jellyfin. GPU passthrough uses
AddDevice=/dev/dri — plain Device= is not a quadlet key and makes the
v1 generator reject the whole unit (learned live: "unsupported key 'Device'
in group 'Container'"). Entropy mounts stay :ro; the container runs as
root inside the user namespace, so the render-node ACLs apply via the device
cgroup regardless of GroupAdd=keep-groups.
Authentication
- Built-in auth (default): Per-user libraries, parental controls, max bitrate limits. Manage via Dashboard → Users.
- Caddy basic auth: Single login layer. Does not integrate with per-user Jellyfin permissions.
- LDAP/OIDC: Available via plugins (LDAP-Auth, Jellyfin SSO). Integrates with Authentik, Keycloak, etc.
For Dankserver, built-in auth is recommended. Use Caddy basic auth on top for external access.
Metadata Scraping
| Provider | Content | Type |
|---|---|---|
| TheMovieDb (TMDb) | Movies, TV shows, posters, fan art, overviews | Default |
| OMDb API | Movies only, English metadata | Default |
| TheTVDB | TV shows, episodes | Plugin |
| fanart.tv | Extra artwork, banners, thumbnails | Plugin |
| AniDB | Anime metadata | Plugin |
| MusicBrainz | Music metadata | Plugin |
| Open Subtitles | Subtitle downloads | Plugin |
| Intro Skipper | TV show intro detection | Plugin |
Clients
| Platform | App | Quality |
|---|---|---|
| Web | Jellyfin Web | Excellent |
| Android | Jellyfin (Google Play / F-Droid) | Excellent |
| Android TV / Fire TV | Jellyfin | Good |
| iOS / iPadOS | Swiftfin (third-party) | Good |
| Desktop | Jellyfin Media Player (mpv-based) | Excellent |
| Roku | Jellyfin (Channel Store) | Good |
| Kodi | Jellyfin addon | Excellent |
| Samsung/LG Smart TVs | TV browser | Fair |
Jellyfin vs Plex
| Feature | Jellyfin | Plex |
|---|---|---|
| Cost | Free, no tiers | Free core; Plex Pass ($5/mo or $120 lifetime) |
| License | MIT | Proprietary |
| Account required | No | Yes |
| Hardware transcoding | Free | Plex Pass required |
| Mobile sync | Free | Plex Pass |
| Live TV / DVR | Free | Plex Pass |
| Client apps | Good, improving | Excellent, widest device support |
Run Jellyfin alongside Plex for an incremental migration. Point Jellyfin at the same media folders as Plex. Watch history and metadata will not transfer cleanly.
Resource Requirements
- Idle / Direct Play: ~200–500 MB RAM, <5% CPU. Negligible.
- 1080p transcode (CPU): 40–60% CPU on a modern multi-core CPU.
- 1080p transcode (with QSV): 3–5% CPU.
- 4K HDR transcode (with QSV): 5–15% CPU.
- Minimum host RAM: 4 GB. 8 GB comfortable for transcoding.
Deployment Checklist
- Verify GPU on host:
ls -l /dev/dri/,lspci | grep -i vga,getent group render. - Install media drivers:
sudo apt install intel-media-va-driver-non-free vainfo intel-gpu-tools. - Create directories:
/home/dank/Containers/jellyfin/config,/home/dank/Containers/jellyfin/cache. - Write compose file, verify device and GID.
- Deploy:
podman compose up -d. - Test GPU passthrough:
podman exec jellyfin /usr/lib/jellyfin-ffmpeg/vainfo --display drm --device /dev/dri/renderD128. - Configure Caddy reverse proxy. Set Known Proxies in Jellyfin.
- Add media libraries in Dashboard.
- Install plugins: Open Subtitles, fanart.tv, Intro Skipper.
- Test playback on Android/Chrome.
Gotchas
- GPU passthrough silently failing:
/dev/drimust be passed and the correct render node used. Verify withvainfoinside the container. - Wrong render group GID: Common GID is 992, but varies per host. Run
getent group renderto find it. - Subtitle burn-in triggers full transcode: PGS (image-based) subtitles force full video transcoding. SRT/ASS (text-based) play fine.
- Database on NFS: SQLite on network storage causes slow startup and corruption risk. Keep
/configon local storage. - Client bitrate limit too low: Many clients default to 20 Mbps. Set Max Streaming Bitrate to Original or Maximum.
- Naming convention mismatches: Non-standard folder/file names cause missing metadata. Use
Movie Title (Year)for movies andShow Name/Season 01/Show Name S01E01.mkvfor TV.
See Also
- Dankserver Stack
- ARR-Stack — automated download into Jellyfin libraries
- Copyparty — alternative file viewer for existing collections
- Plex — existing media server on this host