Quick answer

Jellyfin’s setup hinges on the host first: /dev/dri/renderD128 for Intel Quick Sync or AMD VAAPI, or the NVIDIA Container Toolkit for NVENC. With that present, it is one linuxserver container on 8096, with PUID/PGID 1000, media bind mounts, and Nginx Proxy Manager with Websockets on. Choose media paths before starting; changing them means re-adding libraries.

Contents

By LK Wood IV · 2026-05-25 · Updated 2026-10-10 · ~12 min read · St. Louis County, MO

Jellyfin is a free, open-source media server. No subscription, no required cloud account, no phoning home. It runs on your own hardware, serves your own files, and the transcoding works whether you pay anyone money or not.

This guide covers a Jellyfin Docker install from start to finish. You get a Compose file, the right ports, hardware transcoding for Intel, NVIDIA, and AMD GPUs, HTTPS via Nginx Proxy Manager, and Tailscale remote access.

(Still choosing? Jellyfin vs Plex covers the common alternative. Come back here to build it.)

What you’ll have at the end

  • Jellyfin running in Docker on your homelab host
  • Hardware transcoding configured (Intel QSV, NVIDIA, or AMD VAAPI)
  • HTTPS access at jellyfin.yourdomain.com via Nginx Proxy Manager
  • Remote access via Tailscale without opening ports
  • Metadata scraping from TMDB and TheTVDB

Prerequisites

  • Docker installed on a Linux host (Proxmox LXC, VM, or bare-metal Debian/Ubuntu)
  • For NVIDIA transcoding: NVIDIA Container Toolkit installed
  • For Intel QSV: Intel 6th gen+ CPU (built-in GPU with Quick Sync)
  • Nginx Proxy Manager running for HTTPS (or skip and use LAN IP)

Step 1: Prepare media directories

Decide where your media lives before starting the container. The paths you choose here become the volume mounts in Docker Compose and can’t be easily changed later without restarting and re-adding libraries in the UI.

# Example structure on a NAS mount or local disk
mkdir -p /mnt/media/{movies,tv,music,photos}

# Or on local storage
mkdir -p /opt/media/{movies,tv}

Set ownership if running Jellyfin as non-root:

# Jellyfin container runs as UID/GID 1000 by default with linuxserver image
chown -R 1000:1000 /mnt/media

Step 2: Jellyfin Docker Compose file

A Jellyfin docker-compose.yml is short. It needs an image, port 8096, a /config volume and your media folders.

Jellyfin’s own example uses the official jellyfin/jellyfin image and adds a /cache volume. This guide uses LinuxServer’s image. That image takes PUID and PGID, and it sets /dev/dri permissions for hardware transcoding by itself.

Save the file below as docker-compose.yml. If you are not running Nginx Proxy Manager, delete its two networks: blocks first (see below). Then run docker compose up -d in the same folder.

mkdir -p /opt/stacks/jellyfin && cd /opt/stacks/jellyfin

Base compose file (CPU transcoding or Intel QSV):

# /opt/stacks/jellyfin/docker-compose.yml
services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Chicago
      - JELLYFIN_PublishedServerUrl=https://jellyfin.yourdomain.com  # optional
    volumes:
      - ./config:/config
      - /mnt/media/movies:/data/movies
      - /mnt/media/tv:/data/tv
      - /mnt/media/music:/data/music
      - /tmp/jellyfin-transcode:/transcode   # SSD path for transcode cache
    ports:
      - 8096:8096    # HTTP
      - 8920:8920    # HTTPS (optional, if not using NPM)
    networks:
      - proxy
    # Intel QSV — add this if you have an Intel CPU with Quick Sync:
    # devices:
    #   - /dev/dri:/dev/dri

networks:
  proxy:
    external: true

The two networks: blocks attach Jellyfin to an external network named proxy, the one Nginx Proxy Manager also joins in this setup. It has to exist before you start Jellyfin. No Nginx Proxy Manager? Delete both blocks. Otherwise docker compose up stops: Compose returns an error when an external network does not exist.

For Intel Quick Sync (QSV/VAAPI) — uncomment the device section above:

    devices:
      - /dev/dri:/dev/dri

No group_add is needed. LinuxServer says the image makes sure its user “has the proper permissions to access this device”.

Verify the /dev/dri device exists on the host:

ls /dev/dri
# renderD128  card0   (you should see renderD128 at minimum)

For NVIDIA GPU transcoding — use this compose instead:

services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Chicago
      - NVIDIA_VISIBLE_DEVICES=all
    volumes:
      - ./config:/config
      - /mnt/media/movies:/data/movies
      - /mnt/media/tv:/data/tv
      - /tmp/jellyfin-transcode:/transcode
    ports:
      - 8096:8096
    runtime: nvidia
    networks:
      - proxy

networks:
  proxy:
    external: true

All the variables sit in one environment: list. A second environment: key in the same service is invalid, because YAML requires each key in a mapping to be unique.

LinuxServer’s instructions set runtime: nvidia and NVIDIA_VISIBLE_DEVICES=all. The image sets the NVIDIA capabilities variable itself. The NVIDIA Container Toolkit must be installed on the host. See the GPU transcoding guide for the toolkit install commands.

Start Jellyfin:

docker compose up -d
docker compose logs -f jellyfin

First startup takes 60–90 seconds as Jellyfin initializes its database. Access at http://your-host-ip:8096.

The official image instead

Jellyfin’s own Compose example uses jellyfin/jellyfin. It runs as the user you set, so create the config and cache folders first and hand them to that user, or Docker creates them owned by root and Jellyfin cannot write to them:

cd /opt/stacks/jellyfin
mkdir -p config cache
chown 1000:1000 config cache

Trimmed to this guide’s paths, the file looks like this:

services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    user: 1000:1000
    ports:
      - 8096:8096/tcp
      - 7359:7359/udp
    volumes:
      - ./config:/config
      - ./cache:/cache
      - type: bind
        source: /mnt/media/movies
        target: /data/movies
      - type: bind
        source: /mnt/media/tv
        target: /data/tv
    restart: unless-stopped

user: is optional. Jellyfin says to leave it out to use the image’s default user.

An image with no tag pulls latest. Jellyfin’s latest follows every stable release, including major versions. Jellyfin also publishes version-pinned tags. Use one if you would rather choose when to upgrade.

The media targets match this guide’s /data/movies and /data/tv, so the setup wizard steps below still apply.

For Quick Sync, VAAPI or NVENC with this image, follow Jellyfin’s hardware acceleration docs. The LinuxServer permission handling above does not apply to it.

Jellyfin does not support running it in a container on Windows or macOS, where hardware transcoding is among the broken features.

Jellyfin Docker install with docker run

You can skip Compose. Jellyfin’s container install page, read on 2026-10-10, gives this command for the official image. Replace uid:gid and the paths with your own, or drop --user to use the image’s default user:

docker pull jellyfin/jellyfin
mkdir -p /path/to/config /path/to/cache

docker run -d \
  --name jellyfin \
  --user uid:gid \
  -p 8096:8096/tcp \
  -p 7359:7359/udp \
  --volume /path/to/config:/config \
  --volume /path/to/cache:/cache \
  --mount type=bind,source=/path/to/media,target=/media \
  --restart=unless-stopped \
  jellyfin/jellyfin

Three details from that page save time:

  • Jellyfin calls bind mounts easier than named volumes for a simple setup.
  • Add one --mount type=bind,source=...,target=... per media folder. Append ,readonly to a mount that Jellyfin should not write to.
  • The default Docker network mode is bridge. Host networking (--net=host) is optional, but Jellyfin says DLNA needs it.

The same page says Jellyfin gives no support for containers on platforms other than Linux. Hardware-accelerated transcoding is known to be broken there.

Jellyfin Docker ports: 8096, 8920 and 7359

I use 8096/TCP for the Jellyfin web interface and streaming connection. 8920/TCP is for Jellyfin’s own HTTPS listener, which is disabled by default; a reverse proxy can handle HTTPS separately. 7359/UDP serves local client discovery, not the web interface. Jellyfin documents each port here.

If local discovery is needed, add - 7359:7359/udp under the service’s existing ports: list. Discovery is local-subnet traffic, so publishing the port does not make it work across routed networks. A client can instead connect directly to http://your-host-ip:8096. I keep discovery off the public internet.

Jellyfin’s networking page says firewalls block incoming traffic by default and that opening a port straight to the internet is insecure and not recommended. It strongly recommends handling HTTPS on a reverse proxy. Access through a proxy only works correctly when Jellyfin has the right known proxies set and knows which address ranges count as local. Local ranges go in as comma-separated CIDR blocks.

Step 3: Initial setup wizard

The wizard walks you through:

  1. Language — pick your locale
  2. Create admin account — use a strong password
  3. Add media libraries — this is where your /data/movies, /data/tv mounts are used:
    • Add library → Content type: Movies → Add folder → /data/movies
    • Add library → Content type: Shows → Add folder → /data/tv
  4. Preferred metadata language — English (or your locale)
  5. Enable remote access — leave on for now, configure access control after

After the wizard, Jellyfin starts scraping metadata from TMDB/TheTVDB immediately. For a large library, this takes 10–30 minutes.

Step 4: Enable hardware transcoding

In Jellyfin → Dashboard → Playback → Transcoding:

Intel Quick Sync (most Intel CPUs 6th gen+):

  • Hardware acceleration: Intel QuickSync (QSV)
  • Enable hardware encoding: on
  • H.264, HEVC, VP9, AV1 encode/decode: enable all that your CPU supports

NVIDIA GPU:

  • Hardware acceleration: Nvidia NVENC
  • Enable hardware encoding: on
  • H.264, HEVC, AV1 encode/decode: enable based on your GPU generation (RTX 4000+ supports AV1)

AMD (VAAPI):

  • Hardware acceleration: Video Acceleration API (VAAPI)
  • VA API device: /dev/dri/renderD128
  • Enable hardware encoding: on

Check that the container sees the GPU first. For Intel, Jellyfin’s Intel hardware acceleration page (read 2026-10-10) says to run vainfo inside the container and read the codec list:

docker exec -it jellyfin /usr/lib/jellyfin-ffmpeg/vainfo

That path is for the official image, which ships the Intel media drivers. If the command fails, the device mapping is the first thing to check. The same page notes that QSV is not available on Windows Docker or WSL, and that on Linux it covers Broadwell (5th gen Core) and newer.

Codec support varies by Intel generation, so enable only what your CPU lists:

CodecIntel generation per Jellyfin’s docs
H.264 8-bitAny QSV-capable GPU
HEVC 8-bit encode and decodeGen 9 (Skylake, 6th gen Core) and newer
HEVC 10-bit encode and decodeGen 9.5 (Kaby Lake, 7th gen Core), Apollo Lake, Gemini Lake and newer
AV1 decodeGen 12 (Tiger Lake, 11th gen Core) and newer
AV1 encodeArc A-series and Meteor Lake and newer

Jellyfin’s hardware acceleration overview also says to confirm that Jellyfin uses its own ffmpeg build. Its version string carries a -Jellyfin suffix, for example /usr/lib/jellyfin-ffmpeg/ffmpeg. Binaries from other sources only get partial acceleration.

Verify transcoding is working:

Play a video and intentionally trigger a transcode by selecting a lower quality than the source (e.g., source is 4K HEVC, select “1080p 8Mbps” in the quality settings). Watch the Jellyfin dashboard → Active Sessions — you should see the stream marked as HEVC → H264 (hw) or similar, with GPU utilization on the host.

On a mini PC with Intel N-series, a single 4K → 1080p HEVC transcode should consume under 15% CPU with Quick Sync active (vs 80–100% without it).

Step 5: Nginx Proxy Manager HTTPS

In NPM → Add Proxy Host:

  • Domain: jellyfin.yourdomain.com
  • Scheme: http
  • Forward hostname: jellyfin (container name, on proxy network)
  • Port: 8096
  • Websockets Support: ON (required for Jellyfin’s sync features)
  • SSL: wildcard cert, Force SSL on, HTTP/2 support on

Access Jellyfin at https://jellyfin.yourdomain.com.

Optional — allow direct access for LAN clients:

Some Jellyfin clients (especially smart TV apps) work better with a direct LAN IP if the DNS resolves. Add 192.168.1.X to the Jellyfin “Known Proxies and Networks” list in Dashboard → Networking so Jellyfin knows to trust the X-Forwarded-For header from NPM.

Step 6: Remote access via Tailscale

The simplest secure remote access is Tailscale:

  1. Install Tailscale on your homelab host (or in a Proxmox LXC)
  2. Enable subnet routing to advertise your homelab LAN: tailscale up --advertise-routes=192.168.1.0/24
  3. On each device (phone, laptop, etc.) that you want Jellyfin access from, install the Tailscale app and connect
  4. Access Jellyfin via the local LAN IP or via the NPM domain if you’ve set AdGuard Home DNS rewrites

The Jellyfin iOS/Android apps work identically over Tailscale as they do on your local network. No special configuration needed in Jellyfin itself.

Tailscale is the right first step, not the whole answer — it has no client for Roku, webOS, or Tizen TVs, and family who won’t install a VPN need a different path. The Jellyfin remote access decision guide routes every situation: Tailscale, reverse proxy with a domain, VPS relay for CGNAT, and why Cloudflare Tunnel is a trap for media.

Update and back up the Jellyfin container

Back up before you update. Jellyfin’s backup page, read on 2026-10-10, puts the data in the folder mounted at /config, for both the official and the LinuxServer image. Jellyfin also says a backup is required to restore a previous version.

A cold backup of that folder:

  1. Stop the container with docker stop jellyfin. Jellyfin calls this extremely important, because otherwise the database is locked.
  2. Copy the host folder behind /config to a dated directory.
  3. Start the container again.

Jellyfin 10.11 and newer can also back up while it runs, from Dashboard, Backups, Create Backup.

To update, pull the new image and recreate the container from the folder with your compose file:

docker compose pull jellyfin
docker compose up -d

docker compose pull only downloads images. Docker’s up reference says that when a service’s image has changed, docker compose up stops and recreates the container. Your ./config folder stays where it was, so libraries and users carry over. A version-pinned tag stays put until you edit it.

Step 7: Jellyfin app clients

PlatformAppNotes
AndroidJellyfin (official)Play Store
iOS / iPadOSJellyfin (official)App Store
Apple TVJellyfin (official)tvOS App Store
Android TV / Fire TVJellyfin (official)Available on both stores
RokuJellyfin for RokuChannel Store
Web browserBuilt-in web clientAt your Jellyfin URL
Desktop (Windows/macOS/Linux)Jellyfin Media PlayerGitHub releases
KodiJellyfin for KodiKodi repository

On Apple TV and Android TV, the Jellyfin app supports Direct Play for most common formats (H.264/HEVC MP4/MKV containers). If the container or audio codec requires a transcode, hardware transcoding on the server handles it.

Library organization best practices

Name your files correctly. Jellyfin uses the TMDB naming convention:

/data/movies/The Dark Knight (2008)/The Dark Knight (2008).mkv
/data/tv/Breaking Bad/Season 01/Breaking Bad S01E01.mkv

The year in the movie folder name is required for disambiguation. TV shows need Season folders with SxxExx naming. Wrong naming is the #1 reason metadata fails to scrape.

Extras and specials:

/data/movies/The Dark Knight (2008)/
  The Dark Knight (2008).mkv
  extras/
    The Dark Knight Behind the Scenes.mkv

Jellyfin picks up extras from an extras/ subdirectory and shows them in the detail view.

Subtitles:

External subtitle files go in the same directory as the video with matching name:

The Dark Knight (2008).mkv
The Dark Knight (2008).en.srt        # English SRT
The Dark Knight (2008).en.forced.srt  # Forced English subtitles

Jellyfin surfaces these in the subtitle selection in the player.

Performance reference

Hardware transcoding throughput measured on Intel N100 (Quick Sync, 4K HEVC → 1080p H.264):

Concurrent streamsCPU loadTranscode FPS
1~8%60–90 fps
2~14%55–80 fps each
3~20%45–70 fps each
4~28%40–60 fps each

At 4 concurrent 4K → 1080p transcodes, playback remains smooth on a $160 N100 mini PC. Software transcoding (CPU only) would saturate all 4 N100 cores on a single 4K stream.

For the full GPU hardware comparison across Intel QSV, NVIDIA, and AMD, see the Best GPU for Jellyfin Transcoding guide.


For transcoding GPU picks and benchmarks across platforms, see the GPU transcoding comparison. If you’re deciding between Jellyfin and Plex, the Jellyfin vs Plex comparison breaks down hardware transcoding cost, privacy, remote access, and app quality by use case. If your library also includes audiobooks, the AudioBookshelf Docker setup covers the purpose-built server for them, with per-user progress sync across mobile apps.

Sources

Frequently asked questions

Should I use the official Jellyfin Docker image or LinuxServer’s?
Either works. Jellyfin publishes the official jellyfin/jellyfin image, Debian-based and built from Jellyfin’s source; LinuxServer.io and hotio publish unofficial Ubuntu-based images built from Jellyfin’s official packages. This guide uses LinuxServer’s image because it takes PUID and PGID and sets permissions on /dev/dri for hardware transcoding itself. The official image runs as root unless you set user: uid:gid.
Does Jellyfin need a GPU for transcoding?
No, but it helps. Jellyfin can software-transcode on CPU using ffmpeg. On a modern mini PC (N100/N150), software transcoding handles 1080p to 720p at 1080p → 720p at reasonable quality. The problem is 4K → 1080p: software transcoding an H.265 4K stream demands 6–10 CPU cores and typically fails to deliver smooth playback. Intel Quick Sync (built into almost every Intel CPU since 8th gen) handles 4K H.265 hardware transcoding at under 10% CPU with no additional hardware. For multiple simultaneous streams, hardware transcoding is the difference between a working media server and one that falls over.
Is Jellyfin actually better than Plex in 2026?
For homelab use, yes. Jellyfin requires no subscription for hardware transcoding, no account requirement for remote access, and no calls to Plex’s cloud infrastructure for playback. The apps on Apple TV, Roku, Android TV, Fire TV, iOS, and Android have reached practical parity with Plex’s basics. Plex still wins on polish in the mobile app and on ease of setup for non-technical family members. If you’re managing the server yourself and value control and no cloud dependency, Jellyfin is the better choice.
Can I run Jellyfin and the Arr stack (Radarr, Sonarr) together?
Yes. Jellyfin integrates with the Arr apps through shared volume mounts — the Arr stack downloads to /media/movies and /media/tv, and Jellyfin watches those same directories. No special integration plugin is needed; they communicate through the filesystem.
How much storage does Jellyfin itself use?
Jellyfin’s application data (metadata, thumbnails, trickplay images, transcoding temp files) lives in its config directory. A library with 500 movies and 20 TV series will generate 5–15GB of metadata and thumbnails. The main storage concern is your media — Jellyfin itself is lightweight. Transcode cache is ephemeral and should be pointed at a fast (SSD) path rather than your NAS share.
How do I access Jellyfin outside my home network?
Two main options: Tailscale (zero-config VPN, your devices connect to your homelab LAN as if they’re on it locally) or exposing Jellyfin directly via Nginx Proxy Manager with a valid SSL cert. Tailscale is simpler and more secure — no ports open to the internet. Direct HTTPS works but requires a public domain and makes Jellyfin reachable to anyone who finds the URL. For family members on your Tailscale network, the direct Jellyfin URL works without extra configuration. For sharing with people outside your Tailscale network, HTTPS is necessary.

Sources and corrections

Last updated
Methodology
See our methodology for research and review standards.
Update log
  • 2026-09-28 — Added a Jellyfin Docker install section with the official docker run command, a GPU check with vainfo, update and backup steps, and Jellyfin’s port and DLNA notes, all read from jellyfin.org and docker.com on 2026-10-10. Earlier: added a short answer for the Jellyfin Docker Compose file and Jellyfin’s own official-image example. Fixed the NVIDIA compose, which declared environment twice in one service; YAML requires each key in a mapping to be unique. Dropped group_add from the Intel lines: LinuxServer’s image sets /dev/dri permissions itself. Said what to delete when the external proxy network does not exist.
Corrections
Spotted an error or a stale number? Email contact@techfuelhq.com. Confirmed corrections are recorded here with the date and change.

About the author

Written by Lowell K. Wood IV, who builds and runs TechFuelHQ from St. Louis, Missouri.