Case study · Self-hosted media platform

The problem, the limitations, the infrastructure, and what I learned.

← All projects
SELF-HOSTED MEDIA PLATFORM · active

HOMELAB MEDIA

A single Docker host that requests, organizes, compresses, and streams a personal media library, reachable from anywhere through a Cloudflare Tunnel with no open ports.

dockercloudflare-tunnelwireguardquick-syncpython
OUTCOME

Hands-off operation: cron jobs throttle searches, a watchdog restarts a stalled service, and a Slack digest reports health, usage, and weekly activity.

// problem

Run a full media pipeline on one box without exposing the home network, without babysitting stuck jobs, and without filling the disks.

// limitations

  • One host, consumer hardware.
  • 1080p playback target for remote viewers.
  • Every secret lives in an untracked .env. The repo holds only placeholders.

// infrastructure used

  • Docker Compose stack split into a main services file and a separate playback stack.
  • Outbound-only Cloudflare Tunnel publishes the two public apps. No inbound firewall rules.
  • Download traffic is pinned inside a WireGuard VPN network namespace.
  • Tdarr flows convert the library to HEVC on Intel Quick Sync, with worker limits by time of day.
  • A strict HAProxy socket proxy limits which Docker API calls the health tooling can make.

// lessons learned

  1. 01
    A VM disk was on a failing USB stick.

    Checksum errors appeared before anything visibly broke. I copied the disk to NVMe, zero-filled the bad blocks, and now monitor storage health instead of assuming it.

  2. 02
    Hardware transcoding vanished after a rebuild.

    The transcoder lost its GPU device, so every HEVC job failed quietly. A test encode is now part of checking any rebuild.

  3. 03
    A one-letter typo in an environment variable.

    The variable was empty, so a container got a random IP instead of its planned one. I now render the final Compose config before deploying.

  4. 04
    Files with no extension stalled imports.

    A post-processing script now adds the right extension from the file contents. Its first setup loaded but never ran, so I verify that hooks fire, not just that they load.

  5. 05
    Removing a network client turned off IP forwarding.

    All container networking dropped for two minutes. Kernel settings the stack depends on are now pinned.

View source ↗