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
- One command per intent: If I know what I want to happen, there should be one executable
that does it.
flow deploy jellyfin,flow check health,flow verify media-stack. - Composition over repetition: Every app deploy is built from the same handful of shared building blocks. The differences between apps live in their values files, not in their automation.
- Imperative on purpose: I run the deploys. There’s no controller watching a branch. For a cluster with one operator, a reconciliation loop is more machinery than the problem needs.
- Secrets stay in the vault: Nothing sensitive lands in the repo. Credentials come out of flow’s vault at deploy time and get written into Kubernetes secrets on the way through.
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:
- Ingress: Traefik, which k3s ships with. Everything gets a
*.malik.haushostname and a shared HTTPS-redirect middleware. - Load balancing: MetalLB, handing out addresses from a small reserved range on my LAN.
- Certificates: cert-manager issuing a single Let’s Encrypt wildcard for the domain over a Cloudflare DNS-01 challenge. That one certificate is then mirrored into every namespace by the mittwald replicator, so app manifests just reference a secret name and never think about it again. This is my favorite piece of the whole setup. One cert, issued once, available everywhere.
- Storage: A Synology NAS over the NFS CSI driver, with a
Retainreclaim policy so a botchedhelm uninstallcan’t take data with it. k3s’ built-in local-path handles small config volumes. - Remote access: The Tailscale operator runs a connector that advertises the pod and LAN ranges, which means I get to the cluster from anywhere without exposing anything to the internet.
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
- flow architecture - the tool underneath all of this
- tbox - the smart home gateway that runs on this cluster
- flow docs