Streaming movies with Jellyfin
📅 2026-03-13
In my previous blog post, I covered setting up centralized logging with Loki and Promtail. In this post, I'm deploying Jellyfin — a free, open-source media server. The idea is simple: I have a collection of movies on my server, and I want to stream them on my local network from any device with a web browser.
Jellyfin is a self-hosted media server that organizes your media library, provides metadata (posters, descriptions, subtitles), and streams content to any device with a web browser or a Jellyfin client app. Think of it as a self-hosted Netflix.
It's the most popular open-source alternative to Plex, with no account requirements and no premium tier — everything is free. No telemetry, no tracking, no "sign in to continue" prompts. You own your data and your server.
Other options I considered:
Jellyfin checked all the boxes: fully open source, no account wall, good client support, and active development.
Jellyfin runs as a single Docker container. The Ansible playbook uses the docker_container module (consistent with how I deploy all other services in this home lab):
- name: Ensure Jellyfin container is running
docker_container:
name: jellyfin
image: jellyfin/jellyfin
state: started
restart_policy: unless-stopped
published_ports:
- "127.0.0.1:8096:8096"
env:
TZ: "Europe/Budapest"
volumes:
- "/home/jellyfin/config:/config"
- "/home/jellyfin/cache:/cache"
- "/mnt/movies:/media:ro"
A few things worth noting:
Read-only media: The movies directory is mounted as :ro (read-only). Jellyfin only needs to read the files for playback and metadata scanning — it should never modify the source media.
Localhost binding: Following the same pattern as all my other services, the port is bound to 127.0.0.1 and accessed through my Nginx reverse proxy at https://jellyfin.arcade-lab.io.
Config and cache separation: Jellyfin stores its database, metadata, and settings in /config, and uses /cache for image resizing and other temporary data. Keeping them separate makes backups cleaner — you only need to back up /config.
As with everything else in my home lab, the entire setup is automated with Ansible. The playbook handles the full lifecycle:
All paths and configuration values live in a variables file that's excluded from version control, keeping sensitive information out of the repository. Running the playbook on a fresh server gets Jellyfin up and running in a single command.
With Jellyfin deployed, I now have:
Noice! 🎉