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.