# 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 ```mermaid 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) ```yaml 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 ```caddy 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=` 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