Building a Self-Hosted Media Server with Jellyfin
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:
- Go to Dashboard → Playback → Transcoding
- Set the hardware acceleration to Intel QuickSync (QSV)
- 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