This is the whole home media server as it stands at the end of October 2026: fourteen containers in one Docker Compose file on a 2014 PC, plus a few pieces in AWS. It started as Jellyfin replacing Plex, went behind HTTPS, got tuned for its old hardware, and this month grew monitoring, backups, a check from outside the house and a rebuilt download stack. Below is what each piece does, why it's there, what it costs, and the two things that broke in the same week.

What it doesn't fix, up front: it's still one box. The four media drives have no parity, so a dead drive loses its files; they share one USB 2.0 link, which is the weakest part of the whole thing; and after a power cut, one router setting still needs a human. Each is covered below.

The stack at a glance

flowchart LR
    viewer([Viewer]) -->|HTTPS 443| caddy[Caddy]
    caddy --> jellyfin[Jellyfin]
    caddy --> seerr[Seerr<br/>requests]
    seerr --> sonarr[Sonarr / Radarr]
    prowlarr[Prowlarr<br/>+ FlareSolverr] --> sonarr
    sonarr --> qbit[qBittorrent]
    qbit --> pool[(mergerfs pool<br/>4 drives)]
    sonarr -->|rename, file| pool
    pool -->|read-only| jellyfin
    bazarr[Bazarr<br/>subtitles] --> pool
    lambda[[AWS Lambda<br/>outside check]] -.->|every 5 min| caddy
Piece What it does Reachable from
Jellyfin Plays the library, transcoding on the CPU's integrated graphics The internet, through Caddy
Caddy HTTPS for Jellyfin and Seerr, with Let's Encrypt certificates it renews itself Ports 80 and 443, the only ones forwarded
dns-updater Keeps the DNS records on the home IP (Route 53, with IAM Roles Anywhere) Nothing; it only calls out
Seerr A request page: Jellyfin users ask for shows and films The internet, on its own name
Sonarr, Radarr Follow shows and films, pick releases, rename and file them Home only
Prowlarr, FlareSolverr The indexers in one place; a headless browser for the ones behind Cloudflare Home only
qBittorrent Downloads Home only, no incoming port
Bazarr English subtitles for what Sonarr and Radarr add Home only
Recyclarr Sonarr's and Radarr's quality settings, from a config file in the repo Nothing
Beszel, Scrutiny Server monitoring; drive health Home only
Outside check (Lambda) Opens the site from AWS every five minutes and emails when that changes Runs in AWS
Backup, weekly report restic to a NAS at 2:00 AM; a summary email on Monday mornings Nothing

Everything that runs in Docker is in one Compose file, started by one systemd unit, with every image pinned to a version. The settings live in a folder per app in the home directory, backed up nightly, and the repo's README is the manual for rebuilding it.

The hardware

CPU Intel i5-4690K (Haswell, 4 cores, 2014), integrated graphics, no graphics card
Memory 11 GB
System disk A 1 TB, 5,400 rpm laptop drive
Media Four WD Red drives (three 6 TB, one 8 TB) in a four-bay USB 2.0 enclosure, pooled with mergerfs: 24 TB, 82% full
Library 944 films, 221 series (6,414 episodes), 2,397 songs, 64 books

The fourteen containers use about 1.5 GB of memory between them. Jellyfin is the largest at about 350 MB; the download apps are 85-180 MB each.

The drives are pooled with mergerfs, which shows the four drives' media folders as one tree without reformatting anything. It isn't RAID: each file lives whole on one drive, so a dead drive loses only its own files and the rest stay readable.

Watching: Jellyfin and Caddy

Jellyfin mounts the pool read-only: nothing it does can change the library. Caddy sits in front with HTTPS, and a small container keeps the DNS records pointing at the home IP, so nothing depends on the router's dynamic DNS (which broke Let's Encrypt).

The hardware's limit is HEVC. Haswell's integrated graphics decode and encode H.264 in hardware (279 fps at 22% CPU for 1080p) but don't know HEVC at all, so 4K HDR has to direct play: a device that can't play it gets a CPU transcode that only just keeps up and looks washed out. Transcoding and the measurements are covered separately. That limit shaped the download settings below.

Getting things: the download stack

Sonarr (TV) and Radarr (films) are the core: you tell them what you want, they search indexers (sites that list what's available to download) through Prowlarr, hand the best match to qBittorrent, and when it finishes they rename the file and file it into the library, then tell Jellyfin to rescan that one folder. Seerr puts a friendly front on it, so anyone with a Jellyfin account can ask for something without touching Sonarr or Radarr. Only download what you have the right to.

Why the last one made everything slow

This isn't the first download stack on this server. The first, in 2025, made the whole machine crawl and playback stutter, and it was taken down. Its config explained why: it downloaded to the home directory, on the slow laptop drive that also held the operating system and Jellyfin's database. A torrent writes thousands of small pieces in no particular order; every database read queued behind them. 112 GB of its old downloads were still sitting there.

The rebuilt one is laid out to avoid that:

  • Downloads go to the pool, not the system disk, in a folder no Jellyfin library includes.
  • Every container sees the pool at the same path (/media), so a path in Sonarr is the same path in qBittorrent and Jellyfin, with no mapping. Sonarr and Radarr get the pool as one writable mount, so a finished download can be hardlinked into the library (instant, no extra space) when it's on the same drive; otherwise it's copied. Jellyfin still has it read-only.
  • qBittorrent is capped at 12 MiB/s down and 1 MiB/s up, two downloads at a time, because the four drives share a USB 2.0 link of about 35-40 MB/s. A Samba copy on its own was enough to leave processes waiting on the disks 57% of the time.
  • Sonarr and Radarr don't analyse files or rescan folders on their own, and seeding stops at a 1:1 ratio or a day, so the drives aren't busy for long after a download.

Quality: 1080p, and H.264 first

Recyclarr applies the TRaSH Guides' quality profiles to Sonarr and Radarr from a config file, so the rules (which releases to prefer, which to refuse, size limits) are versioned instead of clicked together. Their 1080p profiles penalize HEVC (x265) at 1080p, which happens to suit this hardware: the integrated graphics can transcode H.264 but not HEVC. The first test download picked an H.264 web release with a score of 1,720.

Everything new is 1080p. The library already keeps 4K and 1080p versions side by side, and Sonarr and Radarr track only one file per episode or film, so 4K versions stay a manual job. Shows already in the library are added with "Monitor: Future Episodes", so Sonarr never replaces an existing 4K file.

Things to know about the *arr apps

  • Some public indexers sit behind Cloudflare and fail in Prowlarr with "blocked by CloudFlare Protection". FlareSolverr is a headless browser that gets past the check; Prowlarr only sends tagged indexers through it.
  • Bazarr 1.6 renamed its menus: "Settings > General" is now "Settings > Application", and each settings page has its own Save button that's easy to miss. Its Podnapisi provider is gone; OpenSubtitles.com (free account), Gestdown (TV) and YIFY Subtitles (films) cover English, and its "Embedded Subtitles" provider counts the subtitles already inside a file, so it doesn't download another copy.
  • Jellyseerr is now Seerr. Jellyseerr and Overseerr have merged into one project; the official image is ghcr.io/seerr-team/seerr.
  • Most of the settings can be set through each app's API. Sonarr, Radarr, Prowlarr, Bazarr and Seerr all take an API key from their own config file, which made setting them up repeatable instead of an afternoon of clicking.

Exposure

Only Seerr was added to the internet, on its own name behind the same Caddy (a CNAME to the Jellyfin name, so it follows the same IP). It signs people in with their Jellyfin accounts; its own local sign-in is off. Everything else is on the home network only, qBittorrent has no incoming port, and its UPnP is off so it can't open one on the router by itself. There's no VPN, so a torrent's peers see the home IP.

Keeping it running

Tool Watches How you hear
Beszel CPU, memory, temperatures, each drive's space, each container Email alerts
Scrutiny Each drive's SMART data against Backblaze's failure rates Email on a failing drive
Outside check The site, from AWS, every five minutes Email when the result changes
Weekly report Viewing per user, load, space, drive health, the last backup Monday-morning email
Backup restic to a NAS: settings, databases, small media Its status in the weekly report

The outside check exists because everything else runs on the server, and a server can't report that the house has lost power. It's a small Lambda that opens the site the way a viewer would and sorts the result: fine, certificate about to expire, Jellyfin down, something else answering, the house offline, or DNS broken. It emails only when that changes and the new result holds for two checks, so a reboot sends nothing. It costs nothing (inside Lambda's free tier), and its code deploys from GitHub Actions without stored AWS keys.

The backup is restic to a WD My Cloud Home on the same network, every night at 2:00 AM, in about five minutes: Jellyfin's database and settings, every app's settings, and the music, books and audiobooks (about 60 GB). Films and TV (20 TB) aren't backed up; the weekly report attaches an inventory of which drive holds what, so a dead drive's contents can be found again. Databases are copied from each app's own backup zip, never mid-write.

What broke this week

A power cut opened the router's login page to the internet

After a short power cut on October 29, 2026, the site stopped working: both from home and from outside, ports 80 and 443 got the router's own login page, with a self-signed certificate. The port forwards were listed and enabled, and the router's remote management was off. Switching the two forwarding rules off and on fixed it. The best explanation is a firmware bug triggered when the router finishes booting before the server is back: it's only a theory, and the firmware's release notes are too vague to say whether an update fixes it.

For about four and a half hours, anyone who opened the address reached the router's login page. The outside check came out of this: it now sees exactly that case (a certificate that isn't the site's) and emails within about ten minutes, with the fix in the email.

The first download hit the USB enclosure's weak spot

The first torrent the new stack downloaded stopped at 4.35 of 6.89 GB with an I/O error. The kernel log told the story: write commands to one drive timed out, the kernel reset the USB device, the writes in flight were lost, and ext4 stopped accepting writes on that drive to protect it.

The drive was fine: no reallocated, pending or uncorrectable sectors, and no cable errors. The enclosure uses ASMedia USB bridges with UAS, a faster USB storage protocol that queues many commands at once, and that combination has a reputation for stalling under heavy writes. Streaming (reads) and the occasional large, front-to-back copy hadn't hit it; a torrent, writing thousands of scattered pieces with commands queued up, did, about ten minutes in.

The fix took two minutes of checking: stop everything, unmount the drive, e2fsck -f (it replayed the journal and fixed one count), mount it again. The standing fix, if it comes back, is to make the kernel use the older usb-storage driver for that bridge, one command at a time, at some cost in speed:

# /etc/modprobe.d/disable-uas.conf: the enclosure's USB ID, from lsusb
options usb-storage quirks=174c:235c:u

followed by sudo update-initramfs -u and a reboot. I'm waiting to see whether it recurs before giving up the speed.

Smaller ones

  • Scrutiny logs its whole SMTP URL, password included, every time it sends an email, so the first email key ended up in a log and had to be rotated.
  • Seerr couldn't match 31 films to a film database ID, so it couldn't tell they were already in the library. Most were typos in folder names ("Anabelle", "Captain Philips"), wrong years or release-site names. Jellyfin's Identify, given the right title and year, fixed them without touching the files; a few needed the running time and audio language to tell candidates apart, and five turned out to be TV specials and moved to the TV library.

What it costs

  • Hardware: nothing new; the PC and drives were already there.
  • AWS: a few Route 53 records in an existing hosted zone, and SES and Lambda within their free allowances.
  • Time: most of it went into the layout, not installing things. Getting paths, hardlinks and disk load right is what separates a stack that runs quietly from the one that made everything stutter.

What this doesn't fix

  • No parity. A dead drive loses its files. SnapRAID (parity on an extra drive at least as big as the largest) is the likely fix, once two drives being repaired are back.
  • The USB 2.0 enclosure is the bottleneck for everything: one fast copy fills it, and its UAS bridges are the reason a download failed. A faster enclosure, or drives on SATA, would matter more than any setting above.
  • A power cut can still need a human. The outside check makes it a ten-minute problem instead of a four-hour one, but the router's forwards still have to be toggled by hand if the bug repeats.
  • No off-site backup. The NAS is in the same house; a fire or theft takes both. That risk is accepted.
  • HEVC and HDR transcoding needs better hardware: an Intel Arc A310 would add HEVC, AV1 and tone mapping through the same driver.
  • No VPN on the downloads, for now.

Would I build it the same way again? Mostly. The one-file Compose stack, pinned versions and a README that can rebuild it have all paid off already. What I'd change is the enclosure.