I’ve written about Talos Linux as the immutable Kubernetes OS, and I’ve compared Arch vs NixOS for workstations. But there’s a question I get asked often: what about NixOS for Kubernetes nodes?

Both NixOS and Talos are declarative. Both can be immutable. Both version their configuration. So why would you choose one over the other for running Kubernetes?

I’ve run both in production. Here’s what I’ve learned.

The Philosophical Difference

Before diving into specifics, understand the core difference:

Talos Linux is a single-purpose operating system. It exists to run Kubernetes. Nothing else. Every design decision optimizes for that one goal.

NixOS is a general-purpose operating system that happens to be excellent at running Kubernetes. It can do anything a traditional Linux can do, but with declarative configuration.

This isn’t just a marketing distinction. It fundamentally shapes what each system is good at.

Configuration Models

Talos: YAML All The Way

Talos configuration is straightforward 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. Simple, 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 but learnable in an afternoon. NixOS’s Nix language is powerful but takes weeks to become comfortable with.

If all you need is Kubernetes, Talos’s simplicity wins. If you need Kubernetes plus other things, NixOS’s flexibility is worth the learning curve.

Security Posture

Talos: Secure by Removal

Talos achieves security by removing 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… nothing useful. No tools to pivot with, no persistence mechanisms, no way to install malware.

NixOS: Secure by Configuration

NixOS can be hardened significantly, but it’s 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
}

NixOS’s /nix/store is always read-only, which is great. But the rest of the filesystem is mutable by default. You can make it immutable with additional configuration, but it’s not the default.

The Trade-off

Talos is secure out of the box. NixOS can be made secure, but requires deliberate configuration. For Kubernetes nodes specifically, Talos’s paranoid defaults are probably what you want.

Flexibility vs Focus

This is where the systems diverge most dramatically.

What NixOS Can Do That Talos Can’t

With NixOS, your Kubernetes node can also run:

  • System services: Prometheus node exporter, log shippers, monitoring agents — as systemd services, not pods
  • Non-containerized workloads: Sometimes you need a bare-metal database or legacy app alongside Kubernetes
  • Custom tooling: Install any package from nixpkgs (80,000+ packages)
  • Development tools: If you need to debug on the node itself, you can install tools

Example: running a local PostgreSQL alongside 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 features:

  • No drift: You literally cannot make undocumented changes
  • Forced discipline: Every change goes through config → API → apply
  • Simpler mental model: The node is the config, nothing else
  • Smaller attack surface: What doesn’t exist can’t be exploited

The Trade-off

If your nodes only run Kubernetes workloads, Talos’s restrictions prevent mistakes. If your nodes need to do more, NixOS’s flexibility is necessary.

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 it fails to boot, automatic rollback to the previous version. The upgrade is atomic — it either fully succeeds or fully fails.

NixOS: Generation-Based Rollback

nixos-rebuild switch --flake .#node-01

NixOS creates a new “generation” for each configuration change. Every generation is bootable from GRUB. Rolling back is selecting a previous generation:

nixos-rebuild switch --rollback
# Or select from boot menu

NixOS keeps multiple generations by default, so you can roll back to configurations from weeks ago.

The Trade-off

Both have excellent rollback stories. Talos is more automatic (failed boots trigger rollback). NixOS is more flexible (keep arbitrary generations, roll back partially).

Kubernetes Integration

Talos: Built for Kubernetes

Talos includes everything needed for Kubernetes:

  • Kubernetes components are built-in
  • CNI configuration is part of machine config
  • etcd management is handled automatically
  • 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 offers 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 choose how Kubernetes is deployed. This means more decisions but also more options.

The Trade-off

Talos’s opinionated integration means fewer decisions and better defaults for Kubernetes. NixOS’s flexibility means you can integrate Kubernetes however you want, but you need to make those decisions.

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 through the API. No SSH, no shell. For emergencies, there’s a debug container feature, but it’s intentionally inconvenient.

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 system. All your familiar tools work. This is comfortable but potentially dangerous — every SSH session is a chance to create undocumented changes.

The Trade-off

Talos forces you to debug through proper channels (API, logs, metrics). NixOS lets you debug however you want, including ways that might cause drift.

My Recommendation

After running both, here’s my decision framework:

Choose Talos When:

  • Kubernetes is the only workload on these nodes
  • Security is paramount — you want minimal attack surface
  • You want guardrails — preventing drift is more important 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 not 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 actually use both:

  • Homelab Kubernetes: Talos. The nodes only run Kubernetes, and I want the security and simplicity.
  • Homelab auxiliary servers: NixOS. These run things like Postgres, backup services, and monitoring infrastructure that I don’t want in Kubernetes.

This combination gives me the best of both worlds: locked-down Kubernetes nodes that can’t drift, plus flexible NixOS machines for everything else.

The Hybrid Approach

Some teams run NixOS for control plane nodes (where they want more debugging access) and Talos for worker nodes (where they want maximum security). This can work, but adds operational complexity.

Another option: start with NixOS for familiarity, then migrate to Talos as your team matures in declarative operations. NixOS teaches the mental model; Talos enforces it.

The KubeVirt Option: Kubernetes as the Control Plane for Everything

There’s a third approach that deserves mention: using KubeVirt to run NixOS as VMs inside Kubernetes. This flips the model — instead of choosing between NixOS and Talos for your nodes, you use Talos (or any minimal OS) for the bare metal, then run NixOS workloads as VMs managed by Kubernetes.

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

Managed by ArgoCD, this VM definition lives in Git. The NixOS configuration inside the VM also lives in Git. Everything is declarative, auditable, and version-controlled.

The Full Declarative Stack

Combine this with:

  • Ansible for bare metal provisioning (install Talos/K3s on physical nodes)
  • ArgoCD for managing everything in Kubernetes (including KubeVirt VMs)
  • NixOS flakes for VM configurations (pulled from Git during VM boot)

The result: 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.

When This Makes Sense

This approach shines when:

  • You need mixed workloads (some containerized, some requiring full VMs)
  • You want one control plane (Kubernetes) for everything
  • You have workloads that can’t be containerized but still want GitOps
  • You’re already running KubeVirt for other reasons

The trade-off is complexity. You’re now running a virtualization layer inside Kubernetes. But if you need VMs anyway, having them managed declaratively through the same GitOps pipeline as everything else is powerful.

Example: Database on NixOS VM

Say you want a PostgreSQL server with specific kernel tuning that doesn’t fit in a container. Instead of maintaining a separate NixOS server:

  1. Define the VM in a KubeVirt manifest (in Git)
  2. The VM boots NixOS, pulls its config from a flake (in Git)
  3. ArgoCD manages the VM lifecycle
  4. Backups, monitoring, networking — all through Kubernetes

The database gets NixOS’s flexibility. You get Kubernetes’s orchestration. Everything is in Git.

Conclusion

Both NixOS and Talos represent the future of infrastructure: declarative, reproducible, version-controlled. The choice between them isn’t about which is “better” — it’s about which constraints serve your needs.

Talos constrains you to Kubernetes-only, API-only, minimal-surface operations. Those constraints are features if you want a secure, drift-free Kubernetes platform.

NixOS constrains you to declarative configuration while preserving full Linux flexibility. Those constraints are features if you need that flexibility.

The question isn’t “NixOS or Talos?” It’s “What constraints do I want?”

For pure Kubernetes nodes, I lean Talos. For everything else, I lean NixOS. And increasingly, I think the right answer is both — each in its proper role.


Both communities are excellent. Both systems are actively developed. You can’t really go wrong with either — you’re already ahead of the curve by thinking declaratively about your infrastructure.