Jellyfin Media Server - Docker Deployment and Remote Access

Status: Active
Last Updated: 2026-08-26
Category: Cloud - Media Services
Prerequisites: docker-compose-intro, traefik-v3-reverse-proxy
Time: 2-4 hours
Tags: jellyfin, media, docker, transcoding, vaapi, reverse-proxy

Summary

Deploying Jellyfin โ€” the open-source media server โ€” on fogserv.cloud with Docker Compose: library organization, hardware-accelerated transcoding via VAAPI, user management, and secure remote access through the Traefik reverse proxy. Includes guidance on which data to back up (config, not media) and how playback decisions drive server load.

๐ŸŽฏ What You'll Learn

By the end of this article, you'll be able to:


Table of Contents

  1. Why Jellyfin
  2. Docker Compose Deployment
  3. Libraries & File Layout
  4. Transcoding & Hardware Acceleration
  5. Remote Access via Reverse Proxy
  6. Users & Backups

Context / Why This Matters

Jellyfin is the media component of the homelab stack: it serves the same media library that other services reference, with no licensing or account requirements. Unlike Nextcloud or Immich, its data model is asymmetric โ€” a few MB of irreplaceable config/database against terabytes of re-downloadable media. That asymmetry drives every operational decision here: aggressive exclusion of media from backups (storage-backup-strategies), but rigorous backup of the config volume.

Implementation / Core Content

Why Jellyfin

Compared to Plex/Emby, Jellyfin is fully open source, needs no external account or "pass", and works offline. The trade-off is that you own more of the configuration โ€” which is exactly what this article covers.

Docker Compose Deployment

# /opt/jellyfin/docker-compose.yml
services:
  jellyfin:
    image: jellyfin/jellyfin:10.10
    container_name: jellyfin
    hostname: fogserver-jellyfin
    restart: unless-stopped
    environment:
      - TZ=Europe/Berlin
      - JELLYFIN_PublishedServerUrl=https://jellyfin.fogserv.cloud
    devices:
      - /dev/dri:/dev/dri          # GPU for VAAPI/QSV transcoding (Intel/AMD iGPU)
    volumes:
      - ./config:/config           # settings, DB, metadata  โ† BACK THIS UP
      - ./cache:/cache             # transcoding scratch, thumbnails cache
      - type: bind
        source: /srv/media
        target: /media
    ports:
      - "8096:8096"                # HTTP UI/API (LAN); TLS terminates at proxy
cd /opt/jellyfin && docker compose up -d
docker compose logs -f        # wait for "Startup complete"

First-run wizard at http://server.lan:8096: create admin user, set language/metadata locales, add libraries.

Pin a specific minor version (10.10, not latest) and bump deliberately after backing up ./config โ€” Jellyfin migrates its database on version changes.

Libraries & File Layout

Scraping succeeds or fails based on directory naming before any settings matter:

/srv/media/
โ”œโ”€โ”€ movies/
โ”‚   โ””โ”€โ”€ Blade Runner 2049 (2017)/
โ”‚       โ”œโ”€โ”€ Blade Runner 2049 (2017).mkv
โ”‚       โ””โ”€โ”€ poster.jpg              # optional local artwork
โ”œโ”€โ”€ shows/
โ”‚   โ””โ”€โ”€ Breaking Bad (2008)/
โ”‚       โ”œโ”€โ”€ Season 01/
โ”‚       โ”‚   โ”œโ”€โ”€ Breaking Bad S01E01.mkv
โ”‚       โ”‚   โ””โ”€โ”€ Breaking Bad S01E02.mkv
โ””โ”€โ”€ music/
    โ””โ”€โ”€ Artist/Album/01 - Track.flac

Rules that prevent 90% of metadata pain:

Library options worth setting: enable Real Time Monitoring (inotify) on movie/show libraries so new files appear without manual scans; disable chapter image extraction on huge libraries (slow initial scan).

Transcoding & Hardware Acceleration

Direct play (client supports the file natively) costs almost nothing. Transcoding burns CPU/GPU. The goal: maximize direct play, offload unavoidable transcodes to hardware.

Enable in Dashboard โ†’ Playback โ†’ Transcoding: Hardware acceleration = VAAPI (Intel/AMD) or NVIDIA NVENC, select /dev/dri/renderD128, tick H.264/HEVC decode+encode formats.

Verify hardware transcoding actually engages:

  1. Start playback of a high-bitrate HEVC file in a browser (forces transcode)
  2. Watch Dashboard โ†’ Activity โ†’ Active tasks โ€” task should show "(transcoding)"
  3. On the host during transcode: intel_gpu_top (or radeontop) should show Video engine activity while CPU stays low
  4. Server logs show VaapiEncoder entries rather than libx264

Sizing rules of thumb: an Intel iGPU handles 2โ€“3 simultaneous 1080p transcodes comfortably; software x264 1080p wants roughly one modern core per stream. If remote users see buffering with CPU idle, the problem is usually network or subtitle burning (image-based subtitles like PGS force transcode even when codecs match).

Tune clients toward direct play: prefer Jellyfin apps (Android TV, iOS, Kodi addon) over browsers; browsers can't handle many containers/codecs natively.

Remote Access via Reverse Proxy

Expose only the reverse proxy publicly; keep 8096 bound to LAN. With Traefik (traefik-v3-reverse-proxy):

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.fogserv.cloud`)"
      - "traefik.http.routers.jellyfin.entrypoints=websecure"
      - "traefik.http.routers.jellyfin.tls.certresolver=le"
      - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"

Then in Jellyfin's Networking settings:

Security checklist for public exposure:

TLS termination details and cert automation live in ../security/tls-configuration.md and ../security/letsencrypt-automation.md.

Users & Backups

User management (Dashboard โ†’ Users):

What to protect:

Data Location Verdict
Config + embedded DB /opt/jellyfin/config Back up (restic, daily)
Thumbnails/cache ./config/data subpaths, /cache Exclude
Media files /srv/media Exclude (re-downloadable)
Watch states inside config DB Covered by config backup
restic backup /opt/jellyfin/config --tag jellyfin \
  --exclude '/opt/jellyfin/config/data/**' \
  --exclude '/opt/jellyfin/config/cache/**'

A full Jellyfin rebuild from backup is: redeploy compose file, restore config dir, remount media โ€” under 30 minutes (RTO).

Practical Examples

Example 1: Diagnose "playback buffers remotely"

docker exec jellyfin tail -f /config/log/log_*.log | grep -i -E 'transcode|direct'
intel_gpu_top   # video engine busy? โ†’ HW transcode working

Decision tree: logs say DirectPlay + still buffering โ†’ bandwidth problem (check upload speed / MTU). Logs say transcoding with libx264 โ†’ acceleration not enabled or /dev/dri missing in container.

Example 2: Add a new library without rescanning everything

Add files under correct naming scheme โ†’ inotify picks up new items within seconds. Verify: Dashboard โ†’ Scheduled tasks shows no full scan triggered; new title appears in library.

Example 3: Version upgrade procedure

cd /opt/jellyfin
docker compose down
tar czf ~/jellyfin-config-pre-upgrade.tgz config/
sed -i 's/jellyfin:10\.10/jellyfin:10.11/' docker-compose.yml
docker compose pull && docker compose up -d
docker compose logs -f          # watch migration messages

Rollback = restore previous tag + unpack the tarball.

Troubleshooting & Common Pitfalls

Problem Cause Fix
No hardware encode options in dashboard /dev/dri not passed through; drivers missing on host Add device mapping; install intel-media-va-driver on host; ls /dev/dri must exist
Transcode falls back to libx264 despite VAAPI Codec not ticked in acceleration formats, or driver lacks HEVC encode Tick formats; check vainfo output on host
Metadata wrong/duplicated titles Bad folder naming; mixed library types Rename per layout rules; separate libraries per type
Remote playback fails only outside LAN Published URL mismatch; proxy missing websocket support Set PublishedServerUrl; verify Traefik routes websockets (default OK in v3)
Subtitles cause constant transcoding Image-based subs (PGS/VOBSub) burned into video Prefer SRT text subtitles in library settings
Library scan pegs disk for hours Chapter image extraction on huge library Disable chapter images; raise scan interval
Watch states lost after rebuild Config volume excluded from backup too broadly Back up config minus cache paths only

Next Steps / Ops Actions

Sources & Related Articles

External references consulted:

Related knowledge-base articles:

Change Log

2026-08-26

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse