Skip to main content

Jellyfin - Media Streaming

Jellyfin is a media streaming hub. It allows you to easily stream audio and video files to a web browser.

Access

Our Jellyfin instance is reverse-proxied by the Secure Web Application Gateway. It is published on swag.kasad.com/jellyfin. The endpoint is protected by Cloudflare Access policies requiring authentication/authorization.

Deployment

Jellyfin runs as a single Docker container using the lscr.io/linuxserver/jellyfin:latest image. It is one of the LinuxServer.io images with available mods.

We run Jellyfin using a Docker Compose file for easy configuration:

version: "3"

services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    ports:
      - 7359:7359/udp
      - 1900:1900/udp
    volumes:
      - /media:/media
      - jellyfin_config:/config
    tmpfs:
      - /config/transcodes
      - /config/cache
      - /config/data/transcodes
    devices:
      # VA-API devices
      - /dev/dri/card0:/dev/dri/card0
      - /dev/dri/renderD128:/dev/dri/renderD128
    restart: unless-stopped
    environment:
      - PUID=938 # swag
      - PGID=941 # servlets
      - UMASK=002
      - TZ=America/Los_Angeles
      - JELLYFIN_PublishedServerUrl=https://swag.kasad.com/jellyfin
      - DOCKER_MODS=ghcr.io/kdkasad/docker-mods/jellyfin-jellyscrub
    networks:
      - default
      - swag
      
volumes:
  jellyfin_config: {}
  
networks:
  swag:
    name: swag_default
    external: true

SWAG network

The Send container is reverse-proxied behind the Secure Web Application Gateway, so the SWAG container needs network access to the Send container. This has been done in the Compose stack above. See this explanation for details.

Persistent data storage

The lscr.io/linuxserver/jellyfin image stores all of its data in /config by default. To make this persistent, we mount a volume on /config:

    volumes:
      - jellyfin_config:/config

Since this is a named volume, we also need to declare it at the end of the Compose file:

volumes:
  jellyfin_config: {}

Media volume

Jellyfin needs to be able to access the media it is going to stream. The media volume can be mounted anywhere within Jellyfin as long as it is configured accordingly in the UI. We mount the media volume on /media to keep things simple.

Jellyfin needs at least read access to all media. If metadata is being stored alongside media, Jellyfin needs write access as well.

We run the Jellyfin container with a umask of 002. This means newly created files will have user/group read-write permissions and global read permissions. We also set the group ownership of /media to the servlets group. We set the SetGID flag to ensure new files/directories inherit the parent's group ownership.

In-memory caches

Some of Jellyfin's data does not need to be persistent. In order to improve performance and reduce unnecessary writes to disk, we mount temporary (in-memory) filesystems on:

  • /config/data/transcodes - Where Jellyfin writes temporary transcoded video files
  • /config/cache - Where short-lived cache data is stored

If the system running the Jellyfin container does not have sufficient RAM, this will likely cause Jellyfin to fail (or at least cause heavy swapping) while transcoding large videos. The transcode directory should be able to fit the media being streamed, so it should be at least a few gigabytes.

VA-API devices

The host system (my laptop) that runs the Jellyfin container has an Intel iGPU which supports VA-API for hardware video decoding. To utilize this in Jellyfin, the VA-API device nodes must be accessible inside the container. This is done in the devices section of the Compose service:

    devices:
      - /dev/dri/card0:/dev/dri/card0
      - /dev/dri/renderD128:/dev/dri/renderD128

It should also be possible to just mount the /dev/dri directory as a volume, but I have not tried that.

One the devices are mounted in the container, follow the Hardware-accelerated video decoding configuration instructions.

DLNA/UPnP

Jellyfin comes built with DLNA and UPnP support for streaming to wireless displays like TVs. To enable this, we forward the following ports for the container.

    ports:
      - 7359:7359/udp
      - 1900:1900/udp

Then select Enable 'Play To' DLNA feature in the DLNA settings tab.

Despite the port forwards, it does not seem to work as well as when the Jellyfin container is run in host network mode.

Configuration

Most of Jellyfin's configuration is done from its built-in settings menu. The only options that are not configured from there are the PID, UID, umask, and the published server URL. These are all defined in the environment section of the jellyfin service in the Compose file.

File paths

Our Jellyfin instance is configured to use the following file paths. Some of these paths are default and some are manually configured in the Settings UI. However all of them are important as they have a specific filesystem mounted on them.

Path Filesystem Type Purpose Where to configure
/config Named volume Main data directory just don't change it
/config/data/transcodes tmpfs(5) Contains transcoded videos while they're being streamed Playback tab
/config/cache tmpfs(5) Temporary cached data General tab
/media Host volume (/media) Media library Each library in the Libraries tab

All directories with volume mounts are explained in the Deployment section. This table is just meant to list them all in one place.

Storing metadata alongside media

I prefer having metadata on the same volume as the media, as most of the data is related. To accomplish this, set General > Metadata Path to /media/metadata. Make sure the directory you choose exists.

Better theme

Jellyfin supports adding custom CSS to style the web interface. We load the Ultrachromic theme, which is an overhaul of the default Jellyfin UI.

General > Custom CSS Code:

@import url('https://cdn.jsdelivr.net/gh/CTalvio/Ultrachromic/presets/monochromic_preset.css');

Hardware-accelerated video decoding

Jellyfin has many settings for hardware-accelerated video decoding (a.k.a. HWDec) in the Playback tab of the Settings UI. Many APIs for this are supported, but I've only used VA-API, so that's the only one documented here.

VA-API (Intel GPUs)

If VA-API devices have been mounted, set the following options:

Option Value Description
Hardware acceleration Video Acceleration API (VAAPI) Enable use of VA-API for HWDec
Enable hardware decoding for all codecs selected Enable hardware decoding for all codecs*
Enable hardware encoding enabled Enable hardware-accelerated video encoding*
Enable Intel Low-Power H.264/HEVC hardware encoder enabled Enable low-power encoders*
Enable encoding in HEVC format enabled Enable HEVC (H.265) encoder*

*Make sure your GPU supports the codecs you've enabled. Use the following command to check which codecs your GPU supports:

$ vainfo | sed 's/VAProfile//; s/VAEntrypointVLD/decode/; s/VAEntrypointEncSlice\(LP\)\?/encode \1/'

encode LP means low-power encoding is supported.

Single sign-on

We want to allow users to log in through the Authentik SSO portal. To do this, we must configure Jellyfin and Authentik properly.

Confguring SSO in Jellyfin

To enable SSO in Jellyfin, you must install the jellyfin-sso plugin. To do this,

  1. Navigate to the Repositories page within the Plugins tab in Jellyfin.
  2. Add a new repository with the URL https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json. Name it whatever you want.
  3. Go to the Catalog page and install the SSO Authentication plugin.
  4. Once it's installed, go to the My Plugins page and click the SSO-Auth plugin. Then enter the settings from the table below.
Setting Value Description
Name of OID Provider Authentik Name of the SSO provider (arbitrary)
OID Endpoint https://auth2.kasad.com URL of the OpenID Connect provider
OpenID Client ID (redacted) OIDC Client ID for Jellyfin
OID Secret (redacted) OIDC Client secret for Jellyfin
Enabled Whether this SSO provider is enabled
Enable Authorization by Plugin Let the SSO plugin assign permissions to new users
Enable all folders Don't allow all users to access all libraries
Enabled folders Music and YouTube videos Allow all users to access these libraries
Roles empty Don't check for a specific group to authenticate with Jellyfin
Admin Roles Administrators Users in the Administrators group are administrators
Enable role-based folder access Allow access to libraries based on user's groups
Folder Role Mapping Role: Pirates. All libraries selected Allow users in the Pirates group to access all media libraries
Role Claim groups The OpenID claim to get user's roles from
Request additional scopes empty Authentik exports all required information in the defaults scopes
Set default provider Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider Setting to this value prevents users from having to authenticate twice

Configuring SSO in Authentik

We also need to configure Authentik to allow Jellyfin to authenticate against it. To do this, first create a new OpenID Connect provider in Authentik's admin interface.

Set the following settings (note that some may be under the Advanced protocol settings dropdown):

Parameter Value Description
Redirect URI https://swag.kasad.com/jellyfin/sso/OID/r/Authentik Sets the URL that Authentik will redirect to after succesful authentication.
Signing Key authentik Self-signed Certificate (RSA) Use the default Authentik key for signing tokens
Issuer mode Each provider has a different issuer, based on the application slug Use the same issuer URL as the OID configuration endpoint

The last path component of the redirect URL must match the Name of OID Provider option configured in Jellyfin.

Jellyscrub (smooth video scrubbing previews)

By default, Jellyfin can only show the chapter thumbnail when hovering over the video scrub bar. The Jellyscrub plugin provides smooth scrub previews.

To install it,

  1. Navigate to the Repositories page within the Plugins tab in Jellyfin.
  2. Add a new repository with the URL https://raw.githubusercontent.com/nicknsy/jellyscrub/main/manifest.json. Name it whatever you want.
  3. Go to the Catalog page and install the Jellyscrub plugin.

The plugin also requires a modification to Jellyfin's defualt index.html file. In order for this modification to be made automatically (and persist across container restarts), I've created a Docker mod to handle this. To enable it, add ghcr.io/kdkasad/docker-mods:jellyfin-jellyscrub to the DOCKER_MODS environment variable for the jellyfin container.

Using Jellyfin

The following sections contain information pertaining to using Jellyfin once it's set up.

Logging in with SSO

There's one problem with SSO support in Jellyfin. Since it's provided via a plugin and not by Jellyfin itself, SSO isn't enabled on the login page. To log in with SSO, a user must visit this URL: https://swag.kasad.com/jellyfin/sso/OID/p/Authentik.

Again, the last path component of the redirect URL must match the Name of OID Provider option configured in Jellyfin.