Every Cluster API tutorial for Hetzner or bare metal starts from the same node image: Ubuntu, cloud-init for first-boot config, and kubeadm to turn a fresh VM into a cluster member. That base carries a full general-purpose OS onto every node — a package manager, an SSH daemon, a shell, and however many hundreds of binaries Ubuntu ships by default — none of which Kubernetes itself needs to run a pod. Talos Linux throws all of that away and replaces it with roughly a dozen binaries, a read-only root filesystem, and a single mTLS API. It also has its own pair of Cluster API providers, which means a self-hosted fleet can run it as a drop-in alternative node OS without leaving the Cluster API model at all.
What Talos Actually Removes
Talos isn't a hardened Ubuntu — it's a different category of thing: a Linux distribution with no general-purpose ambitions at all, built to do exactly one job. The concrete difference shows up in what simply isn't there:
- No SSH daemon, no shell, no package manager. There is no
apt, nobashlogin, no way tosshinto a node and poke around. All of Talos's roughly 12 essential binaries exist to boot the machine and run the Kubernetes control plane and kubelet — compared to the 1,500+ binaries a typical general-purpose distro installs by default. - A read-only root filesystem. The OS itself is mounted from a SquashFS image; the only writable path on disk is
/var, which holds ephemeral state like container images, logs, and (on control-plane nodes) etcd data. You cannotapt installa debugging tool, edit a config file in place, or drop a backdoor script onto the root filesystem, because the root filesystem doesn't accept writes. - Every kernel module is signed at build time with an ephemeral key generated during that specific kernel build, so the kernel a node boots is cryptographically tied to that build and can't be swapped for a tampered one without the signature failing.
- One API, not a shell. All node management — reading status, applying config, upgrading, rebooting — goes through
talosctltalking toapid, a gRPC service authenticated with mutual TLS. There's no interactive login prompt on the other end.
The effect is a node whose only interaction surface is a single authenticated API. Compare that to a stock Ubuntu node under kubeadm, where "management surface" also includes the SSH port, whatever shell tools happen to be installed, systemd unit files an operator might hand-edit, and a package manager that can pull in anything from the Ubuntu archive. Talos doesn't hope you configure that surface securely — it removes it.
The Walkthrough: Wiring Talos Into a Cluster API Fleet on Hetzner
Talos Linux ships two purpose-built Cluster API providers, together referred to as CAPT (Cluster API Provider Talos):
- CABPT —
cluster-api-bootstrap-provider-talos— generates each node's Talos machine configuration and reconciles it into the bootstrap data Cluster API expects. - CACPPT —
cluster-api-control-plane-provider-talos— manages the control-planeMachineset the same wayKubeadmControlPlanedoes for a kubeadm-based cluster, except it drives Talos's own bootstrap sequence instead ofkubeadm init/kubeadm join.
Neither provider touches infrastructure provisioning — that's still the job of whatever InfrastructureProvider you've installed. On owned Hetzner machines, that's CAPH (Cluster API Provider Hetzner), the same provider a standard Ubuntu+kubeadm CAPH fleet already uses. Swapping node OS is a bootstrap/control-plane provider swap, not an infrastructure rewrite:
clusterctl init \
--infrastructure hetzner \
--bootstrap talos \
--control-plane talosA Cluster resource then points its controlPlaneRef at a TalosControlPlane (reconciled by CACPPT) instead of a KubeadmControlPlane, and each Machine's bootstrap.configRef points at a TalosConfigTemplate (reconciled by CABPT) instead of a kubeadm-flavored bootstrap config:
spec:
controlPlaneRef:
kind: TalosControlPlane # was: KubeadmControlPlane
apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
infrastructureRef:
kind: HetznerCluster # unchanged — same CAPH provider
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1From there, provisioning follows the same shape any CAPI flow does: clusterctl applies the Cluster/TalosControlPlane/MachineDeployment objects, CAPH provisions the underlying Hetzner servers, and CABPT hands each new machine its Talos machine config (network, disk, and Kubernetes join settings) instead of a cloud-init script. The first control-plane node calls talosctl bootstrap once etcd's peers are reachable — a one-time, explicit step, not an automatic first-boot action, so a fleet never accidentally reruns etcd initialization on a reboot.
Upgrades work the same way whether you triggered them by hand or through a TalosControlPlane rolling update: talosctl upgrade points a node at a new installer image, the node writes it to an alternate partition, and reboots into it. If the new image fails to come up healthy, the node automatically rolls back to the previous partition — there's no separate rollback procedure to remember, because the mechanism is symmetric by design. That's the same property kubeadm-based fleets get from MachineDeployment rolling replacement at the Machine level (replace the node, don't patch it in place) — Talos adds it within a single node too, at the OS-image level.
Ops and Resource Cost, Side by Side
| Ubuntu + kubeadm + cloud-init | Talos + CAPT | |
|---|---|---|
| Node interaction surface | SSH + shell + package manager + systemd | talosctl over mTLS only |
| Update unit | Individual apt packages, per node, can drift | Whole-OS image, atomic, identical across the fleet |
| Rollback on failed upgrade | Manual (re-image or restore from snapshot) | Automatic (boots previous partition) |
| Config source | cloud-init at first boot, then ad hoc | Single reconciled YAML, continuously enforced |
| Memory | baseline | ~7% less |
| CPU | baseline | ~6% more |
| Disk I/O | baseline | ~49% less |
| Network I/O | baseline | ~16% more |
| Disk usage | baseline | ~47% less |
Those percentage deltas come from a published Talos-vs-kubeadm resource benchmark (Sidero Labs' own comparison, run against equivalent node sizing) — worth reading as directional rather than a guarantee your exact workload reproduces the same numbers, but they line up with what the architecture predicts: fewer background services and a smaller resident OS trade a little more CPU (from apid and Talos's controller loops) for meaningfully less disk I/O, less disk footprint, and less memory pressure. On a fleet of owned Hetzner boxes where you're paying for disk and RAM whether or not you use them, that's not a rounding error.
The bigger operational change isn't in the table, though: on Ubuntu, a node update means SSH in (or run an Ansible playbook that does), apt upgrade, and hope every node in the fleet got the same packages at the same versions before someone builds a new one against a different Ubuntu image. On Talos, an update means replacing the entire node OS with a new signed image — there's no package-level drift to detect after the fact because there's no package manager to drift.
What You Actually Give Up
None of this is a free upgrade, and the honest version of this post says so directly.
Debugging changes shape. When something goes wrong on a stock Ubuntu node, the reflex is to SSH in, run journalctl -u kubelet, read /etc/kubernetes/manifests/*.yaml, and reach for etcdctl with the right TLS flags — the exact skills the CKA exam spends over half its material testing. None of that works on Talos, because there's no shell to run those commands from. talosctl logs, talosctl dashboard, and the structured API surface replace them, which is a real skill switch for a team that's spent years debugging the traditional way, not just a syntax change.
A compromised node has less to offer an attacker, which is exactly the point. If an attacker does get code execution inside a container on a Talos node, there's no shell to escalate to, no package manager to pull tooling from, no writable root filesystem to plant a persistence mechanism on, and no SSH daemon to pivot through. The blast radius of "we got a shell in a pod" stops at the container boundary far more often than it does on a general-purpose OS where the node underneath is just as hackable as your laptop.
It's an all-or-nothing swap per cluster, not per node. A Cluster resource picks one control-plane/bootstrap provider pairing; you don't run half your control plane on KubeadmControlPlane and half on TalosControlPlane in the same cluster. Adopting Talos means standing up a cluster (or a MachineDeployment) that uses it, migrating workloads over, and retiring the Ubuntu-based one — the same blue/green pattern any node-OS change requires, just with a different bootstrap provider on the new side.
Where This Fits a Self-Hosted PaaS Fleet
For a platform already running Cluster API against owned Hetzner machines via CAPH, adopting Talos doesn't mean replacing the infrastructure layer — it means adding CABPT and CACPPT alongside the existing bootstrap/control-plane providers and offering Talos-based node pools as an option, cluster by cluster, while the Ubuntu-based path keeps running unchanged for clusters that haven't migrated yet. That's a meaningfully smaller lift than "rewrite the fleet," and it's the same incremental-adoption shape Cluster API is designed to support: swap the piece of the object graph you want to change, keep the rest of the contract identical.
The attack-surface argument matters more, not less, for a platform whose control plane manages other people's workloads. A self-hosted PaaS's own management-cluster nodes are a higher-value target than any single tenant's app — they hold the credentials and reconciliation logic for the entire fleet. Removing SSH, a shell, and a package manager from that specific set of nodes closes off exactly the escalation path a stolen container credential or a supply-chain-compromised base image would otherwise have to work with.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, backed by Cluster API's declarative fleet management instead of a hand-managed server pool. Star the repo on GitHub or deploy your first app today.
Sources
- Cluster API Bootstrap Provider Talos — GitHub
- Cluster API Control Plane Provider Talos — GitHub
- Which Kubernetes Is the Smallest? — Sidero Labs
- Talos Linux: No SSH, No Shell — Production Guide (2026)
- Talos Linux: Redefining Security for Kubernetes Environments — Linux Security
- Upgrading Kubernetes — Sidero Documentation
- Managing Kubernetes on Hetzner with Cluster API — Hetzner Community
- Why kubeadm Over k3s, RKE2, and Talos in 2026