Homelab 10 min read

Building a Self-Hosted Media Server with Jellyfin

H

Why Self-Host?

After years of juggling multiple streaming subscriptions for shows that kept getting removed or shuffled between platforms, I decided it was time to reclaim my media library. Enter Jellyfin — an open-source, self-hosted media server that gives you a Netflix-like experience over your own hardware, with zero subscription fees and complete control over your data.

Hardware

I repurposed an old desktop workstation I had sitting in a closet:

  • CPU: Intel Core i5-8400
  • RAM: 16 GB DDR4
  • Storage: 2 × 4 TB WD Red NAS drives (RAID 1 via mdadm)
  • OS: Ubuntu Server 22.04 LTS

The i5-8400 has Intel Quick Sync Video, which is excellent for hardware-accelerated transcoding — a lifesaver when multiple streams are running simultaneously.

Docker Compose Setup

I run Jellyfin (and most of my homelab stack) with Docker Compose. Here’s the core of my docker-compose.yml:

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    network_mode: host
    volumes:
      - /opt/jellyfin/config:/config
      - /opt/jellyfin/cache:/cache
      - /mnt/media:/media:ro
    environment:
      - JELLYFIN_PublishedServerUrl=https://media.homelab.local
    devices:
      - /dev/dri:/dev/dri
    restart: unless-stopped

The /dev/dri device passthrough enables hardware transcoding via Intel Quick Sync. Without it, every transcode hits the CPU — fine for one stream, but it chokes quickly with multiple viewers.

Enabling Intel Quick Sync

Inside the Jellyfin admin panel:

  1. Go to Dashboard → Playback → Transcoding
  2. Set the hardware acceleration to Intel QuickSync (QSV)
  3. Enable H.264, H.265, and VP9 hardware decoding

After enabling QSV, CPU usage during a 1080p transcode dropped from ~85% to under 15%.

Automatic Media Organization

I pair Jellyfin with Radarr (movies) and Sonarr (TV shows) for automated library management, and Bazarr for subtitle fetching. Everything runs as Docker services on the same host.

When a new file lands in the watch folder, the *arr stack renames it to the standard Movie Title (Year)/Movie Title (Year).mkv format that Jellyfin expects, then triggers a library refresh.

Lessons Learned

  • Use RAID, but also back up. RAID 1 protects against a single drive failure; it’s not a backup strategy. I run nightly rclone syncs of my config and metadata to an off-site cloud bucket.
  • Hardware transcoding is worth it. The 10 minutes spent configuring QSV passthrough saves significant CPU headroom.
  • Start simple. It’s tempting to add every *arr tool at once. Start with just Jellyfin, get it stable, then add automation layer by layer.

Result

The setup has been running for over 6 months with zero unplanned downtime. Streaming from my phone on the go (via Tailscale VPN) feels just as smooth as any commercial service — and my entire library is exactly where I left it, no licensing changes required.

If you’re on the fence about self-hosting, give Jellyfin a try. The community is great and the documentation has improved tremendously over the past year.

Jyers

PhD student in quantum physics · tinkerer · open source enthusiast