← All Guides
beginner

Jellyfin Setup on Docker: The Complete Guide

Set up your own Jellyfin media server on Docker with hardware transcoding, organized media directories, and remote access via Tailscale.

Budget Homelab ·
dockermediastreaming

Jellyfin is a fully open-source media server — no licenses, no premium tiers, no data going anywhere you didn’t put it. You own the server, you own the data. It plays movies, TV, music, photos, and books. The mobile and TV apps are free. The web interface works fine in any browser.

This guide gets you from zero to a working Jellyfin install on Docker, with hardware transcoding configured and remote access set up via Tailscale. I’m assuming you already have Docker and Docker Compose installed on your server. If not, start with the Docker Compose basics guide. If you’re still deciding how to run Jellyfin — dedicated VM, LXC, or bare metal — the Proxmox VM vs LXC guide covers that decision.

Directory Structure

Set up your directories before writing any config. Jellyfin expects to find your media at specific paths, and getting this right at the start saves you from having to reconfigure libraries later. If you’re still figuring out where to actually store your media files, see Homelab Storage Options before going further.

mkdir -p /opt/jellyfin/{config,cache}
mkdir -p /data/media/{movies,tvshows,music}

What these are:

Adjust the media paths to wherever your actual files live. If your media is on a separate drive, mount it there and update the paths in the compose file accordingly.

Docker Compose Config

Create /opt/jellyfin/docker-compose.yml:

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    user: "1000:1000"
    network_mode: host
    volumes:
      - /opt/jellyfin/config:/config
      - /opt/jellyfin/cache:/cache
      - /data/media:/media:ro
    environment:
      - JELLYFIN_PublishedServerUrl=http://YOUR_SERVER_IP:8096
    restart: unless-stopped

A few notes on the config choices:

network_mode: host — Jellyfin uses service discovery features (DLNA, local network detection) that work better with host networking. This means Jellyfin uses your server’s IP directly rather than a Docker bridge network.

user: "1000:1000" — Runs Jellyfin as your regular user rather than root. Replace 1000:1000 with your actual user and group IDs (run id in the terminal to check). Your media directory needs to be readable by this user.

/media:ro — The :ro flag mounts your media as read-only. Jellyfin doesn’t need to write to your media directories — keeping it read-only is a good habit.

JELLYFIN_PublishedServerUrl — Replace YOUR_SERVER_IP with the actual IP of your server on your local network. This tells Jellyfin what address clients should use to reach it.

Hardware Transcoding

Hardware transcoding lets Jellyfin offload video encoding work to your GPU rather than using the CPU. This matters most when clients request formats that need to be transcoded — different codec, lower bitrate, different resolution. Without hardware transcoding, a single 4K transcode can pin your CPU. With it, the same transcode barely registers.

Intel Quick Sync (N-series, 12th/13th gen+)

If you’re running an Intel mini PC with integrated graphics, you likely have Quick Sync available. Check if the device exists:

ls /dev/dri/

You should see renderD128 (and possibly others). If it’s there, add this to your compose file under the jellyfin service:

    devices:
      - /dev/dri/renderD128:/dev/dri/renderD128
    group_add:
      - "render"
      - "video"

Then in Jellyfin’s admin panel: Dashboard > Playback > Transcoding, set Hardware acceleration to Intel QuickSync (QSV) and enable the codecs you want offloaded (H.264, HEVC, AV1 if supported).

AMD (Radeon integrated graphics)

Same device path approach — AMD also uses /dev/dri/renderD128. Add the same devices and group_add blocks. In Jellyfin’s transcoding settings, select Video Acceleration API (VAAPI).

The Radeon 680M and 780M in recent AMD mini PCs handle 4K transcoding well. If you bought the Beelink EQR6 or Minisforum UM790 Pro specifically for transcoding, this is where that investment pays off.

NVIDIA (dedicated GPU)

If you have an NVIDIA GPU, you need the NVIDIA Container Toolkit installed on the host first. Then:

    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]

In Jellyfin’s transcoding settings, select NVIDIA NVENC.

Verify It’s Working

After setup, start a stream that requires transcoding — a 4K file on a client that doesn’t support the native codec, or force transcoding via the Jellyfin web player’s quality settings. Watch the activity dashboard in Jellyfin admin. A hardware-accelerated transcode shows a small GPU utilization bump on the server with CPU staying low. A software transcode shows significant CPU utilization.

Start the Container

cd /opt/jellyfin
docker compose up -d

Access the web interface at http://YOUR_SERVER_IP:8096. The initial setup wizard walks you through:

  1. Creating an admin account
  2. Adding media libraries (point each one at the relevant subdirectory under /media)
  3. Setting up metadata providers

Library scan runs automatically after setup. Depending on how much media you have, the initial scan and metadata download takes anywhere from a few minutes to a couple of hours.

Remote Access via Tailscale

Tailscale gives you secure remote access to Jellyfin without opening any ports on your router. If you don’t have Tailscale set up on your server yet, the Tailscale setup guide covers it.

Once Tailscale is running:

  1. Note your server’s Tailscale IP (something like 100.x.x.x). Find it by running tailscale ip on the server.
  2. On any device on your tailnet, Jellyfin is reachable at http://100.x.x.x:8096.
  3. Add your Tailscale IP as a known server in the Jellyfin mobile app.

For the best experience on mobile, update JELLYFIN_PublishedServerUrl in your compose file to use your Tailscale IP instead of your local LAN IP. This ensures the app always gets a reachable address regardless of which network you’re on.

There’s no need to set up SSL certificates for local + Tailscale use. Tailscale traffic is encrypted end-to-end. The only reason to add HTTPS termination is if you plan to expose Jellyfin to the public internet, which I’d recommend against — Tailscale is simpler and more secure.

Media Organization

Jellyfin uses filenames to identify media and match it against metadata providers (The Movie Database, TheTVDB). The more closely your filenames match the expected format, the better the matching works.

Movies:

/media/movies/The Matrix (1999)/The Matrix (1999).mkv
/media/movies/Inception (2010)/Inception (2010).mkv

TV Shows:

/media/tvshows/Breaking Bad/Season 01/Breaking Bad S01E01.mkv
/media/tvshows/Breaking Bad/Season 01/Breaking Bad S01E02.mkv

The year in parentheses helps Jellyfin distinguish between films with the same name. The S01E01 format is the most reliably detected episode naming scheme.

If you have a large existing library in a different format, don’t rename everything at once. Add a small test folder first, verify Jellyfin identifies it correctly, then move your actual files in batches.

Backups

Back up /opt/jellyfin/config. That directory contains your user accounts, library configuration, watched status, and metadata cache. If you lose it, you’re rebuilding from scratch.

The media files themselves don’t need to be backed up from Jellyfin’s perspective — they’re your source files and presumably already backed up separately (or they should be).

A simple approach: add a cron job that tarballs the config directory daily and copies it off the server. Something like:

0 3 * * * tar -czf /backup/jellyfin-config-$(date +\%Y\%m\%d).tar.gz /opt/jellyfin/config

Keep a week of daily backups. Jellyfin configs are small — a week of backups might be a few hundred megabytes at most.

Updating Jellyfin

cd /opt/jellyfin
docker compose pull
docker compose up -d

Jellyfin releases pretty frequently. I update when I notice there’s a new release with something I care about, not on a strict schedule. The latest tag makes updates trivial. For automated container updates, Watchtower can handle this for you.


If you’re deciding between Jellyfin and Plex, the Plex vs Jellyfin article covers the tradeoffs honestly. For monitoring server health during transcoding-heavy usage, Grafana + Prometheus gives you the CPU/GPU dashboards worth having. A Homarr dashboard can show your active Jellyfin streams at a glance.

See the full homelab stack at /stack/.