Self-hosted • Privacy-first • No tracking
Home / Homelab / Jellyfin Media Server on Docker: A Complete Homelab Installation Guide
Homelab #self-hosted#homelab#docker-compose#jellyfin#media-server ⏱ 8 min • 👁 3 • Aug 29, 2026

Jellyfin Media Server on Docker: A Complete Homelab Installation Guide

Step-by-step Docker Compose deployment of Jellyfin v10.11.11 with hardware acceleration, reverse proxy, backups, and troubleshooting.

AdSense — Top (970x90) • Responsive
Jellyfin Media Server on Docker: A Complete Homelab Installation Guide

Introduction

Jellyfin is the open-source, self-hosted media server that puts you in complete control of your movies, TV shows, music, and photos. Unlike proprietary alternatives, Jellyfin has no subscription fees, no telemetry, and no cloud dependency. It streams your media directly to any device on your network or across the internet via a secure reverse proxy. For homelab enthusiasts, it is the cornerstone of a privacy-first entertainment stack.

This guide walks you through deploying Jellyfin v10.11.11 (the latest official release as of 2026-06-06) using Docker Compose. You will learn how to set up persistent storage, configure hardware acceleration for efficient transcoding, and secure remote access with a reverse proxy. We also cover backup strategies and advanced hardening for production-grade deployments.

By the end, you will have a fully functional media server that is reproducible, maintainable, and ready for daily use. We focus on practical, copy-paste commands with explanations, avoiding vague instructions. All configuration files are versioned and stored in a single directory for easy backup and migration.

Let's get your media library streaming with Jellyfin on Docker.

Prerequisites

Before you begin, ensure your host meets the following minimum requirements. These are typical values; actual usage depends on the number of concurrent streams and whether hardware transcoding is enabled.

Component Minimum Recommended Notes
CPU 2 cores 4+ cores (Intel Quick Sync or AMD VCE for HW transcoding) Software transcoding of 1080p requires ~2 cores per stream; 4K needs 4+ cores.
RAM 2 GB 4-8 GB Jellyfin itself uses ~500 MB; transcoding buffers add to this.
Storage 10 GB free 50+ GB free For Docker images, metadata, and transcoding cache. Media library size is separate.
GPU (optional) None Intel iGPU (QSV), NVIDIA (NVENC), or AMD (AMF) Required for hardware transcoding. See Jellyfin Hardware Acceleration docs.
OS Linux (Ubuntu 22.04+), macOS, Windows 10+ Linux recommended Docker Engine 20.10+ and Docker Compose v2.
Software Docker Engine, Docker Compose v2 Latest stable Install via Docker's official docs.

Network: Ensure port 8096 is reachable on your LAN. For remote access, you will need to forward ports 80/443 on your router (covered later).

Step-by-Step Installation

Step 1: Create the Project Directory and .env File

Open a terminal and create a dedicated directory for Jellyfin. This keeps all configuration in one place.

mkdir -p ~/jellyfin && cd ~/jellyfin

Now create a .env file to store your user ID and group ID. This avoids hardcoding values in the Compose file and makes it portable.

cat > .env << 'EOF'
PUID=1000
PGID=1000
TZ=UTC
JELLYFIN_VERSION=10.11.11
EOF

Warning: Never commit this .env file to a Git repository. It may contain sensitive values in the future (e.g., TZ is public, but keep the habit). Run id -u and id -g on your host and verify against the image's documentation (default user in this image is jellyfin). Adjust PUID and PGID to match your host user to avoid permission issues with media files.

Step 2: Create the docker-compose.yml File

Using your preferred editor, create docker-compose.yml in the same directory.

nano docker-compose.yml

Paste the following complete configuration. We use the official image and pin the version via the environment variable.

services:
  jellyfin:
    image: jellyfin/jellyfin:${JELLYFIN_VERSION:-latest}
    container_name: jellyfin
    environment:
      - PUID=${PUID:-1000}
      - PGID=${PGID:-1000}
      - TZ=${TZ:-UTC}
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /path/to/your/media:/media:ro
    ports:
      - "8096:8096"
      - "8920:8920" # Optional: HTTPS direct access
    restart: unless-stopped

Replace /path/to/your/media with the absolute path to your media library (e.g., /srv/media). The :ro flag mounts it read-only to prevent accidental modification from the container.

Note: The version 10.11.11 is set in .env. If you remove it, the latest tag is used. Check the official GitHub releases page before pinning a version — the version above may be outdated by now.

Step 3: Pull and Start the Container

Run the following command to pull the image and start the container in detached mode.

docker compose up -d

Verify the container is running and check its logs for errors.

docker compose ps && docker compose logs --tail=50 jellyfin

Wait a few seconds. You should see a message like [INF] Jellyfin.Server.Main: Startup complete.

Step 4: Initial Web Setup

Open a browser and navigate to http://your-server-ip:8096. Follow the setup wizard:

  1. Choose your display language.
  2. Create an admin username and password. Do not skip this — store credentials in a password manager.
  3. Add your media library: select the /media mount point from the container's perspective. Map it to the folders you mounted (e.g., /media/movies).
  4. Configure metadata language and country.
  5. Complete the wizard and log in.

Step 5: Configure Hardware Acceleration (Optional but Recommended)

For efficient transcoding, enable hardware acceleration. This offloads video encoding/decoding from the CPU to a GPU, reducing load and power consumption.

Intel Quick Sync (QSV)

Add the following to the jellyfin service in docker-compose.yml:

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

Then in the Jellyfin admin dashboard, go to Dashboard > Playback > Transcoding. Select Intel QuickSync as the hardware acceleration method. Enable all available decoders (H.264, HEVC, etc.).

NVIDIA NVENC

Install the NVIDIA Container Toolkit, then add:

    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

In Jellyfin, select NVIDIA NVENC as the acceleration method.

Verification: After enabling, play a video that requires transcoding. Check the admin dashboard's Activity tab; you should see Transcode: hw in the session details.

Step 6: Set Up a Reverse Proxy (Caddy)

For secure remote access with HTTPS, use Caddy as a reverse proxy. It automatically obtains and renews SSL certificates from Let's Encrypt.

Create a separate directory for Caddy:

mkdir -p ~/caddy && cd ~/caddy

Create a docker-compose.yml for Caddy:

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./data:/data
      - ./config:/config
    restart: unless-stopped

Create the Caddyfile:

media.example.com {
    reverse_proxy jellyfin:8096
}

Replace media.example.com with your domain. Add the following line to your Jellyfin docker-compose.yml to connect it to Caddy's network:

    networks:
      - default
      - caddy

networks:
  caddy:
    external: true

Create the external network and start Caddy:

cd ~/caddy && docker network create caddy && docker compose up -d

Restart Jellyfin to join the network:

cd ~/jellyfin && docker compose up -d

Now access your server via https://media.example.com. Configure Jellyfin's Dashboard > Networking to set the base URL to https://media.example.com for correct callback URLs.

Step 7: Enable Automatic Backups

Backup the config directory, which contains your entire Jellyfin database, user settings, and metadata. Use a cron job to create nightly snapshots.

crontab -e

Add the following line to run a backup every day at 2 AM:

0 2 * * * tar -czf ~/backups/jellyfin-$(date +\%Y\%m\%d).tar.gz -C ~/jellyfin config

Create the backup directory first:

mkdir -p ~/backups

Test the command manually to ensure it works. Restore by extracting the archive back to ~/jellyfin/config.

Advanced Configuration and Hardening

Reverse Proxy and SSL

We covered Caddy above. Other options include Nginx Proxy Manager or Traefik. For Nginx, use the following server block:

server {
    listen 443 ssl;
    server_name media.example.com;

    ssl_certificate /etc/letsencrypt/live/media.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/media.example.com/privkey.pem;

    location / {
        proxy_pass http://jellyfin:8096;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Backup Strategy

Beyond the config directory, consider backing up your media library metadata separately if you use custom images. Jellyfin stores thumbnails and fanart in config/metadata. The tar command above covers this. For offsite backups, use rclone to sync ~/backups to a cloud provider or another server.

Optional Hardening

The following settings increase security but may break functionality if not adapted to your environment. Do not copy them blindly.

    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETUID
      - SETGID
      - DAC_OVERRIDE
    read_only: true
    tmpfs:
      - /tmp
      - /cache
  • read_only: true makes the root filesystem read-only, forcing Jellyfin to write only to volumes and tmpfs. This prevents an attacker from modifying binaries.
  • cap_drop: ALL removes all kernel capabilities, then selectively adds back the minimum needed for Jellyfin to run. The list above is a starting point; you may need to add NET_BIND_SERVICE if you bind to port 80/443 directly.
  • tmpfs mounts for /tmp and /cache ensure transient data is in memory, reducing disk writes and clearing on restart.

Test thoroughly after applying. If the container fails to start, check logs with docker compose logs jellyfin.

Troubleshooting Common Issues

Error Cause Solution
Permission denied when accessing media files PUID/PGID mismatch Run id -u and id -g on your host, update .env, recreate container: docker compose up -d --force-recreate
Container exits with code 1 Corrupt config or port conflict Check docker compose logs. If port 8096 is in use, change the host port in Compose. If config is corrupt, restore from backup.
No hardware transcoding available GPU device not passed through Verify /dev/dri exists on host (ls -la /dev/dri). Add devices section in Compose and restart. For NVIDIA, ensure toolkit is installed and nvidia-smi works.
Reverse proxy returns 502 Bad Gateway Jellyfin not on the same Docker network as proxy Ensure both containers share the caddy network. Run docker network inspect caddy to confirm.
Playback stutters on remote access Insufficient upload bandwidth or transcoding disabled Enable transcoding in Jellyfin and set a lower bitrate limit (e.g., 10 Mbps). Check your ISP upload speed.
Metadata not downloading Outbound network blocked Jellyfin needs access to api.themoviedb.org. Allow outbound HTTPS from the container.

Conclusion

You now have a production-grade Jellyfin media server running in Docker. You deployed it with persistent storage, optional hardware acceleration, and a secure reverse proxy. The backup routine ensures your config is safe, and the troubleshooting table helps you resolve common issues quickly.

Docker makes Jellyfin portable — you can migrate the entire stack to a new host by copying the ~/jellyfin directory and running docker compose up -d. This guide gives you a solid foundation to expand with additional services like Sonarr, Radarr, and Prowlarr for automated media management.

FAQ

1. Can I update Jellyfin without losing my settings?

Yes. Update the JELLYFIN_VERSION in your .env file to the new version, then run docker compose pull && docker compose up -d. Your config directory persists, so all settings, users, and metadata remain intact. Always backup before upgrading.

2. How do I access Jellyfin from outside my home network?

Set up a reverse proxy with a domain name (as shown in Step 6). Forward ports 80 and 443 on your router to the host running Caddy. Do not expose port 8096 directly. Use a strong password and enable HTTPS.

3. What is the best hardware for transcoding?

Intel CPUs with Quick Sync (6th gen or newer) offer the best performance-per-watt for Jellyfin. NVIDIA GPUs are also excellent but consume more power. For a low-power homelab, an Intel N100 or i5-12400 is commonly reported to handle multiple 4K transcodes. The exact number depends on codec and bitrate.

4. Why does Jellyfin use so much RAM?

Jellyfin caches metadata and transcoding buffers in RAM. The default is to use up to 50% of available memory. You can limit this in Dashboard > Playback > Transcoding by setting a lower buffer size. Typical usage is 500 MB to 2 GB depending on library size and active streams.

5. Can I run Jellyfin and Plex simultaneously?

Yes, they can coexist on the same server. Use different ports (Plex uses 32400). Both can access the same media files, but they will each generate their own metadata. This is useful for migration or comparison testing.

AdSense — In-article (responsive)

Related Guides