Here is how most of us run Kubernetes nodes. You install a general-purpose Linux distro, harden it with a CIS benchmark script, add an SSH key, set up a config management tool to keep drift in check, and then spend the next two years SSH-ing in to fix the things the config management tool didn’t catch. Every node is a little snowflake with its own history. We’ve accepted this as normal.
The first time I tried to SSH into a Talos node, I got nothing. No shell, no connection, no familiar Linux prompt. My first reaction was confusion, then mild panic. How am I supposed to debug this thing?
That was three years ago. Now I can’t picture running Kubernetes on anything else.
What is Talos Linux?
Talos Linux is a Linux distribution built for one job: running Kubernetes. Calling it a “Linux distribution” sells it short, though, because Talos throws away most of what makes a normal Linux box feel normal.
- No SSH. You can’t shell into a Talos node.
- No package manager. No apt-get, no yum, no installing things.
- No shell. There’s no bash, no sh, nothing.
- Immutable filesystem. The root filesystem is read-only.
- API-driven. All management happens through a gRPC API.
Your first reaction is probably mine: “That’s insane. How do you get anything done?”
The answer is what changed how I think about infrastructure.
Why Remove SSH?
Let me tell you about a 3 AM incident from years ago, well before Talos.
Production was down. I SSH’d into the bad node, did some panicked debugging, found the issue, and fixed it by hand-editing a config file and bouncing a service. Crisis averted, back to bed.
Then the snowflake bites. That fix lived in exactly one place: that one node’s disk. No documentation, no Git commit, no audit trail. Months later we replaced the node, and the problem came straight back, because nobody remembered the manual change that had quietly kept it alive.
That is the tax on mutable infrastructure, and it compounds. Every SSH session is a chance for config drift. Every “quick fix” is debt that you pay back later, usually at 3 AM, usually without the context you had the first time.
Now picture the other side of that. What if the node physically could not drift, because there was no door to walk through and change it by hand? Talos makes that real. You can’t SSH in and tinker. Every change goes through the API, so every change can be tracked, versioned, and reproduced. The fix from that 3 AM incident would have been a one-line config diff in Git, reviewed and reapplied, instead of a secret that died with the disk.
When I explain this, people push back fast: “But what about debugging? What about emergencies?”
Debugging Without SSH
Fair question. Here is the reality without SSH. Talos ships talosctl, a CLI that talks to the Talos API, and it covers the things you’d normally reach for a shell to do:
# Get system logs
talosctl logs kubelet
# Stream kernel messages
talosctl dmesg -f
# List running processes
talosctl processes
# Get node health
talosctl health
# Read any file from the node
talosctl read /etc/hosts
# Get full machine configuration
talosctl get machineconfig
Everything you’d normally do over SSH is here, with one important difference. The API has access control, audit logging, and can be scoped to specific operations. SSH is an all-or-nothing root shell, and a root shell is exactly what an attacker wants too.
For the “I genuinely need a shell” moments, there’s an escape hatch:
talosctl dashboard
That gives you a read-only TUI with system stats, logs, and node status. And for real emergencies there’s a debug container that spins up a privileged container with full host access. It’s deliberately awkward to use, which is the point. Reaching for it should feel exceptional, because it is.
The Configuration Model
Talos nodes are configured with a single YAML machine config. A trimmed-down example:
machine:
type: controlplane
token: <bootstrap-token>
ca:
crt: <ca-certificate>
key: <ca-key>
network:
hostname: node-01
interfaces:
- interface: eth0
dhcp: true
install:
disk: /dev/sda
image: ghcr.io/siderolabs/installer:v1.6.0
cluster:
clusterName: homelab
controlPlane:
endpoint: https://192.168.1.100:6443
This config describes everything about the node: network settings, disk layout, Kubernetes version, kernel arguments, system extensions, CNI configuration, all of it.
When you apply it:
talosctl apply-config --nodes 192.168.1.10 --file controlplane.yaml
Talos doesn’t “configure” the node so much as the node becomes the config. It converges to the desired state. Change the config, apply again, and the node converges to the new state. Same declarative mental model as Kubernetes, just one layer down the stack.
GitOps All The Way Down
Compare that to where OS configuration usually lives: Ansible playbooks that drift from reality, a config management tool nobody fully trusts, and a layer of tribal knowledge accumulated through SSH sessions that left no trace. The source of truth is “ask the person who set it up,” and that person eventually leaves.
Because everything in Talos is YAML, it drops straight into a GitOps workflow. My setup looks like this:
infrastructure/
├── talos/
│ ├── controlplane-1.yaml
│ ├── controlplane-2.yaml
│ ├── controlplane-3.yaml
│ ├── worker-1.yaml
│ └── worker-2.yaml
├── kubernetes/
│ └── ... (manifests managed by ArgoCD)
└── ...
When I need to change something on a node, like adding a kernel argument or tweaking network config, I edit the YAML, commit it, and apply. The change is documented because the commit is the documentation. It’s reviewable in a PR, and it’s reproducible on the next node I build. The source of truth is explicit and version-controlled, which is the whole point of doing this.
Security by Default
Strip away the shell and the package manager and a whole class of attacks stops working:
- No package manager means no
curl | bashmalware install - No SSH means no brute-forcing SSH credentials
- Read-only filesystem means no swapping out system binaries
- Minimal userspace means a smaller attack surface to begin with
The system runs only what Kubernetes needs: containerd, kubelet, and a few supporting services. No cron, no systemd-resolved, none of the usual background daemons that exist mostly because a general-purpose distro assumed you might want them.
So even if an attacker pops a container and escapes to the host, where do they land? A read-only filesystem with no shell, no package manager, and nowhere to persist. This lines up with zero trust principles: the node doesn’t even trust itself to stay configured correctly, so it continuously enforces the declared state.
Upgrades That Actually Work
Upgrades are one of my favourite parts:
talosctl upgrade --nodes 192.168.1.10 \
--image ghcr.io/siderolabs/installer:v1.7.0
Talos pulls the new OS image, writes it to a second partition, and reboots into it. If the upgrade fails, the node rolls itself back to the previous version automatically.
That’s a transactional upgrade. No half-applied state, no “kernel panicked halfway through the upgrade” Saturday. It either completes cleanly or rolls back whole.
For Kubernetes version bumps you update the machine config with the new version and apply it. Talos handles draining and cordoning if you use the Talos-aware upgrade path.
When Not to Use Talos
I’d be lying if I said this fits everyone. Stick with traditional Linux if:
- You need specific packages that aren’t available as Talos extensions
- You’re learning Linux and the abstraction hides too much
- Your team isn’t ready for the shift to API-driven management
- You run non-Kubernetes workloads on the same nodes
Talos is laser-focused on running Kubernetes. If that’s not your main use case, you’ll spend your time fighting the design instead of using it.
And debugging has a steeper curve up front. When something breaks you can’t just vim /etc/whatever your way out of it. You have to learn the Talos tools and concepts first. Long term that’s a benefit, because it forces better habits, but it’s a real cost during adoption and pretending otherwise would be dishonest.
Getting Started
The fastest way to try Talos is with Docker:
# Create a local cluster
talosctl cluster create --name demo
# Get kubeconfig
talosctl kubeconfig --nodes 127.0.0.1
# You're now running Talos locally
kubectl get nodes
For something closer to real, start with a small homelab cluster. My homelab runs on refurbished mini-PCs, which is a cheap way to experiment with Talos before you bet production on it. The Talos documentation is genuinely good, with guides for bare metal, AWS, GCP, Azure, VMware, and more.
The Mental Shift
So why doesn’t everyone run their nodes this way? Honestly, because the whole industry assumes the opposite. Traditional infrastructure management is built around the idea that you’ll need to get in there and fix things by hand. SSH keys, bastion hosts, jump boxes, console access: the entire toolchain exists to give you a door.
Talos closes the door on purpose. The bet is that you should never need to get in there, because the system describes itself and converges on its own. If something’s wrong, you fix the config and reapply. That sounds rigid. It is rigid. The rigidity is the feature.
Closing the gap is easier than the philosophy makes it sound. Every incident I’ve had on Talos followed the same loop: read the logs via the API, spot the misconfiguration, fix the config, apply. The fix documents itself because it’s a config change in Git. There’s no lingering “wait, did someone SSH in and do something?” question hanging over the cluster, because the answer is always no.
Three years in, I don’t miss SSH. The API gives me everything a shell gave me, with better security, a real audit trail, and reproducibility a shell could never offer. Talos is a strong opinion about how infrastructure should work, baked into an OS. If that opinion matches yours, immutable, declarative, API-driven, it’s one of the best ways I know to run Kubernetes.
