Homelab platform

The Kubernetes platform most of my projects run on.

Built with
Proxmox, Ceph, TrueNAS SCALE, Sidero Omni, Talos Linux, Traefik, CloudNativePG, Gitea, MinIO, Coder, Keycloak, Technitium DNS
  • Kubernetes on Talos Linux, managed by Omni, with Traefik out front.
  • My own Gitea and private container registry, so my git hosting is mine too.
  • Coder for dev environments on demand.

It’s where I test ideas before trusting them at work. Most fail, and that’s the point.

Hardware

  • A 9-node Proxmox cluster with mixed Intel and AMD nodes. Three nodes on bonded 10G carry the fast storage tier; the rest are small, efficient boxes on 1G.
  • Ceph with two separate tiers: fast SSDs for VMs, and bulk drives for everything else.
  • TrueNAS SCALE hosts the Talos VMs and serves iSCSI and NFS volumes to Kubernetes.
  • UniFi networking with VLANs for services, storage and an isolated segment for the cluster.

Clusters

  • Management cluster: a single Talos node that runs Omni and Keycloak. I moved them off two hand-built VMs without losing Omni’s state, so the existing clusters reconnected without being re-adopted. It’s deliberately not managed by Omni itself.
  • Homelab cluster: one control plane and three workers, defined as an Omni cluster template in git.
  • Omni’s Proxmox provider can create Talos nodes on demand. I’ve used it to build and tear down throwaway clusters.

What runs on it

  • Gitea: two active-active replicas with Postgres, shared storage and a Dragonfly cache, with Actions and a private container registry. It hosts all my repos.
  • CloudNativePG: a three-instance Postgres 17 cluster behind Gitea, Coder and Pit Wall.
  • MinIO for object storage: S3 API and console on one hostname.
  • Coder for dev environments, and Obsidian in the browser for notes.
  • Moonlight for game streaming in the browser.
  • This site, and Pit Wall.
  • Platform add-ons: TrueNAS CSI drivers, MetalLB, a descheduler, and the NetBird operator that puts it all on the mesh.
  • DNS: a Technitium pair on two separate physical machines, replicating zone changes in about 10 seconds.

How things get deployed

  • Cluster config, Helm values and manifests all live in git and are applied deliberately. I tried GitOps controllers elsewhere and they cost more than they gave at this scale.
  • Apps build in Gitea Actions on a custom runner image. Each merge pushes an image tagged with its commit and rolls it out with a namespace-scoped service account. If the new pods never become ready, the job fails.

Things it taught me

  • A $15 transceiver can take down a cluster. A failing optic in a round-robin bond made a node fence itself six times. Every bond is LACP now.
  • HTTP/2 connection reuse can break logins. A wildcard certificate let the browser reuse a connection for a different host, and single sign-on returned 404. A certificate scoped to the right hostnames fixed it.
  • More CPUs can crash a pod. Dragonfly starts a thread per core and needs memory for each, so giving its node more vCPUs put it in a crash loop. Pinning the thread count fixed it.
  • DNS replication can fail quietly. The primary’s NOTIFY messages came from a NAT’d address, so the secondary refused them and fell back to slow polling. Nothing looked broken.
  • Changing one bridge can drop every VM. Editing any interface through the NAS’s middleware re-applied the main bridge and disconnected all VMs at once.