Files
serverAppConfigs/serverAppConfigs-docs/Dankserver/Jellyfin.md
T

9.6 KiB
Raw Blame History

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: per-user libraries, parental controls, max bitrate limits. The local dank admin account is built-in; its password is Frank's bootstrap value — never recorded in this repo. (2026-09-01: a corrupted session-archive reading briefly suggested the password had changed; verified offline against the stored PBKDF2-SHA512 hash, the bootstrap value still authenticates.)
  • LDAP via Authentik (live 2026-09-01): the LDAP-Auth plugin authenticates users against the authentik LDAP outpost on Dankclaw — see Authentik for the outpost unit, provider, and RBAC objects.

LDAP-Auth plugin (v23.0.0.0, live 2026-09-01)

  • Install from the GitHub releases zip — repo.jellyfin.org's stable manifest path 404s here, so fetch jellyfin/jellyfin-plugin-ldapauth release asset ldap-authentication_23.0.0.0.zip from GitHub and unzip into config/plugins/LDAP-Auth_23.0.0.0/. The config lives at config/plugins/configurations/LDAP-Auth.xml — write it directly, then restart the service.
  • Live config: server 192.168.0.143:389, base DN dc=franksdank,dc=cloud, bind DN cn=svc-jellyfin-ldap,ou=users,dc=franksdank,dc=cloud, bind password = the authentik outpost token (secret — the XML lives on the host only, never in the repo), search filter (memberOf=cn=jellyfin_users,ou=groups,dc=franksdank,dc=cloud), admin filter on cn=jellyfin_admins,ou=groups,…, CreateUsersFromLdap=true (auto-create on first login).
  • Attribute separator is a comma. LdapSearchAttributes is Split(',') in the plugin source — a semicolon-separated list (uid;cn;mail;displayName) parses as ONE bogus attribute, the search silently matches nothing, and the log says Found no users matching X. Use uid,cn,mail,displayName.
  • Username attribute is cn. Authentik 2026.x maps uid to an opaque per-user hash, not the username, so uid=<name> never matches. Set LdapUsernameAttribute=cn (authentik exposes the username as cn).
  • Verify with a throwaway user: create a test member of jellyfin_users in authentik, then POST /Users/AuthenticateByName with headers Content-Type: application/json and X-Emby-Authorization: MediaBrowser Client="…", Device="…", DeviceId="…", Version="10.11.0" — both are required in 10.11 (missing X-Emby-Authorization → 400). 200 + non-admin policy = LDAP path works. Delete the test user from authentik and Jellyfin afterward.
  • Plugin load lines only show in journalctl --user -u jellyfin.service (Loaded plugin: LDAP-Auth 23.0.0.0) — podman logs does not carry the loader lines in this image.

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

  1. Verify GPU on host: ls -l /dev/dri/, lspci | grep -i vga, getent group render.
  2. Install media drivers: sudo apt install intel-media-va-driver-non-free vainfo intel-gpu-tools.
  3. Create directories: /home/dank/Containers/jellyfin/config, /home/dank/Containers/jellyfin/cache.
  4. Write compose file, verify device and GID.
  5. Deploy: podman compose up -d.
  6. Test GPU passthrough: podman exec jellyfin /usr/lib/jellyfin-ffmpeg/vainfo --display drm --device /dev/dri/renderD128.
  7. Configure Caddy reverse proxy. Set Known Proxies in Jellyfin.
  8. Add media libraries in Dashboard.
  9. Install plugins: Open Subtitles, fanart.tv, Intro Skipper.
  10. Test playback on Android/Chrome.

Gotchas

  • GPU passthrough silently failing: /dev/dri must be passed and the correct render node used. Verify with vainfo inside the container.
  • Wrong render group GID: Common GID is 992, but varies per host. Run getent group render to 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 /config on 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 and Show Name/Season 01/Show Name S01E01.mkv for 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