I’ve written about Talos Linux as the immutable Kubernetes OS, and I’ve compared Arch vs NixOS for workstations. One question keeps landing in my inbox after both: what about NixOS for the Kubernetes nodes themselves?
It’s a fair question, because on paper these two look like siblings. NixOS and Talos are both declarative. Both can be immutable. Both put your configuration under version control. So why pick one over the other to run a cluster?
Up front, here’s my bias so you can read the rest with it in mind: I run a sovereign homelab where I want to understand and own every layer, and I have a low tolerance for drift. I’ve put both of these in production. This post is me comparing them honestly, not crowning a winner.
I’ll compare them on the criteria that actually move the decision: configuration model, security posture, flexibility, upgrades, Kubernetes integration, and how you debug them when things go sideways. Feature checklists you can find on either project’s homepage. The trade-offs are where the real choice lives.
The Philosophical Difference
Start with intent, because it colours everything else.
Talos Linux is a single-purpose operating system. It exists to run Kubernetes and nothing else. Every design decision bends toward that one goal.
NixOS is a general-purpose operating system that happens to be very good at running Kubernetes. It does anything a traditional Linux can do, except the whole machine is described as declarative configuration.
That gap in intent is the root of every trade-off below. Keep it in your head as we go.
Configuration Models
Talos: YAML All The Way
Talos configuration is plain YAML:
machine:
type: controlplane
network:
hostname: node-01
interfaces:
- interface: eth0
dhcp: true
install:
disk: /dev/sda
cluster:
clusterName: production
controlPlane:
endpoint: https://k8s.example.com:6443
You apply it with talosctl apply-config, and the node converges to that state. Predictable, no surprises.
NixOS: The Nix Language
NixOS configuration is written in Nix, a functional language:
{ config, pkgs, ... }:
{
networking.hostName = "node-01";
services.k3s = {
enable = true;
role = "server";
extraFlags = toString [
"--cluster-init"
"--disable traefik"
];
};
# You can do anything here
services.prometheus.exporters.node.enable = true;
virtualisation.docker.enable = true;
system.stateVersion = "24.05";
}
You apply it with nixos-rebuild switch, and the system converges to the declared state.
The Trade-off
Talos’s YAML is limited, and you can learn it in an afternoon. The Nix language is far more powerful, and it’ll take you weeks before it stops feeling alien. I say that as someone who likes Nix.
So the call is about scope. Need only Kubernetes? Talos’s simplicity wins outright. Need Kubernetes plus a pile of other things on the same box? Then the Nix learning curve buys you something real.
Security Posture
Talos: Secure by Removal
Talos earns its security by deleting attack surface:
- No SSH daemon
- No shell
- No package manager
- Read-only root filesystem
- Minimal userspace (just containerd, kubelet, and essentials)
An attacker who compromises a container and escapes to the host finds almost nothing useful. There are no tools to pivot with, no obvious persistence mechanism, no package manager to pull down malware. That maps directly to a value I care about: shrinking the blast radius when (not if) something gets breached.
NixOS: Secure by Configuration
NixOS can be hardened a long way, but you opt in:
{
# Disable SSH password auth
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
};
# Firewall
networking.firewall = {
enable = true;
allowedTCPPorts = [ 6443 ];
};
# Read-only store (always true)
# But /etc, /var, /home are still mutable
}
The /nix/store is always read-only, which is great. The rest of the filesystem stays mutable by default. You can lock it down further, but that’s extra work you have to remember to do.
The Trade-off
Talos is hardened out of the box. NixOS gets there too, but only when you put the effort in deliberately. For nodes whose only job is Kubernetes, Talos’s paranoid defaults are usually what you actually want, and the discipline isn’t optional, which is the point.
Flexibility vs Focus
This is where the two systems pull apart hardest.
What NixOS Can Do That Talos Can’t
On a NixOS node, your Kubernetes machine can also run:
- System services: Prometheus node exporter, log shippers, monitoring agents, all as systemd services rather than pods
- Non-containerized workloads: sometimes you genuinely need a bare-metal database or a legacy app sitting next to Kubernetes
- Custom tooling: install any package from nixpkgs (80,000+ of them)
- Development tools: if you need to debug on the node itself, the tools are a config line away
Here’s a local PostgreSQL running next to Kubernetes for testing:
{
services.k3s.enable = true;
services.postgresql = {
enable = true;
package = pkgs.postgresql_15;
};
}
What Talos Does That NixOS Won’t
Talos’s restrictions are the whole product:
- No drift: you literally cannot make an undocumented change
- Forced discipline: every change goes through config, then API, then apply
- Simpler mental model: the node is the config, full stop
- Smaller attack surface: what doesn’t exist can’t be exploited
The Trade-off
If your nodes only ever run Kubernetes workloads, Talos’s walls stop you making mistakes. If your nodes need to do more than that, the same walls become a cage and NixOS’s flexibility is the answer.
Upgrade and Rollback
Talos: Transactional OS Updates
talosctl upgrade --nodes 192.168.1.10 \
--image ghcr.io/siderolabs/installer:v1.7.0
Talos writes the new OS to an alternate partition and reboots. If the new image fails to boot, it rolls back to the previous version automatically. The upgrade is atomic, succeeding fully or failing fully, with nothing in between to babysit.
NixOS: Generation-Based Rollback
nixos-rebuild switch --flake .#node-01
NixOS creates a new “generation” for every configuration change. Each generation is bootable from GRUB. Rolling back means selecting an earlier one:
nixos-rebuild switch --rollback
# Or select from boot menu
NixOS keeps several generations around by default, so you can drop back to a configuration from weeks ago.
The Trade-off
Both rollback stories are genuinely good, which is rarer than it sounds. Talos is more automatic: a failed boot triggers the rollback for you. NixOS is more flexible: you keep arbitrary generations and can step back to whichever one you like.
Kubernetes Integration
Talos: Built for Kubernetes
Talos ships with everything a cluster needs:
- Kubernetes components are built in
- CNI configuration lives in the machine config
- etcd management is handled for you
- Certificate rotation is automatic
- Upgrades coordinate with Kubernetes (drain, upgrade, uncordon)
You don’t install Kubernetes on Talos. Kubernetes is part of Talos.
NixOS: Kubernetes as a Service
NixOS hands you several options:
# Option 1: K3s (lightweight)
services.k3s = {
enable = true;
role = "server";
};
# Option 2: Full Kubernetes via kubeadm
services.kubernetes = {
roles = ["master" "node"];
masterAddress = "k8s.example.com";
};
# Option 3: Just the kubelet
services.kubernetes.kubelet.enable = true;
You decide how Kubernetes gets deployed. More options, but also more decisions you own.
The Trade-off
Talos’s opinionated integration means fewer decisions and sensible defaults for Kubernetes specifically. NixOS lets you wire Kubernetes up however suits you, at the cost of having to make those calls yourself and stand behind them.
Debugging and Troubleshooting
Talos: API-Only Access
# View logs
talosctl logs kubelet --nodes 192.168.1.10
# Get system info
talosctl get members
# Read files
talosctl read /etc/hosts
# Dashboard (read-only TUI)
talosctl dashboard
Everything goes through the API. No SSH, no shell. There’s a debug container feature for emergencies, and it’s intentionally inconvenient so you don’t reach for it casually.
NixOS: Full Linux Access
# SSH in
ssh root@192.168.1.10
# Use any tool
htop
journalctl -u k3s
kubectl get nodes
vim /etc/nixos/configuration.nix
It’s a full Linux box. Every tool you know already works. Comfortable, and quietly dangerous, because each SSH session is a chance to make a change that never makes it back into Git.
The Trade-off
Talos pushes you to debug through proper channels: API, logs, metrics. NixOS lets you debug however you like, including the ways that quietly introduce drift. Whether that freedom is a gift or a trap depends entirely on your discipline.
My Recommendation
After running both, here’s the decision framework I actually use.
Choose Talos When:
- Kubernetes is the only workload on these nodes
- Security is paramount and you want minimal attack surface
- You want guardrails and preventing drift matters more than flexibility
- You’re comfortable with API-driven management
- Your team is disciplined about declarative workflows
Choose NixOS When:
- You need non-Kubernetes workloads on the same nodes
- You want full Linux flexibility with declarative benefits
- You need specific packages that aren’t available as Talos extensions
- Your team is already invested in the Nix ecosystem
- You want to customize deeply beyond what Talos allows
My Setup
I run both, in different roles:
- Homelab Kubernetes: Talos. These nodes only run Kubernetes, and I want the security and the simplicity that comes with the cluster having nowhere to hide.
- Homelab auxiliary servers: NixOS. These run Postgres, backup services, and monitoring infrastructure that I deliberately keep outside Kubernetes.
The combination gives me locked-down cluster nodes that physically cannot drift, plus flexible NixOS machines for everything that doesn’t belong in a pod. Each tool sits where its constraints help instead of hurt.
The Hybrid Approach
Some teams run NixOS for control plane nodes, where they want more debugging access, and Talos for workers, where they want maximum lockdown. It works, though it adds operational surface you now have to maintain in two shapes.
There’s another path I’ve seen work well: start on NixOS because it’s familiar, then migrate to Talos as your team matures into declarative operations. NixOS teaches the mental model gently. Talos enforces it whether you like it or not.
The KubeVirt Option: Kubernetes as the Control Plane for Everything
There’s a third approach worth putting on the table: using KubeVirt to run NixOS as VMs inside Kubernetes. This flips the framing. Rather than picking NixOS or Talos for your nodes, you run Talos (or any minimal OS) on the bare metal and run your NixOS workloads as VMs that Kubernetes manages.
The stack looks like this:
flowchart TD
subgraph stack["KubeVirt Stack"]
ArgoCD["ArgoCD (GitOps)"]
KubeVirt["KubeVirt VMs (NixOS, other OSes)"]
K8s["Kubernetes (K3s/Talos)"]
BareMetal["Bare Metal (Ansible provisioned)"]
ArgoCD --> KubeVirt
KubeVirt --> K8s
K8s --> BareMetal
end
Why This Works
Your VM definitions become Kubernetes manifests:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: nixos-workload-01
spec:
running: true
template:
spec:
domain:
resources:
requests:
memory: 4Gi
cpu: 2
devices:
disks:
- name: nixos-disk
disk:
bus: virtio
volumes:
- name: nixos-disk
persistentVolumeClaim:
claimName: nixos-workload-01-pvc
With ArgoCD driving it, this VM definition lives in Git. The NixOS config inside the VM lives in Git too. The whole thing is declarative, auditable, and version-controlled top to bottom.
The Full Declarative Stack
Combine that with:
- Ansible for bare metal provisioning (install Talos or K3s on the physical nodes)
- ArgoCD for managing everything in Kubernetes, KubeVirt VMs included
- NixOS flakes for VM configurations, pulled from Git during VM boot
The payoff: your entire infrastructure, from bare metal to VMs to Kubernetes workloads, is defined in Git. Change a VM’s NixOS config, commit, ArgoCD syncs, the VM rebuilds. Full audit trail, full reproducibility, exactly the sovereignty story I keep coming back to.
When This Makes Sense
This approach earns its complexity when:
- You need mixed workloads, some containerized, some genuinely needing full VMs
- You want one control plane (Kubernetes) for all of it
- You have workloads that can’t be containerized but you still want them under GitOps
- You’re already running KubeVirt for other reasons
The cost is real: you’re now running a virtualization layer inside Kubernetes, with everything that implies. But if you need VMs anyway, managing them declaratively through the same GitOps pipeline as the rest of your estate is a genuinely strong position to be in.
Example: Database on NixOS VM
Say you want a PostgreSQL server with specific kernel tuning that won’t fit inside a container. Rather than maintaining a separate NixOS server by hand:
- Define the VM in a KubeVirt manifest (in Git)
- The VM boots NixOS and pulls its config from a flake (in Git)
- ArgoCD manages the VM lifecycle
- Backups, monitoring, networking all run through Kubernetes
The database gets NixOS’s flexibility. You get Kubernetes’s orchestration. Everything stays in Git.
Conclusion
NixOS and Talos both point at the same future: declarative, reproducible, version-controlled infrastructure. The choice between them was never about which is objectively “better.” It’s about which set of constraints serves what you’re trying to build.
Talos boxes you into Kubernetes-only, API-only, minimal-surface operations. Those walls are a feature when you want a secure, drift-free Kubernetes platform and you’d rather the system enforce the discipline than rely on yours.
NixOS holds you to declarative configuration while keeping the full run of a Linux box. Those constraints are a feature when you need that range and you trust your team to use it without sliding into drift.
So the real question isn’t “NixOS or Talos?” It’s “which constraints do I actually want enforcing my behaviour?” For pure Kubernetes nodes, I lean Talos. For everything around them, I lean NixOS. More and more, I think the honest answer is both, each kept in the role where its limits do you a favour.
Both communities are excellent, and both systems are under active development. You can’t really go wrong with either one. Thinking declaratively about your infrastructure already puts you ahead of most.
