Talos Kubernetes homelab
A three-node Talos Linux cluster on Proxmox, provisioned with OpenTofu and reconciled by FluxCD, running the household DNS, monitoring and internal services.
I built this home Kubernetes cluster so that I could rebuild it from source instead of maintaining it by hand. It’s three Talos Linux control-plane nodes on a Proxmox host, provisioned with OpenTofu, and FluxCD reconciles the whole application layer from a git repository.
Shape
The nodes are three Talos control-plane VMs. Talos has no shell and no package manager; the machine configuration is the API. That’s what makes “rebuild it” something I can actually do and not just something I’d like to be able to do. OpenTofu provisions them, using the Proxmox and Talos providers.
FluxCD reconciles everything in apps/ and clusters/. If I change something
by hand, the next reconcile reverts it, and that’s intended.
Ingress is Traefik behind MetalLB. cert-manager issues a wildcard certificate through a DNS-01 webhook, so internal services get real TLS without being exposed to the internet. Storage is Longhorn, with replicated volumes and scheduled snapshots.
What it runs
Pi-hole as the household DNS resolver, Prometheus and Grafana, Gitea with a Gitea Actions runner, Longhorn’s own UI, Headlamp, and a dashboard that ties them together. All of it is LAN-only.
Monitoring goes beyond the cluster. The NAS runs an exporter installed directly on it, and Prometheus scrapes that, so disk temperatures, RAID volume usage and per-drive SMART status end up in the same Prometheus as the Kubernetes metrics.
DNS is the only service with real availability requirements, because the whole house notices when it stops. It runs as a two-replica StatefulSet with its own failover, which takes roughly four seconds. That failover is built on MetalLB’s L2 announcements; Kubernetes doesn’t offer anything for it natively.
Current work
Getting the storage layer off the control plane’s disk. It turned out that every etcd member and every Longhorn replica were sharing one consumer NVMe. Under load, etcd’s flush latency cost the controller manager its leader election on every node. Three replicas that share a disk don’t protect you from that disk. All they add is write amplification. Running fewer Longhorn replicas and enabling TRIM buys time. The real fix is a drive with power-loss protection, or a separate one for etcd.
Home Assistant now runs in the cluster. I moved it off the Docker Compose host it used to live on, and it was the awkward kind of migration. It needed a Zigbee USB dongle passed into a pod, a second network interface on the LAN for multicast device discovery, and node labelling that follows the hardware instead of naming a specific node. Each of those had a trap in it, and the device one taught me the most.
A clean rebuild. This one is designed but I haven’t done it yet. The plan is to replace the split Terraform-plus-Ansible-templates setup with a single OpenTofu state, move secrets to SOPS with separate keypairs per repository, and add drift detection that files an issue instead of applying anything automatically. At the infrastructure layer, automatically “correcting drift” can mean destroying and recreating a virtual machine.
This site’s own CI runs on the cluster’s Gitea runner.