🏗️ Building my home server: Part 7

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.

🤔 Why Jellyfin?

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:

  • Plex — the most polished option, but requires an account, has a freemium model, and phones home. Some features (hardware transcoding, mobile sync) are locked behind Plex Pass.
  • Emby — started as open source but went proprietary. Similar to Plex in terms of requiring a license for key features.
  • Navidrome — excellent for music, but doesn't handle video.

Jellyfin checked all the boxes: fully open source, no account wall, good client support, and active development.

🐳 Running Jellyfin in Docker

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.

🤖 Automating with Ansible

As with everything else in my home lab, the entire setup is automated with Ansible. The playbook handles the full lifecycle:

  1. Checks that Docker is installed
  2. Creates the config and cache directories
  3. Starts the Jellyfin container with the correct volumes, ports, and environment variables

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.

🎉 Outcome

With Jellyfin deployed, I now have:

  1. On-demand streaming — a Netflix-like interface for browsing and watching my movie collection from any device on the network.
  2. Metadata and organization — Jellyfin automatically fetches posters, descriptions, ratings, and subtitles for my movies, making the library easy to browse.
  3. Fully automated deployment — one Ansible playbook sets up everything from directories to the running container.

Noice! 🎉

Share this post on: