# Movie Night Over the Internet: Jellyfin + Tailscale on a Raspberry Pi 4

A cozy, slightly chaotic, very fun setup story: turning a Raspberry Pi 4 into a private movie-night server with Jellyfin, Docker, and Tailscale—starting on microSD and graduating to SSD later.
December 21, 2025 · 9 min · Iman Alipour Comments

Ok. Let me tell you the why first. So…. I was turning from one side to the other on my bed, going through YouTube. The thing that I used to silence my pain, to let the time pass, and to forget. But it didn’t hit the same anymore. I was bored. Eternal boredom. Then the loneliness came back. It’s funny how you can be in the middle of dozens of people, i.e., not alone, but still feel lonely. Anyway. I though to myself “let’t message one of my two best friends”, We eventually went into a google meet, talked and talked, and decided to watch a movie together at the same time. I told him I still have my Raspberry Pi lying around, let me put it to good use. Few hours passed, and I was ready. Here is what happened in those few hours.

I wanted the kind of movie night where everyone presses play at the same moment, laughs at the same joke, and groans at the same plot twist—even when we’re scattered across different apartments and time zones. I also wanted it to be a little nerdy, a little elegant, and the kind of project that makes you feel like you own your gadgets again. Enter: a Raspberry Pi 4 (8GB), Jellyfin, and Tailscale.

This post is the “how it went” and the “how to do it” version. It’s written for the future-me who will forget everything the second it works, and for anyone who wants a private streaming setup without punching holes in their router for the entire internet to poke at.

## The idea#

Jellyfin is the media server that turns your own movie files into a nice Netflix-like library. Tailscale is the secret tunnel that lets your friends reach your server as if they were sitting on your Wi‑Fi—without you exposing ports to the public internet. Docker is the little shipping container that keeps everything tidy and portable, which matters because I started on a microSD card and planned to move to an SSD later.

The trick to making a Raspberry Pi feel “fast enough” is to avoid video transcoding whenever possible. When Jellyfin can “direct play” a file, the Pi mostly just serves bytes. When it has to transcode, it suddenly feels like you asked a hamster to tow a car. So the theme of this build is compatibility: store media in formats that phones, browsers, and TVs can play directly.

## What you need#

My setup uses Ubuntu (64-bit is the long-term-friendly choice), a Raspberry Pi 4, a microSD card to get started, and eventually a USB 3 SSD for real “server vibes.” You’ll also want a folder of movies you’re allowed to stream to your friends. Streaming DRM content from Netflix or rental platforms through Jellyfin isn’t supported, and I’m intentionally keeping this firmly in the “your own files” lane.

## Step one: choose a home for your stuff#

Before installing anything, I picked paths that would survive my future SSD upgrade. The idea is simple: keep all persistent app data under one directory, keep all media under another directory, and let Docker mount those paths into containers.

On the Pi, I created a little directory neighborhood under `/srv`:

    sudo mkdir -p /srv/jellyfin/{config,cache} /srv/media /srv/compose/jellyfin
    sudo chown -R $USER:$USER /srv

If you’re not ready to move movies into `/srv/media` yet, that’s okay. The important part is that you pick a place you can later mount your SSD onto without rewriting your whole setup.

## Step two: install Docker and start thinking in containers#

I installed Docker and verified that `docker compose` worked. After that, everything became a file called `docker-compose.yml` and the comforting feeling that I can rebuild my server from a single folder if life gets weird.

A typical install flow on Raspberry Pi OS looks like this:

    sudo apt update
    sudo apt install -y ca-certificates curl
    curl -fsSL https://get.docker.com | sh
    sudo usermod -aG docker $USER
    newgrp docker
    docker --version
    docker compose version

Now for the part that feels like summoning a friendly daemon.

## Step three: Jellyfin, running happily in Docker#

In `/srv/compose/jellyfin/docker-compose.yml`, I defined a Jellyfin service and mounted my config, cache, and media directories into it. The key mental shift is that Jellyfin can only see what you mount into the container. If you try to browse to `/home/hub/Documents/movies` from inside the Jellyfin UI and it looks like the folder doesn’t exist, it’s not being dramatic—it genuinely can’t see it. Containers are like that.

Here’s a simple `docker-compose.yml` that works well on a Pi:

    services:
      jellyfin:
        image: jellyfin/jellyfin:latest
        container_name: jellyfin
        user: "1000:1000"
        ports:
          - "8096:8096/tcp"
          - "7359:7359/udp"
        volumes:
          - /srv/jellyfin/config:/config
          - /srv/jellyfin/cache:/cache
          - /srv/media:/media
        restart: unless-stopped

If your user isn’t UID 1000, check it with `id -u` and `id -g`, then update the `user:` line accordingly.

I started Jellyfin like this:

    cd /srv/compose/jellyfin
    docker compose up -d

After that, Jellyfin lived at `http://<pi-lan-ip>:8096` on my home network, which is the most satisfying “it’s alive” moment of the whole build.

## Step four: Tailscale, the magic door for friends#

Now comes the part that makes everything feel effortless. Instead of forwarding ports, configuring certificates, and hoping I didn’t accidentally publish my server to the open ocean, I installed Tailscale on the Pi host.

    curl -fsSL https://tailscale.com/install.sh | sh
    sudo tailscale up
    tailscale ip -4

That last command gives you a `100.x.y.z` address. It’s your Pi’s Tailscale IP, and it’s the address your friends will use to reach Jellyfin. From anywhere, the server becomes:

    http://100.x.y.z:8096

Or if you enabled magicDNS:

    http://rpi:8096

The moment you can open that URL from your phone on mobile data and see your own server is the moment you start plotting your next homelab project.

If you want friends to access only this server and not everything else in your tailnet, Tailscale has a device sharing flow that’s perfect for “here’s the Pi, please don’t look in the rest of my digital house.”

## Step five: create a Jellyfin user for your friend#

Jellyfin accounts are local to your server. Your friend won’t “sign up” on some global Jellyfin site; instead, you create a user for them inside your Jellyfin admin dashboard. The nice part is that you can give them access only to the libraries you want, and you can keep admin permissions to yourself.

If typing passwords on a TV app makes you feel old instantly, Jellyfin’s Quick Connect feature can make logins much smoother. Your friend gets a short code on their device, you approve it from your logged-in browser, and nobody has to poke letters into a remote control one by one.

## Step six: the secret to a smooth Pi movie night is Direct Play#

A Raspberry Pi 4 is perfectly happy serving media files. It becomes less happy when it has to convert them on the fly. The goal is to store your library in a way that most clients can play without conversion.

For “plays on basically everything,” I like MP4 with H.264 video and AAC audio. When I inspected one of my files, I found H.264 video already, but the audio was AC3. AC3 is fine on some devices, but it’s not universal in browsers, and “my friend has no audio” is a movie-night mood killer.

The fix was to keep the video as-is and convert only the audio to AAC, which is much lighter than re-encoding the whole video. This command copies the H.264 video stream, converts audio to AAC stereo, and produces an MP4 that’s friendlier to phones and browsers:

    ffmpeg -i "500.Days.of.Summer.2009.1080p.BluRay.RARBG.FilmKio.mkv"   -map 0:v:0 -map 0:a:0   -c:v copy   -c:a aac -b:a 384k -ac 2   -movflags +faststart   "500.Days.of.Summer.2009.1080p.mp4"

Subtitles deserve their own tiny cautionary tale. Some subtitle formats trigger transcoding because the client can’t render them natively and asks the server to burn them into the video. Text-based SRT subtitles are usually the easiest path. If your MKV contains an SRT track, you can extract it and keep it next to your MP4:

    ffmpeg -i "500.Days.of.Summer.2009.1080p.BluRay.RARBG.FilmKio.mkv"   -map 0:s:0   "500.Days.of.Summer.2009.1080p.srt"

Once your friend is watching, you can confirm how Jellyfin is handling playback by checking the active session in the admin dashboard. Seeing “Direct Play” feels like winning.

## Step seven: SyncPlay, aka “we pressed play together”#

Jellyfin has SyncPlay for “group watch” nights, and it’s wonderfully simple when everyone is using compatible clients. In practice, the web client is an excellent baseline because it’s consistent and easy for friends. When we do movie night, we start a SyncPlay group, join it from each device, and keep a voice call open for commentary and laughter. It’s not complicated; it’s just… cozy.

## Starting on microSD, graduating to SSD later#

Yes, you can run all of this on microSD to begin with. It works. It also feels like using a napkin as a hard drive if you add heavy-write services later. My plan is to move the system to an SSD when convenient, because SSDs are faster and they handle constant writes far better than microSD cards.

The reason the `/srv` layout is so helpful is that migration becomes a simple copy-and-mount story. When the SSD arrives, you can copy `/srv` to the SSD, mount the SSD at `/srv`, reboot, and your containers will come back exactly as before. Docker will happily reuse the same volume paths; Jellyfin will happily reuse the same library; and you’ll feel like a wizard who planned ahead.

## Where this goes next#

Once Jellyfin and Tailscale are stable, it’s easy to imagine the Pi doing more. Pi-hole is light and useful. A smart home hub is fun. Nextcloud is possible, especially with SSD storage and some attention to databases and caching. The practical advice I’m giving future-me is to add services one at a time and keep `/srv` as the center of gravity, because nothing ruins movie night like “I tweaked DNS and now nothing works.”

For now, though, this little Pi does the job: it hosts a library, it welcomes friends through a private tunnel, and it turns “let’s watch something” into a real shared moment—even when we’re not in the same room.

Happy streaming.

The movie night went great, I even posted a BeReal on it. The choice of movie was not random either. I’ll write a review for it later on. It was just amazing.

Downloads: Markdown · Signature (.asc)

View OpenPGP signature

    -----BEGIN PGP SIGNATURE-----

    iQIzBAABCgAdFiEEVaKl3oR5K6zGyu4/tYgoUOBMjSoFAmpkbugACgkQtYgoUOBM
    jSrqzw/8Crod3Gm5xGMMrOtsoVm9NFwTAD7xdc8H5LcFC8P5OZmyEYBxF/SEHk6m
    TDhSyj6bNdShWIrzkKL0XHm6ntjhkBX4+dFzPbKjpHZ84on/xfNwIOh8br+WJEBF
    V3zCialBjjxxE37PVLkqsNVVtMgT2Wv5GnR7SWLKkovJWpIn/FP0gSsQFUIvRvdC
    Xhy3nvODqXZqfg77kKllmbkPI5ePkxl95CK9fd4yzhI13G41vveVO4dt2JhWVR6H
    dfRv6XG53WGP0nVWyEdHJjJhiT1JTd7//AD3DsRCTmBNyaxweRlPsvqqICquGx1U
    LGQ62y0oZL5WDqjiD7T2ZJAJ6sqr6NngFGjh7RaKOWDT1+8fTdXfQg/t91lTHVfh
    efVqOiH+B6SlzqPM4LgzRjf+36XdSu9R1w75sGykQpGRBfq2IymoM6RPQLEwgm3J
    haMjYRqx5jZSLj9FpjOZFYEuqYCa0ut0lUgY9BDHf0u8kKrW6OQZFVdhN31g+Lvf
    5qjzC+ZzR4BLMpA4E33NKmYT4Rt1i5mSPiGY85yqhJdjFJRtPfXXf05+m7VGjRPp
    sWQmqO1o7cChFqVnF5IalDFCNIsGavBf8F0/7tu3y4bLIqoBMBfgIgg9KMORVHIn
    kqUsV4LpNgVWdTzp0QURkHUOL5uP72Jqa3ppVYEXlJQbhuLpCDY=
    =/K6E
    -----END PGP SIGNATURE-----

Verify locally:

    torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/boredom.md
    torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/boredom.md.asc
    gpg --verify boredom.md.asc boredom.md

Open on GitHub ↗
Comments powered by Isso
RSS · Visitors this week: —