Self-hosting a media server with Jellyfin
Plex wants $6.99/month for hardware transcoding now. They doubled the lifetime pass to $250. And they keep bolting on features I never asked for: ad-supported streaming, discovery feeds, rentals. Somewhere along the way, Plex stopped being a media server and became a streaming platform. Jellyfin is the open-source alternative that just plays my media.
Why Jellyfin
Jellyfin is a free, open-source media server. No accounts, no subscriptions, no phone-home telemetry. You point it at your media files and it serves them to any device on your network.
The project has a good origin story. Emby was the original open-source media server, but in late 2018 it relicensed and went closed-source starting with version 3.5.3. A group of contributors forked the last open version and Jellyfin was born in December 2018. It has been fully community-driven since then.
As of this writing, Jellyfin 10.10 is the current stable release. It handles movies, TV shows, music, photos, and live TV. The web UI is solid, metadata scraping works well, and hardware transcoding is properly supported.
Docker Compose setup
I run Jellyfin in Docker on my homelab. Here is the full docker-compose.yml with Intel Quick Sync passthrough for hardware transcoding:
services:
jellyfin:
image: jellyfin/jellyfin:10.10.6
container_name: jellyfin
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Berlin
volumes:
- ./config:/config
- ./cache:/cache
- /mnt/media/movies:/data/movies
- /mnt/media/shows:/data/shows
- /mnt/media/music:/data/music
devices:
- /dev/dri:/dev/dri
ports:
- 8096:8096The key parts: PUID and PGID set the user and group ID so file permissions stay consistent between the host and container. The /dev/dri device passthrough gives Jellyfin access to your Intel integrated GPU for hardware transcoding. The media directories are bind-mounted read-only in practice since Jellyfin only needs to read your files.
Run docker compose up -d and open http://your-server:8096 to walk through the setup wizard.
Hardware transcoding
Without hardware transcoding, your server's CPU handles all the video encoding when a client cannot direct-play a file. This works fine for one stream, but falls over quickly with multiple users or 4K content.
Intel Quick Sync is the easiest path. If your server has an Intel CPU with integrated graphics (most do), you just need the /dev/dri device passthrough shown above. In the Jellyfin admin dashboard, go to Playback and set the hardware acceleration dropdown to Intel Quick Sync (QSV). Enable the codecs you want to transcode and you are done.
For NVIDIA GPUs, you need the nvidia-container-toolkit installed on the host and a different device configuration:
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]Quick Sync is my recommendation for a homelab. It is built into the CPU, uses minimal power, and handles multiple 1080p streams without breaking a sweat. NVIDIA is more capable for heavy 4K transcoding but adds cost and complexity.
Library organization
Jellyfin uses metadata providers like TMDb and MusicBrainz to fetch posters, descriptions, and ratings. For this to work reliably, your files need to follow a naming convention.
For movies:
Movies/
├── Inception (2010)/
│ └── Inception (2010).mkv
├── Dune Part Two (2024)/
│ └── Dune Part Two (2024).mkv
└── The Grand Budapest Hotel (2014)/
└── The Grand Budapest Hotel (2014).mkv
For TV shows:
Shows/
└── Breaking Bad (2008)/
├── Season 01/
│ ├── Breaking Bad S01E01.mkv
│ └── Breaking Bad S01E02.mkv
└── Season 02/
└── Breaking Bad S02E01.mkv
The pattern is simple: folder per item, year in parentheses, season folders padded to two digits. Jellyfin matches against TMDb or TVDB and pulls in all the metadata automatically. If a match fails, you can add a provider ID to the folder name like Movie Name (2010) [tmdbid-12345] to force a match.
For music, Jellyfin reads embedded tags (artist, album, track number) and falls back to folder structure. If your music is already tagged properly, it just works.
Client apps
This is where Jellyfin is honest about its tradeoffs. The clients are functional but not as polished as Plex.
- Web UI: The default way to watch. Works in any browser. Solid for desktop use.
- Jellyfin Media Player: A desktop app built on MPV. Better playback support than the browser, especially for surround sound and HDR. Available as a Flatpak on Linux, and for macOS and Windows.
- Android: The official Jellyfin app on the Play Store. It works. Direct play is reliable, and the interface gets the job done.
- iOS/iPadOS: Swiftfin is the native client. It is under active development and handles most media well. Not as polished as the Plex iOS app, but perfectly usable.
- Android TV / Fire TV: Official Jellyfin app available on the Play Store. This is the weakest client in my experience. Navigation is a bit clunky, but playback itself is solid.
If you are used to Plex's apps, the step down in UI polish is noticeable. But every Jellyfin client is free, with no features locked behind a subscription.
Remote access via Tailscale
The default approach for remote access is port forwarding 8096 through your router. I do not recommend this. Exposing a media server directly to the internet is unnecessary risk.
Instead, I use Tailscale. Every device on my Tailscale network can reach Jellyfin at its Tailscale IP on port 8096. No port forwarding, no firewall rules, no exposed services. I wrote about setting up Tailscale two weeks ago. If you have it running, Jellyfin remote access is just connecting to http://homelab:8096 from anywhere.
This means my phone can stream from Jellyfin on a hotel Wi-Fi without any of my services being publicly accessible.
Jellyfin vs Plex vs Emby
This question comes up constantly. Here is my honest take.
Plex has the best clients and the smoothest user experience. The apps on every platform are polished. Discovery, recommendations, and the "watch together" features work well. But Plex requires an account, phones home, and the best features (hardware transcoding, offline sync, skip intro) require Plex Pass at $6.99/month or $69.99/year after their April 2025 price increase. Plex also now requires a paid Remote Watch Pass for streaming your own media outside your network.
Emby is the middle ground. It has better clients than Jellyfin and a more polished interface, but it is partially closed-source since the 2018 relicense. Emby Premiere (their paid tier) unlocks hardware transcoding and other features. If you want something between Plex and Jellyfin, Emby is worth a look.
Jellyfin is fully free and open-source. No accounts, no telemetry, no paywalls. Hardware transcoding works out of the box. The tradeoff is less polished clients and a smaller ecosystem. For a homelab where you control the setup and are comfortable with Docker, Jellyfin is the obvious choice.
I went with Jellyfin because I was tired of paying for features that should be free. Transcoding my own media on my own hardware should not require a subscription. Jellyfin agrees.
I cover more Docker Compose patterns in my post on lessons learned from self-hosting with Compose. And if you are wondering why I bother self-hosting anything at all, I wrote about the case for self-hosting earlier this year.
Sources
Related posts
Self-hosting with Coolify: a PaaS on your own server
How Coolify turns your VPS into a Heroku-like platform for deploying apps, databases, and services with a clean web UI.
Backup strategies for self-hosted data
The 3-2-1 backup rule applied to self-hosted services, with practical tools and patterns I use to protect my data.
Replacing Google Photos with Immich
How I set up Immich as a self-hosted alternative to Google Photos, with automatic backup, face recognition, and map view.
Enjoying the blog? Subscribe via RSS to get new posts in your reader.
Subscribe via RSS