Streaming services have fragmented into a dozen competing platforms, each with their own subscription fee and content library. A self-hosted media server puts you back in control: your media, organized your way, accessible from any device, with no monthly fees and no content disappearing due to licensing changes. The three leading options are Jellyfin (fully open source), Plex (freemium with optional paid tier), and Emby (freemium with paid premium).

Jellyfin vs Plex vs Emby

Feature Jellyfin Plex Emby
License GPL-2.0 (fully open source) Proprietary (freemium) Proprietary (freemium)
Cost Free, no paid tiers Free + Plex Pass ($5/mo or $120 lifetime) Free + Emby Premiere ($4.99/mo or $119 lifetime)
Account required No (local auth) Yes (Plex account mandatory) No (local auth)
Hardware transcoding Free (VAAPI, NVENC, QSV) Plex Pass required Premiere required
Remote access Self-managed (reverse proxy) Built-in relay service Self-managed
Live TV / DVR Yes (free) Yes (Plex Pass) Yes (Premiere)
Mobile apps Free (official + third-party) Free with ads / paid unlock Paid unlock required
Plugins/Extensions Yes (community plugins) Limited Yes (plugin system)
Client ecosystem Good (web, apps, Kodi, Infuse) Excellent (widest platform support) Good (web, apps)
Metadata providers TMDb, OMDb, local NFO files Plex agents + custom TMDb, OMDb, TVDb

For self-hosters who value open source and data sovereignty, Jellyfin is the clear choice. Plex is superior if you need the widest client compatibility and are willing to pay for Plex Pass. Emby occupies a middle ground but has a smaller community than either alternative.

Docker Setup: Jellyfin

# docker-compose.yml for Jellyfin
version: "3.8"
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    user: "1000:1000"
    environment:
      - JELLYFIN_PublishedServerUrl=https://media.example.com
    volumes:
      - jellyfin_config:/config
      - jellyfin_cache:/cache
      - /mnt/media/movies:/data/movies:ro
      - /mnt/media/tv:/data/tv:ro
      - /mnt/media/music:/data/music:ro
    ports:
      - "8096:8096"
      # DLNA (optional)
      # - "1900:1900/udp"
      # - "7359:7359/udp"
    # Hardware transcoding - Intel QSV/VAAPI
    devices:
      - /dev/dri:/dev/dri
    # Hardware transcoding - NVIDIA
    # deploy:
    #   resources:
    #     reservations:
    #       devices:
    #         - driver: nvidia
    #           count: 1
    #           capabilities: [gpu]

volumes:
  jellyfin_config:
  jellyfin_cache:

Docker Setup: Plex

# docker-compose.yml for Plex
version: "3.8"
services:
  plex:
    image: lscr.io/linuxserver/plex:latest
    container_name: plex
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
      - VERSION=docker
      - PLEX_CLAIM=${PLEX_CLAIM_TOKEN}  # Get from plex.tv/claim
    volumes:
      - plex_config:/config
      - /mnt/media/movies:/movies:ro
      - /mnt/media/tv:/tv:ro
      - /mnt/media/music:/music:ro
      # Transcode directory (use SSD or tmpfs for performance)
      - /tmp/plex_transcode:/transcode
    ports:
      - "32400:32400"
    devices:
      - /dev/dri:/dev/dri  # Intel VAAPI/QSV

volumes:
  plex_config:

Docker Setup: Emby

# docker-compose.yml for Emby
version: "3.8"
services:
  emby:
    image: emby/embyserver:latest
    container_name: emby
    restart: unless-stopped
    environment:
      - UID=1000
      - GID=1000
      - GIDLIST=44,105  # video and render groups
    volumes:
      - emby_config:/config
      - /mnt/media/movies:/mnt/movies:ro
      - /mnt/media/tv:/mnt/tv:ro
    ports:
      - "8096:8096"
      - "8920:8920"  # HTTPS
    devices:
      - /dev/dri:/dev/dri

volumes:
  emby_config:

Hardware Transcoding

Transcoding converts media from one format to another on the fly, which is essential when a client device does not support the original codec. Without hardware acceleration, transcoding is CPU-intensive and limits the number of simultaneous streams. Hardware transcoding offloads this work to the GPU.

Intel Quick Sync / VAAPI

Intel integrated graphics (6th generation and newer) provide excellent transcoding performance with minimal power usage:

# Verify Intel GPU is available
ls -la /dev/dri/
# Should show: card0, renderD128

# Check GPU capabilities
vainfo
# Should list H.264, HEVC decode/encode profiles

# Docker device mapping
devices:
  - /dev/dri:/dev/dri

# Ensure the container user has access to the video group
# For Jellyfin with user: "1000:1000", add supplemental groups:
group_add:
  - "44"    # video group
  - "105"   # render group (varies by distro)

NVIDIA NVENC

NVIDIA GPUs provide powerful transcoding but require additional setup:

# 1. Install NVIDIA Container Toolkit
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
  sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# 2. Verify GPU is accessible in Docker
docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi

# 3. Docker Compose configuration
deploy:
  resources:
    reservations:
      devices:
        - driver: nvidia
          count: 1
          capabilities: [gpu]

Transcoding Performance Comparison

Method Simultaneous 1080p Streams Power Usage Quality
Software (x264) 1-2 (depends on CPU) High Excellent
Intel QSV (i5/i7) 5-10+ Low (~15W) Good
NVIDIA NVENC (GTX 1660+) 8-15+ Medium (~30-75W) Very good
AMD VCN (RX 5000+) 3-8 Low-Medium Good
Tip: Whenever possible, direct play instead of transcode. Organize your media in formats that most clients support natively (H.264 with AAC audio in MP4/MKV containers). This eliminates transcoding overhead entirely and produces the best quality.

Library Organization

Proper directory structure is critical for automatic metadata matching:

# Movies - one folder per movie
/mnt/media/movies/
  The Matrix (1999)/
    The Matrix (1999).mkv
    The Matrix (1999).srt
  Inception (2010)/
    Inception (2010).mkv

# TV Shows - show > season > episodes
/mnt/media/tv/
  Breaking Bad/
    Season 01/
      Breaking Bad - S01E01 - Pilot.mkv
      Breaking Bad - S01E02 - Cat's in the Bag.mkv
    Season 02/
      Breaking Bad - S02E01 - Seven Thirty-Seven.mkv

# Music - artist > album > tracks
/mnt/media/music/
  Pink Floyd/
    The Dark Side of the Moon (1973)/
      01 - Speak to Me.flac
      02 - Breathe.flac
Warning: Mount media directories as read-only (:ro) in Docker. This prevents the media server from accidentally modifying or deleting your files. Write access is only needed for metadata caching, which should go to a separate config/cache volume.

Remote Access with Nginx Reverse Proxy

# nginx/conf.d/jellyfin.conf
server {
    listen 443 ssl http2;
    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;

    # Large file uploads (for syncing)
    client_max_body_size 20M;

    # WebSocket support
    location /socket {
        proxy_pass http://jellyfin:8096;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    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;

        # Disable buffering for streaming
        proxy_buffering off;
    }
}

Subtitle Management

Subtitles are essential for accessibility and multilingual media. Configure automatic subtitle downloading:

Jellyfin Plugin: Open Subtitles

  1. Install the "Open Subtitles" plugin from the Jellyfin plugin catalog
  2. Configure your OpenSubtitles.org API key
  3. Set preferred subtitle languages in library settings
  4. Enable automatic subtitle downloading for new media

Bazarr (Standalone Subtitle Manager)

# Add Bazarr to your media stack
  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
    volumes:
      - bazarr_config:/config
      - /mnt/media/movies:/movies
      - /mnt/media/tv:/tv
    ports:
      - "6767:6767"

Bazarr integrates with Sonarr and Radarr to automatically download subtitles for your entire library. It supports multiple subtitle providers and can synchronize subtitle timing automatically.

Mobile and TV Clients

Platform Jellyfin Plex Emby
iOS Swiftfin (free), Infuse Plex (free with ads) Emby (paid unlock)
Android Jellyfin (free), Findroid Plex (free with ads) Emby (paid unlock)
Android TV Jellyfin (free) Plex (free) Emby (paid unlock)
Apple TV Swiftfin (free), Infuse Plex (free) Emby (paid unlock)
Roku Jellyfin (free) Plex (free) Emby (paid unlock)
Web browser Built-in Built-in Built-in
Kodi JellyCon / Jellyfin for Kodi Plex for Kodi Emby for Kodi

Performance Tuning

# Use tmpfs for transcode directory (uses RAM, much faster)
services:
  jellyfin:
    tmpfs:
      - /config/transcodes:size=4G

# Or use an SSD-backed path
volumes:
  - /ssd/jellyfin_transcode:/config/transcodes

# Network mode host for DLNA discovery (optional)
network_mode: host

For managing multiple media-related containers (Jellyfin, Bazarr, and related services), container management platforms like usulnet provide a unified view of resource usage across your media stack, making it easy to identify when transcoding is consuming excessive resources or when disk space is running low.

Summary

A self-hosted media server is one of the most satisfying self-hosting projects. Jellyfin provides a fully open-source solution with no paywalls or account requirements. Plex offers the most polished client experience and widest device support. Emby sits between the two. Regardless of which you choose, hardware transcoding is essential for a smooth multi-user experience, and proper library organization is the foundation of a well-functioning media server.