Infrastructure

The Homelab

A three-node Proxmox cluster and a rebuilt enterprise storage server, running the things I actually use every day. This whole environment is about two weeks old and the server has only been in the rack for one of them, so read it as an active build rather than a finished system. What follows is organized around decisions: the problem, what I weighed, what I chose, and what it cost.


The Cluster

Compute

Three nodes, each with a job

Compute runs as a Proxmox VE cluster on repurposed business-class desktops. One node carries the game servers and doubles as a local backup target for files pulled off the storage server. A second runs the Cloudflare tunnel daemon and a Windows VM, which is how I keep a Windows environment while working primarily from a MacBook. The third is racked but not yet online, waiting on a CPU.

  • Proxmox VE
  • Clustering
  • Virtualization
Next

Ceph, once the third node is live

The third node exists to make the cluster a real three, which is what Ceph needs for monitor quorum. Two nodes cannot vote sensibly about anything. That is the plan the hardware was bought for, and it is waiting on a shipment rather than on a decision.

  • Ceph
  • Quorum

Storage

Decision

A decommissioned enterprise server, rebuilt

Primary storage is a decommissioned 2U enterprise server rebuilt as the storage and media platform, running Unraid across two dozen enterprise SAS drives with dual parity. Any two drives can fail at the same time with zero data loss. Unraid also lets me mix drive sizes and add capacity a disk at a time, which is how a homelab actually grows.

  • Unraid
  • Dual Parity
  • Enterprise SAS
Decision

Pass-through, not hardware RAID

The host bus adapter runs in pass-through mode rather than presenting a hardware RAID volume. That is deliberate: the array stays reconstructible on any Linux machine, with no proprietary controller I would have to source and match if the card died. Tying my data to a specific piece of silicon is a dependency I did not want.

Read the writeup →
Recent fix

Mirroring the cache closed the last unprotected component

A mirrored NVMe cache sits in front of the array. Parity writes are read-modify-write across three disks, so sustained ingest would stall without something absorbing it. It is mirrored specifically because it holds live databases and in-flight data that array parity does not cover. Dual parity was already protecting my photos; nothing was protecting the database describing them. That was the last genuinely unprotected thing in the stack.

Read the writeup →
Integrity

Corruption should page you, not surprise you

A daemon watches cache pressure and responds in stages rather than all at once. Separately, files are checksummed against recorded hashes on a rolling schedule, so silent corruption surfaces as an alert now instead of as a file that will not open three years from now.

Distributed Transcoding

Decision

Send the work where the work is fast

Video encoding is CPU-hungry, and the server's chip generation predates AVX2, which modern encoders lean on heavily. Per core it loses badly to anything recent. Rather than force the server to grind through encodes, a scheduler farms jobs out to whichever machine is idle: a Mac mini that runs continuously, and a desktop workstation that contributes opportunistically. The server does what it is genuinely good at, holding disks and moving bytes, and hands off the part it is bad at.

Read the writeup →

What Runs On It

Photos

Immich

A self-hosted photo and video library, with a GPU-accelerated machine learning service doing semantic search, facial recognition, and OCR entirely on-premises. No photo of mine leaves the house to be indexed by somebody else.

Media

Jellyfin

Streaming with hardware transcoding, sharing the same GPU as the ML service. Services release the card when idle so one 4GB GPU can serve both.

Alongside these sit a handful of other containers, plus a media automation stack. I am deliberately not publishing the full inventory.

Network

Decision

Segmented, with policy between segments

The network is UniFi-managed and segmented, separating infrastructure, personal devices, IoT, and guest traffic, with explicit policy governing what may cross between them. Segmentation is the single change that made the lab safe to keep running while I break things inside it.

  • UniFi
  • VLAN Segmentation
  • Inter-VLAN Policy
Decision

Two ways in, doing different jobs

External exposure is exclusively through Cloudflare Tunnel with Cloudflare Access in front of it. No port forwarding, no dynamic DNS, no inbound hole in the router to find. Separately, WireGuard runs on the gateway as a private mesh back into the network for my own devices. These are not redundant: the tunnel publishes specific public hostnames to the world, WireGuard gets me in privately. Different jobs, different exposure.

Read the writeup →
DNS

AdGuard

DNS and ad blocking for the whole network.

Aside: local AI inference runs on my desktop machines through LM Studio, not on server infrastructure. It is a desktop hobby that happens to live in the same house, not part of the stack.

Power

Decision

UPS-backed, and monitored

The rack is UPS-backed with monitoring, so a brief outage does not turn into an unclean shutdown across a cluster and a parity array at the same time.

What Running This Has Taught Me

Destructive operations fail closed

Anything that deletes requires every precondition to be affirmatively true: a verified successor file, a cryptographic receipt, a link count proving nothing else references it. Uncertainty means do nothing.

Verify the artifact, not the exit code

A job reporting success only proves the job thinks it succeeded. The backup routine dumps the database, checks the compressed output for expected schema markers, and only then promotes it from a partial file to a real filename.

Measure before optimizing

Throughput problems have consistently not been what they first looked like. What presented as a network bottleneck turned out to be parity-write saturation. What looked like too many concurrent jobs was actually a queue of finished work waiting on disk.

Design for the machine going away mid-operation

State is persisted per item, operations are idempotent, and completion is never inferred from a file's absence.

Redundancy is not backup

Dual parity survives hardware failure. It does not survive a wrong path typed by a human.

Backups, Tiered On Purpose

Tradeoff

Not everything deserves the same protection

That last lesson is why backups here are split by whether something can be replaced. The irreplaceable tier, photos and the files I have flagged as high value, is backed up offsite to iCloud in compressed form. The reproducible tier, the media library and container and VM state, is covered by array parity and a local backup only, on the reasoning that it can be rebuilt or re-acquired if it is lost.

That is 3-2-1 applied selectively rather than uniformly. Backing up everything offsite would cost more than the reproducible half is worth, and pretending otherwise would mean either overspending or quietly not doing it.

Honest caveat

The local copy is in the same building

Locally that means Unraid backup plugins plus a cluster node acting as a backup target. Both sit in the same building as the array, so one bad event at the house still threatens the primary and the local copy together. The irreplaceable tier is safe offsite regardless, which is the part I actually care about, but this is a real limitation rather than a solved problem.

What Is Next

Monitoring

Thinner than it should be

Right now monitoring is whatever UniFi provides built in, plus something specifically watching qBittorrent. That is it. Beszel is planned and not yet deployed. For a two-week-old environment that is survivable, but it is the next thing to fix, because I would rather find problems than be told about them.

What Is Not Here

Security & Privacy

Network topology, internal addressing, segment layout, hostnames, the full service inventory, and version strings are deliberately left out. The architecture and the reasoning are the interesting parts, and they are safe to publish. The map of what runs where is not.