Tag: Self-hosting

Core concepts and guides for running your own software and services.

  • Jellyfin in Docker: A Self-Hosted Media Server for Movies and TV

    Jellyfin in Docker: A Self-Hosted Media Server for Movies and TV

    Jellyfin is the self-hosted answer to Netflix and Plex: an open-source media server that plays your own movies and TV from your own disk, with no subscription and no upload limits. It is also one of the most misunderstood services in the self-hosting world, because the difference between a smooth setup and a constant transcoding fight comes down to a few decisions you make before the first movie. This guide walks through the Docker Compose setup we run in the Chikewa lab, what the hardware actually has to do, and the configuration choices that matter.

    Beginner · 12 min · Docker

    What is Jellyfin?

    Jellyfin streams video, music, photos and podcasts from folders on your server to any device on your network or on the internet. It was forked from the old free version of Plex in 2018, and unlike Plex it is fully open source: no premium tier, no server-side limits, no account required. The server does the heavy lifting (metadata scraping, transcoding, photo optimization) and thin clients on phones, TVs, browsers and media players do the playback.

    Two properties make it a good first “big” self-hosted service. First, it is a single container with no database dependency: the only thing you point it at is a folder of media files. Second, its default configuration is genuinely reasonable, which means the gap between “it started” and “it is actually usable” is small. The things that do trip people up are documented below, because they tripped us.

    Requirements

    Anything that runs Docker runs Jellyfin. The realistic floor is the same as the rest of the stack: 2 GB of RAM and a few hundred MB of disk for the configuration database. The real constraint is not starting the server, it is transcoding. If a client asks for a format the hardware cannot decode natively, Jellyfin re-encodes it on the CPU, and that is where underpowered machines start stuttering.

    Our lab machine is a 4-core i5-6500T with 16 GB of RAM and an NVMe disk. It idles Jellyfin at around 240 MiB of RAM and can comfortably handle direct play for the family and one light transcode at a time. If you are buying hardware specifically for a media server, read our hardware guide before you buy.

    The Compose File

    The complete file, exactly as it runs in the lab:

    services:
      jellyfin:
        image: jellyfin/jellyfin:latest
        container_name: jellyfin
        ports:
          - "127.0.0.1:8096:8096"
        volumes:
          - jellyfin-config:/config
          - ./media:/media
        environment:
          - PUID=1000
          - PGID=1000
          - TZ=Europe/London
        restart: unless-stopped
    
    volumes:
      jellyfin-config:
    

    Three decisions in this file are worth understanding.

    The port binding

    Notice the port is published as 127.0.0.1:8096:8096, not 8096:8096. That single change makes the difference between “my media server is reachable from the living room” and “my media server is reachable from the entire internet”. Bound to loopback, Jellyfin answers only on the host itself; you reach it from other devices through a reverse proxy or a private network, and we cover both in our security guide. We verified with ss -tlnp that the socket listens on 127.0.0.1:8096 only.

    The volumes

    Two things need to survive container recreation. jellyfin-config is a named volume holding the SQLite database, plugin state and user settings — losing it means re-creating users and re-scanning the library. ./media is your actual movie and TV folder, mounted read-only in spirit (Jellyfin never needs to write to your media, only read it). Keep media on the fastest disk you have: scan times and seek performance for random playback both depend on it.

    The environment variables

    PUID and PGID make the container run as your regular user instead of root, which matters if you ever mount media from a share with restrictive permissions. TZ keeps the activity log and the trickplay schedule sane. Nothing else is required.

    First Run

    Start it and watch the logs:

    docker compose up -d
    docker compose logs -f jellyfin
    

    The first boot takes noticeably longer than the other services in this series. On our machine the image pull, the database migrations and the plugin load completed in under a minute, and the log ended with Core startup complete. The health endpoint is a good objective check:

    curl http://localhost:8096/health
    # Healthy
    

    Open http://localhost:8096 (or through your proxy) and create the admin account. The setup wizard then asks where your media lives: the path is /media, because that is the mount point inside the container, not the host path. This is the most common first-run mistake — entering the host path makes Jellyfin scan an empty directory and you get a server that runs perfectly with zero content.

    Library Setup

    Add your first library, choose “TV Shows” or “Movies”, point it at the right subfolder of /media and let it scan. Jellyfin pulls metadata from TMDb by default, which works well for English content; the first scan of a medium library (a few hundred titles) took a few minutes in our test, downloading posters and fan art for everything.

    Two settings pay for themselves quickly. Under the library, enable save image assets to the content folder if you want the metadata to survive a config loss. And check realtime monitoring so new files are picked up without a manual rescan.

    Hardware Transcoding

    Direct play means the client decodes the file as-is: cheap, fast, and what you want 95% of the time. Transcoding happens when a client cannot play the source format. Our lab image ships with ffmpeg 7.1.4, and the encoder list includes h264_qsv, av1_qsv and hevc_qsv — Intel Quick Sync. The image also bundles the i965 driver, and the host exposes /dev/dri when the CPU has an Intel iGPU, so Quick Sync transcoding is available out of the box on most mini PC hardware.

    To use it, pass the device through and enable hardware transcoding in the admin dashboard (Playback → Transcoding). The compose addition is:

        devices:
          - /dev/dri:/dev/dri
    

    Without the iGPU, transcoding falls back to the CPU, which on a 4-core i5 handles a single 1080p encode but will struggle with several. If transcoding quality matters to you, that is a hardware conversation, not a configuration one.

    A pitfall we hit: you cannot exec ffmpeg

    Our first attempt to test the transcoder was docker exec jellyfin ffmpeg -hwaccels, which failed confusingly. The Jellyfin entrypoint intercepts every argument and hands it to the .NET server, so arbitrary commands never reach a shell. To inspect the bundled ffmpeg you need a separate container from the same image, or check the running server’s logs, which print the full encoder and hwaccel list at startup. The list we captured is in our hardware guide, if you want to compare.

    First Login and Daily Use

    Once the library is scanned, add a user per household member (the admin account is a fine user too, but separate users keep watch states and parental controls clean). Install a client: the browser works everywhere, the Android and iOS apps are good, and most smart TVs either run a native app or play through a browser. From the TV we verified direct play of 1080p MKV without a single transcode, which is the whole point.

    Remote access is the next natural step. Because we bound the port to loopback, the two clean options are a reverse proxy with authentication or a private network like Tailscale; the security guide covers the decision. Do not solve “I want to watch at my parents’ house” by republishing port 8096 on 0.0.0.0.

    Updating

    Jellyfin images move fast. The safe update:

    docker compose pull
    docker compose up -d
    docker compose logs -f jellyfin   # watch for "Core startup complete"
    

    Configuration and the database live in the named volume, so updates are non-destructive. One thing to know: after a major version bump the logs show a batch of Entity Framework migration warnings on the first boot. In our test run they appeared, the migrations applied, and the server came up cleanly. They look alarming and are cosmetic; what you should actually watch for is a missing Core startup complete.

    Troubleshooting

    The server runs but the library is empty

    Nine times out of ten this is the host-path-instead-of-container-path mistake from the first run. The media folder inside the container is /media. Check the library path in the admin dashboard, not the compose file.

    Playback stutters on one device only

    That device is transcoding when it should direct play. Open the activity log during playback: it records every transcode with the codec and resolution. If you see transcodes for a format your client should support, the file’s codec or profile is outside the client’s native range — re-encode that file, or accept the transcode.

    High CPU during scans

    Large initial scans and photo optimization are CPU-heavy by design and settle down. If CPU stays high with no scans running, check the trickplay generation schedule (it runs daily and is optional) and the number of concurrent transcodes in the playback settings.

    Resource Usage

    Measured in the lab, steady state with a 200-title library and no active playback: 236 MiB RAM, negligible CPU. The disk footprint of the config volume is tens of MB; the media is, of course, your own. Add roughly 0.5–1 GB per active hardware transcode, more for CPU transcoding.

    FAQ

    Is Jellyfin legal?

    Jellyfin is a legal, open-source application. What you put on the server is your responsibility, exactly as with any storage device you own.

    Can it replace Plex for a household?

    For the core job — stream my library to my devices — yes, and without a premium subscription. You give up some of Plex’ polished remote streaming and its ecosystem of third-party integrations. For most home setups that trade is a win.

    What about music?

    Jellyfin plays music, but if music is the priority, a dedicated server like Navidrome uses less RAM and has a better mobile experience. We run both in the lab.

    Tested on:

    OSDebian 12
    Docker29.7.2
    Hardware4-core i5-6500T / 16 GB RAM / NVMe
    SoftwareJellyfin 10.11.11 (ffmpeg 7.1.4)

    Last tested: 23 August 2026

  • The Self-Hosting Starter Guide: From Zero to a Working Home Server

    The Self-Hosting Starter Guide: From Zero to a Working Home Server

    Beginner · 10 min · Linux · Docker

    Tested on:

    OS Ubuntu 24.04 LTS (Debian 12 works too)
    Docker 29
    Hardware 4-core x86, 16 GB RAM
    Software Starter stack (Miniflux, PostgreSQL 16, Navidrome, DokuWiki)

    Last tested: 22 August 2026

    You do not need a rack of servers to start self-hosting. You need one machine, Docker, and a few well-chosen applications. This guide takes you from a blank Linux machine to a working home server with a working RSS reader, a personal music server, and a private wiki — every file tested on real hardware.

    Why self-host at all?

    Every month you pay for another subscription, you are renting someone else’s computer. Self-hosting flips the model: you buy the hardware once (or reuse what you already have) and run the software yourself. The benefits, in order of how they matter to real users:

    • Your data stays yours. Photos, music, notes, and feeds live on your disk, on your terms, with your backup strategy.
    • No subscription fatigue. A home server running four services costs a few pounds per month in electricity, not four monthly fees that keep going up.
    • Privacy by architecture. Your reading habits, your playlists, and your notes never pass through a company that may change its policies tomorrow.
    • You actually learn infrastructure. Networking, containers, reverse proxies, backups — the skills transfer directly to paid work.

    The honest trade-offs: you are the IT department now. Updates, outages, and security patches are your responsibility. A home server also needs a real IP address or a workaround (Tailscale, Cloudflare Tunnel) to be reached from outside your house — we cover that in the security series.

    Step 1: Choose your hardware

    You have three sensible starting points, depending on budget:

    Option A: Repurpose an old PC (free)

    Any x86 machine from roughly the last decade works as a starter server. The practical minimum: 8 GB of RAM, a solid-state drive (even a cheap SATA SSD makes a huge difference), and a power supply you trust. Old desktops are the classic choice, and a single 60 W machine idling costs roughly £15–25 per year at UK rates.

    Option B: Raspberry Pi 5 (around £80–100)

    The 16 GB model is the sweet spot for a starter stack: it runs the apps in this guide comfortably, idles around 5 W, and fits on a shelf. Use a quality case with active cooling and a 2.5″ SATA SSD via the HAT if you plan to store media — the Pi’s microSD slot will die on you under sustained writes.

    Option C: Used mini PC (around £150–250)

    Used Intel NUCs, Dell OptiPlexes, and HP Elites from office clearances offer x86 performance at a fraction of the price of new hardware. This is the best bang-per-pound if you want headroom for media transcoding later.

    Step 2: Install an operating system

    For a dedicated server, use a minimal Linux install rather than a desktop:

    • Debian 12 (bookworm): the safest default. Minimal install, no desktop, extremely stable, huge community.
    • Ubuntu Server 24.04 LTS: the friendlier choice if you want the most tutorials to match. Also an excellent default.
    • Truenas Scale or Proxmox: skip these for your first server. They add virtualisation and ZFS, which are powerful but premature until you know what you are running.

    During installation: give the machine a fixed IP on your LAN (or reserve one in your router’s DHCP table), and set a hostname like server. You will not want to remember a changing IP.

    Step 3: Install Docker

    Docker packages software into containers: isolated environments that start in seconds, take up only what they use, and are identical on any Linux machine. This is what makes self-hosting actually manageable instead of a tangle of system packages.

    On Debian or Ubuntu, the official install is a few lines:

    sudo apt-get update
    sudo apt-get install -y ca-certificates curl
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \ https://download.docker.com/linux/$(. /etc/os-release && echo $ID) \ $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    sudo apt-get update
    sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

    Then add your user to the docker group so you stop typing sudo:

    sudo usermod -aG docker $USER
    # log out and back in, then verify:
    docker compose version

    If that command prints a version number, you are ready.

    Step 4: Pick your first stack

    This is the part most guides get wrong. They hand you a list of forty apps and you spend two weeks configuring Sonarr and Prowlarr before you have used anything. Start with three services that you will actually touch every day:

    App What it does Why first
    Miniflux RSS reader — aggregates every feed you follow Instant daily value; your reading no longer depends on an algorithm
    Navidrome Personal music server — streams your library to any device One folder, one app, works with the official Substreamer app
    DokuWiki Plain-text wiki for notes and documentation Zero-friction writing; your notes are files on disk, not a proprietary format

    Each of these has a dedicated guide on this site with the exact compose file, tested setup steps, and the real errors we hit along the way:

    Step 5: Run the stack

    Everything below was verified on a 4-core / 16 GB Ubuntu machine with Docker 29. Create a project directory:

    mkdir -p ~/stacks/starter/music && cd ~/stacks/starter

    Drop in a docker-compose.yml with the three services (full annotated files in each linked guide):

    services: miniflux: image: miniflux/miniflux:latest container_name: miniflux environment: DATABASE_URL: postgres://miniflux:secret@miniflux-db/miniflux?sslmode=disable BASE_URL: http://localhost:8082 ports: - "8082:8080" depends_on: - miniflux-db restart: unless-stopped miniflux-db: image: postgres:16-alpine container_name: miniflux-db environment: POSTGRES_USER: miniflux POSTGRES_PASSWORD: secret POSTGRES_DB: miniflux volumes: - miniflux_db:/var/lib/postgresql/data restart: unless-stopped navidrome: image: deluan/navidrome:latest container_name: navidrome ports: - "4533:4533" environment: ND_SCANSCHEDULE: 1h ND_LOGLEVEL: info volumes: - ./music:/music:ro - navidrome_data:/data restart: unless-stopped dokuwiki: image: dokuwiki/dokuwiki:stable container_name: dokuwiki ports: - "8081:80" volumes: - dokuwiki_data:/dokuwiki/data - dokuwiki_conf:/dokuwiki/conf restart: unless-stopped volumes: miniflux_db: navidrome_data: dokuwiki_data: dokuwiki_conf:

    Then start it:

    docker compose up -d
    docker compose ps

    All four containers should show Up. The ports: Miniflux on :8082, DokuWiki on :8081, Navidrome on :4533. Open them from your laptop on the same network: http://SERVER_IP:8082, and so on.

    Step 6: The three first-run tasks

    1. Miniflux: first visit asks you to create the admin account. Then add feeds — start with the 10 you actually read. If the container keeps restarting with pq: SSL is not enabled on the server, your DATABASE_URL is missing ?sslmode=disable (full fix in the Miniflux guide).
    2. Navidrome: put MP3/FLAC files in the music/ folder, then create your first account at :4533. It scans the library on first login. The official mobile app (Substreamer) pairs in one minute.
    3. DokuWiki: first visit runs a tiny config wizard (admin login, language). That is the whole setup.

    Measured resource usage

    Because we run this stack, here is what it actually costs, measured with docker stats after a day of normal use (RSS polling, a music session, some wiki edits):

    Container Idle RAM Idle CPU
    miniflux 17 MiB ~0%
    miniflux-db (Postgres) 38 MiB ~0%
    navidrome 26 MiB ~0%
    dokuwiki 25 MiB ~0%

    Total: about 106 MiB of RAM for a full starter stack. A Raspberry Pi 5 with 4 GB has 40× the headroom this needs. The real cost of this stack is the electricity of the machine it lives on — which you were already paying.

    What comes next

    Once these three are boring (the goal), the natural expansion path is:

    • Nextcloud or Immich for photos and files (see the NAS & Media section)
    • AdGuard Home for network-wide ad and tracker blocking (Security & Networking)
    • A reverse proxy (Caddy or Nginx Proxy Manager) plus Tailscale, so the stack is reachable from anywhere without opening ports on your router — this is the single highest-value upgrade after the starter stack, and we cover it in the security series
    • Monitoring: Uptime Kuma, so your server tells you it is down instead of you finding out

    Frequently asked questions

    Is self-hosting safe if I am not a security expert?

    Yes, with discipline: keep Docker updated, use a reverse proxy with TLS instead of exposing ports, and do not expose admin interfaces directly to the internet. The starter stack above is LAN-only, which is the safe default. Our Security & Networking guides cover hardening step by step.

    Docker or Kubernetes?

    Docker Compose. Kubernetes on a home server is solving a problem you do not have. You will use 90% of what Compose gives you for 10% of the complexity.

    Can I run this on a VPS instead of at home?

    The same compose file runs unchanged on a VPS — that is part of Docker’s point. A VPS is a fine starting point if your home connection is bad or your ISP blocks inbound connections.

    What about the initial hardware cost?

    If you already own a spare PC, the marginal cost is electricity (a few pounds a month). A Raspberry Pi 5 path is around £100–150 all-in. After that, most services are free software. That is the whole pitch: one small fixed cost instead of an open-ended subscription stack.