Years of running Linux machines have left me with a recurring problem: stale software scattered everywhere, half-remembered compile flags, tarballs I can no longer identify. Where did this binary come from? What version is it? Did I build it with the right options? After enough of that, I started to develop a strong preference for systems that stay clean by default.
Docker helped a lot, and not for the reasons people usually cite. For me it was never really about resource isolation -- it was about keeping software off the host. The host is a stable surface; everything else runs in a container. I've been managing docker-compose.yml files on a handful of NixOS machines for several years now, and honestly it has worked pretty well.
But I've been curious about Talos Linux for a while. The pitch is: an immutable, read-only operating system with no SSH, no package manager, no shell. You configure it with YAML, the same way you configure Kubernetes, and then you leave it alone. That appeals to me for the same reason NixOS does -- the system is declared, not accumulated -- but it goes further. There's nothing to accumulate even if you wanted to. You literally cannot log in and make a mess.
The part I've been less enthusiastic about is Kubernetes itself. It feels like a lot of machinery for problems I don't have. The learning curve is steep, the surface area is huge, and my docker-compose setup has never failed me in a way that required a scheduler. But I haven't actually given k8s a real try, so I decided to stop having opinions about it from the outside and just build something.
Three VMs on a Proxmox host. Each one gets 2 vCPU, 4GB RAM, and a 32GB disk on a ZFS pool backed by a Samsung SSD. All three run as control-plane nodes -- no separate workers -- with scheduling allowed on all of them. The goal is a monitoring stack: Prometheus, Grafana, and the supporting infrastructure to run them reliably.
I used Talos v1.13.0. The first thing you learn about Talos is that you have to decide what
your OS needs before you install it, because there is no way to add things later without
reimaging. My workloads need Longhorn for distributed storage, and Longhorn uses iSCSI to
mount volumes. iSCSI support is not in the base image. So before doing anything else, I
went to the Talos Image Factory, selected the iscsi-tools and
util-linux-tools extensions, and got back a schematic ID:
613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245
That ID is baked into the ISO for initial installation and into the installer image URL for future upgrades. Download the ISO, upload it to Proxmox, attach it to each VM, boot.
When Talos boots from the ISO it sits in maintenance mode, waiting for you to push it a configuration. Generate the configs:
talosctl gen config talos-cluster https://<VIP>:6443 \
--install-image factory.talos.dev/installer/613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245:v1.13.0
This produces a controlplane.yaml, a worker.yaml (unused here), and a talosconfig. Push the config to each node while they're still in maintenance mode:
talosctl apply-config --insecure --nodes 192.168.1.242 --file clusterconfig/controlplane.yaml
talosctl apply-config --insecure --nodes 192.168.1.74 --file clusterconfig/controlplane.yaml
talosctl apply-config --insecure --nodes 192.168.1.101 --file clusterconfig/controlplane.yaml
Each node installs Talos to disk and reboots. Then bootstrap etcd on one of them and pull the kubeconfig:
talosctl bootstrap --nodes 192.168.1.242 --endpoints 192.168.1.242
talosctl kubeconfig --nodes 192.168.1.242 --endpoints 192.168.1.242
A couple of minutes later, kubectl get nodes shows all three nodes Ready. That
part was genuinely smooth.
Kubernetes on bare metal doesn't have a load balancer by default. MetalLB fills that gap by responding to ARP requests for a designated VIP and directing traffic to the right node. I tried MetalLB's FRR mode first -- it uses BGP and a daemon called bfdd -- and immediately hit a problem: bfdd fails on Talos's kernel due to missing capabilities. Switched to native L2 mode, which needs no extra daemons and is perfectly adequate for a flat home network:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: homelab-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.4/32
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: homelab-l2
namespace: metallb-system
One thing worth knowing: the VIP does not respond to ping. MetalLB's L2 advertisement handles ARP, so TCP connections to port 80 and 443 work fine, but ICMP just disappears. I spent more time than I should have staring at a terminal thinking something was broken.
Traefik runs as a DaemonSet on all three nodes with hostNetwork: true, which
means it binds directly to the host's network interfaces. No overlay network overhead,
no port forwarding. Traffic hits the VIP, goes to whichever node currently owns it, and
lands directly on that node's Traefik process. It works.
I'm using a wildcard cert for *.MYDOMAIN.net obtained via Let's Encrypt with
a Cloudflare DNS-01 challenge. The cert is managed outside the cluster with certbot; I
import it as a Kubernetes TLS secret and push it into every namespace that needs it.
A Traefik TLSStore makes it the default cert for all ingresses cluster-wide, so I don't
have to annotate anything individually.
When the cert renews I have to update three namespaces and restart Traefik. This is the most manual part of the setup and I'll automate it eventually.
Longhorn is distributed block storage. It replicates volumes across nodes using iSCSI --
which is why the extensions in the Talos schematic matter. Without iscsi-tools
baked into the image, Longhorn volume mounts just fail silently and you spend a long time
wondering what's wrong.
I'm using two replicas per volume. Three nodes with 32GB disks doesn't leave a lot of room for three-replica overhead, so two is the right tradeoff here. Longhorn's UI gives a decent overview of volume health and handles node draining gracefully before maintenance reboots.
Rancher is a Kubernetes management UI. For a homelab it's mostly a dashboard, but it makes navigating resources and managing RBAC less painful than raw kubectl. One of the three replicas crash-loops -- this is a known bug in the current version -- but two out of three is enough for quorum and everything works.
The kube-prometheus-stack Helm chart drops in Prometheus, Grafana, and Alertmanager with pre-built Kubernetes dashboards. All three use Longhorn PVCs for persistence. One gotcha: if the Helm install times out (the Prometheus operator can take a while to reconcile), Helm marks the release as failed even though everything is actually running. Just re-run the upgrade; it's idempotent.
The cluster is running. Grafana is up at grafana.DOMAIN.net, Prometheus and Alertmanager behind it, Longhorn managing volumes, Traefik routing traffic, MetalLB owning the VIP. From an infrastructure standpoint, Talos itself has been exactly what it advertised: the nodes run, I can't accidentally break them by hand, and upgrades are mechanical.
I still don't know whether I actually want Kubernetes in my life. The overhead is real. The mental model is large. There are already a couple of crash-looping pods that I've decided to just leave alone because they don't affect anything I care about -- and I'm not sure whether that's the right attitude or a sign that the system has more surface area than I want to maintain. My docker-compose setup never had crash-looping anything.
I'm going to give it a few months before making a call. Talos at least removes one of the things I was most worried about, which is the OS layer becoming a mess over time. Whatever I end up deciding about Kubernetes, I think Talos is the right way to run it.