Note

This is a high-level overview of how my homelab is put together, with most of the attention on the flow side of it. I’m about to refactor a good chunk of this, so consider it a snapshot rather than a recommendation.

Last updated: August 2026

My homelab is a small k3s cluster running on hardware in my house. It hosts media, a few productivity apps, single sign-on, monitoring, and the services that tie my smart home together. Nothing here is novel infrastructure. What I find interesting about it is that there is no GitOps controller in the picture. I’m the reconciler, and flow is how I stay one.

Design Philosophy

The Cluster

k3s on bare metal, self-managed, one small set of nodes. I label them by role and pin the workloads that care about it. The media stack lands on the box with the Intel GPU so Jellyfin can use QuickSync for transcoding.

The pieces that make it feel like a real cluster:

Things that live outside the cluster

Not everything belongs in Kubernetes. The Hubitat hub, Home Assistant, the NAS, and Pi-hole all run on their own hardware. Rather than pretend otherwise, I stitch them in as plain Service + Endpoints pairs with an Ingress in front, so they get the same hostname scheme and the same wildcard certificate as everything else. From the outside they’re indistinguishable from a cluster workload.

DNS works the same way in reverse: Pi-hole runs on a physical box, and CoreDNS is patched to forward the private zone to it. Split-horizon DNS without running a second DNS server in the cluster.

Tradeoffs

Talos, then back to k3s. I ran a Talos control plane long enough to decide that for a cluster I mostly want to use, immutable-by-default was buying me less than it cost. k3s and an SSH session win on a Tuesday night.

Ingress, not Gateway API. My newer templates emit HTTPRoute, but everything deployed still runs on Traefik Ingress. Gateway API is the better model on paper. In practice, Traefik ships with k3s and already works, so the migration keeps losing to things I would rather be doing.

Imperative deploys can drift. Running the deploys myself is the right call for a cluster with one operator, but it does mean the cluster’s state and the repo’s state can disagree without anything complaining. The check executables exist partly to make that visible.

Resources