My Home Media Server: A Raspberry Pi 4 for Compute, a Synology NAS for Storage

My home media server is a Raspberry Pi 4 named uther. It runs Plex, the entire *arr stack, qBittorrent, a request manager, a reverse proxy, Vaultwarden as a password manager, and this very blog — all in Docker Compose. It sips around five watts at idle, more like seven during peak indexing, and the most expensive part of the whole setup is a separate Synology NAS sitting on the same shelf doing nothing but being a quiet, reliable hard drive over NFS.

This is the honest tour. Architecture, wiring, the small decisions that make the difference between runs fine and runs for years untouched.

The architecture in one diagram

flowchart TD
    NAS[("Synology NAS
3.6 TB · NFSv4")] subgraph UTHER ["uther — Raspberry Pi 4 · Debian 13"] direction TB MEDIA["Media
Plex · Radarr · Sonarr · Prowlarr
Bazarr · Ombi · qBittorrent"] WEB["Edge
nginx-proxy-manager · cloudflared"] APPS["Apps
vaultwarden · danilo (this blog)"] end NET(["Public Internet"]) NAS -- "NFS · fstab" --> UTHER UTHER -- "Cloudflare Tunnel" --> NET

Two boxes. One does compute, the other does storage. They never get confused about which is which.

Why split compute and storage

The lazy default for a home media setup is to put everything on the NAS. Synology’s “Container Manager” runs Docker, Plex has a native package, the *arr apps have community Synology packages. It works. But:

  1. The NAS is the part you want to be boring. Storage devices live longer when they do one thing — read and write blocks. Every extra service running on the NAS is another reason to reboot it, another process competing for IO, another security surface.
  2. The Pi is the part you want to be cheap. Five watts of compute for a media server that mostly sits there waiting for new releases is exactly the right amount of compute. A second-hand Pi 4 4GB is $40 in 2026.
  3. Replacing either one is easy. If the Pi dies, I buy another Pi, restore the compose files and container configs from backup, apt install docker.io, and we’re back. If the Synology dies, the Pi notices that NFS is gone and the containers gracefully stop. Both halves are independently replaceable.

Coupling everything to one device is the homelab equivalent of putting all your services on one big Kubernetes cluster — it works until the day it doesn’t.

The storage: NFS in /etc/fstab, not Docker volumes

The Pi sees the Synology as two NFS mounts that come up at boot. The relevant lines in /etc/fstab:

nas:/volume1/qBittorrent  /qbittorrent  nfs  defaults  0 0
nas:/volume1/Plex         /plex         nfs  defaults  0 0

That’s it. No Docker volume drivers, no per-container NFS configuration, no third-party CSI nonsense. The OS handles the mounts, the containers just see /plex/Movies, /plex/TV, and /qbittorrent as ordinary host directories — bind-mounted in:

volumes:
  - /radarr/data:/config
  - /plex/Movies:/movies
  - /qbittorrent:/downloads

If NFS hiccups for a moment, every container that’s mid-write notices and recovers when the mount comes back. The kernel’s NFS client is the right layer for this. Pushing it into Docker would be re-inventing what the OS already does.

The *arr stack, end to end

For anyone new to this corner of self-hosting, the *arr stack is a small ecosystem of open-source apps that automate the boring parts of a media library:

ComponentRole
ProwlarrIndexer manager — talks to public/private trackers, hands results to Radarr and Sonarr
RadarrMovies — adds to your watchlist, finds the right release, hands it to qBittorrent, renames/moves it on completion
SonarrThe same as Radarr but for TV series, episode-aware
qBittorrentThe actual download client
BazarrSubtitles — watches your library and fetches matching subs in your preferred languages
OmbiRequest frontend — non-technical users ask for content; Ombi pushes the request into Sonarr/Radarr
PlexThe media server — indexes the finished library and streams it to clients

The loop, in one sentence: Ombi receives a request → Sonarr/Radarr ask Prowlarr where to find it → qBittorrent downloads to the Synology over NFS → Sonarr/Radarr rename and move the file to the Plex folder (still on the Synology) → Plex notices it → Bazarr finds subtitles.

The whole thing runs on a Raspberry Pi because none of those steps need real CPU. Prowlarr is glorified HTTP. Radarr/Sonarr are bookkeeping apps. qBittorrent is bandwidth-bound, and the Pi’s gigabit Ethernet is the bottleneck, not its CPU. Plex on the Pi handles direct play — where the client device decodes the file natively and the server just streams bytes. That’s 95% of my playback.

The remaining 5% — where a phone wants a transcoded version of a 4K HEVC file on cellular — Plex politely tells the client “this device doesn’t support that format” and the client (or I) pick a different quality. Hardware transcoding on the Pi 4 is technically possible via /dev/dri but it’s not the right tool for that job, and I’ve never missed it.

linuxserver.io images everywhere

Every container in this stack uses an image from linuxserver.io. The reasons matter:

  • First-class ARM64 multi-arch builds. Every linuxserver image ships native arm64 next to amd64. No emulation, no QEMU, no performance penalty on a Pi.
  • Consistent PUID/PGID environment variables across every image. One conventions to learn, applied uniformly.
  • Sane defaults for paths and volumes. Their docs all assume /config for app state and bind-mount targets for media — exactly how I have it wired.
  • A predictable update cadence. New upstream releases land in the image within days, not weeks.

A representative compose file from this stack — the Radarr one — is ten lines and contains nothing surprising:

services:
  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    network_mode: host
    environment:
      - PUID=0
      - PGID=0
      - TZ=America/Sao_Paulo
    volumes:
      - /radarr/data:/config
      - /plex/Movies:/movies
      - /qbittorrent:/downloads
    restart: unless-stopped

Sonarr, Prowlarr, Bazarr and Plex follow the same shape. One service per directory, one compose file per service. No giant 200-line monolith, no depends_on: chains, no service mesh. The containers find each other on localhost because they all use network_mode: host.

External access without opening ports

This is the part most home-server tutorials get wrong. They tell you to forward 32400 on your router to the Pi for Plex remote access, maybe 80/443 for the reverse proxy. Then they tell you to configure fail2ban because you just exposed your home network to the internet.

I do none of that. There are zero inbound ports open on my home router. External access happens through a Cloudflare Tunnel: a small container (cloudflare/cloudflared) maintains an outbound connection to Cloudflare’s edge, and Cloudflare routes incoming requests through that tunnel back to the Pi.

Internally, an nginx-proxy-manager container handles the TLS termination and routing to each backend. Plex on :32400, this blog on :1313, Vaultwarden on its port, all multiplexed behind sensible hostnames.

The combination — Cloudflare Tunnel outward, nginx-proxy-manager inward — gives me HTTPS everywhere, no port forwards, no certificates to renew manually, and Cloudflare’s network in front of every request. For a home media server in 2026, this is the only sane way to expose anything.

Plex in 2026 — and why I’m still on it

I can’t write this post without addressing the elephant in the room: Plex’s 2026 paywall changes. As of March 2026, remote streaming is no longer free for the server owner or the viewer. You need either:

  • A Plex Pass (server owner): $6.99/month, $69.99/year, or $249.99 lifetime — going to $749.99 on July 1, 2026.
  • A Remote Watch Pass (viewers): $1.99/month, going to $2.99/month on June 1.

Reasonable people are upset, and the timing of the lifetime price hike feels like a slow-motion farewell to the free tier. Jellyfin is right there — free, open-source, no telemetry, no cloud auth, no paywall.

I’m staying on Plex, with eyes open:

  • I have a Plex Pass from years ago, before the changes.
  • The non-technical family members who use my server know the Plex apps on their TVs. “Switch to Jellyfin” is a non-trivial conversation multiplied by every TV in every household.
  • Plex’s apps on Roku, Apple TV, Samsung, LG, and game consoles remain more polished than Jellyfin’s. That gap is closing — and I’ll happily reevaluate when it closes — but in May 2026 it’s still real.

If I were starting from zero today, I’d give Jellyfin a much harder look. But this stack has been running for years and the cost of switching horses is higher than the cost of one already-paid Plex Pass. Honesty matters more than purity here.

Updates, by script — and manually

I deliberately don’t run any auto-update mechanism. Containers updating themselves while I sleep is a recipe for “why doesn’t Plex work this morning.”

What I do have is a small shell script that pulls the latest images and recycles each compose project in turn. It’s a script, not a cron job — I run it by hand, at the keyboard, watching it happen. If anything breaks I know exactly when, and the cure is docker compose up -d with the previous tag. It takes a few minutes once every couple of weeks. That’s the right cadence for a home server: scripted enough to be repeatable, manual enough to stay safe.

The honest bottom line

A Raspberry Pi 4 has no business running a 12-container production-feeling stack with multiple users, external access, automated media management and a personal blog on top. Except that it does, and it has been, and it costs less than a fast-food meal per month in electricity.

The trick is giving each piece exactly one job: the Synology stores bytes, the Pi orchestrates services, NFS bridges them, Cloudflare Tunnel handles ingress, nginx-proxy-manager handles routing, and the *arr apps automate the parts that are too repetitive to do by hand.

Nothing here is clever. It’s just a small number of well-chosen pieces, wired in the most obvious way they can be wired. The home server ecosystem in 2026 makes this possible in an afternoon. It’s a good time to be running your own media stack.