Talos Linux is a minimal, immutable operating system built only for Kubernetes.
It has no SSH server, interactive shell, or package manager. Nodes are configured
declaratively and managed through the authenticated Talos API with talosctl.
K3s or RKE2 on Ubuntu is familiar and flexible, but it leaves two platforms to operate: the Linux servers and Kubernetes. That includes SSH access, users, packages, repositories, systemd services, patching, hardening, and configuration drift.
Talos narrows the node to one purpose:
| Ubuntu with K3s/RKE2 | Talos Linux |
|---|---|
| SSH and shell administration | Certificate-authenticated API |
| Mutable packages and files | Immutable, image-based OS |
| Ansible/cloud-init configures the host | Declarative machine configuration |
| Package-by-package updates | Atomic image upgrades and rollback |
| General-purpose troubleshooting tools | talosctl resources, logs, and diagnostics |
This reduces the node attack surface and configuration drift. It does not replace Kubernetes RBAC, admission policies, NetworkPolicy, secrets management, backups, monitoring, or workload security.
Talos is a good fit when nodes run only Kubernetes, supported hardware is used, and replacement is preferred over manual repair. A general-purpose distribution may remain simpler for unusual drivers, host-installed vendor agents, or software that expects systemd and a conventional filesystem.
A practical highly available homelab uses three similar x86-64 mini PCs, each connected by wired Ethernet. All three can run the control plane, etcd, and workloads.
Before installation:
talosctl and kubectl versions on the workstation;Example address plan:
| Name | Address | Role |
|---|---|---|
node-1 | 192.168.1.111 | Control plane, etcd, workloads |
node-2 | 192.168.1.112 | Control plane, etcd, workloads |
node-3 | 192.168.1.113 | Control plane, etcd, workloads |
| Kubernetes API VIP | 192.168.1.200 | Stable API endpoint |
| Internal Gateway | 192.168.1.201 | Optional Cilium LoadBalancer IP |
Three etcd members provide quorum while one member is unavailable. The API VIP
moves to a healthy control-plane node, so kubectl does not depend on one
machine. talosctl, however, normally targets the individual node addresses.
Keep these paths distinct:
talosctl โ each node on TCP 50000;kubectl โ API VIP on TCP 6443;The API VIP must be on the same layer-2 network as the control-plane interfaces. For routed or cloud environments, use a supported load balancer instead.
Boot each machine from the Talos metal ISO or USB image. In maintenance mode, record the node address, wired interface, and installation disk:
1export NODE1=192.168.1.111
2export NODE2=192.168.1.112
3export NODE3=192.168.1.113
4export VIP=192.168.1.200
5
6for node in "$NODE1" "$NODE2" "$NODE3"; do
7 talosctl --nodes "$node" get links --insecure
8 talosctl --nodes "$node" get disks --insecure
9done
Generate one cluster configuration locally and protect the generated credentials:
1umask 077
2mkdir -p talos/patches talos/generated/nodes
3
4talosctl gen config homelab "https://${VIP}:6443" \
5 --output-dir talos/generated
Create one patch per node. Change the disk, interface, and hostname:
1machine:
2 install:
3 disk: /dev/nvme0n1
4 network:
5 interfaces:
6 - interface: enp1s0
7 dhcp: true
8 vip:
9 ip: 192.168.1.200
10---
11apiVersion: v1alpha1
12kind: HostnameConfig
13hostname: node-1
14auto: off
Patch and validate each machine configuration before applying it:
1for node in node-1 node-2 node-3; do
2 talosctl machineconfig patch talos/generated/controlplane.yaml \
3 --patch "@talos/patches/${node}.yaml" \
4 --output "talos/generated/nodes/${node}.yaml" || break
5 talosctl validate \
6 --config "talos/generated/nodes/${node}.yaml" \
7 --mode metal || break
8done
Do not commit controlplane.yaml, worker.yaml, talosconfig, kubeconfig, or
rendered node configurations. They contain cluster credentials.
Apply each configuration while its node is still in maintenance mode:
1talosctl apply-config --insecure --nodes "$NODE1" \
2 --file talos/generated/nodes/node-1.yaml
3talosctl apply-config --insecure --nodes "$NODE2" \
4 --file talos/generated/nodes/node-2.yaml
5talosctl apply-config --insecure --nodes "$NODE3" \
6 --file talos/generated/nodes/node-3.yaml
Remove the installation media and boot from the internal disks.
Configure several Talos API endpoints, then bootstrap one node once:
1export TALOSCONFIG="$PWD/talos/generated/talosconfig"
2
3talosctl config endpoint "$NODE1" "$NODE2" "$NODE3"
4talosctl config node "$NODE1"
5talosctl version
6
7talosctl bootstrap --nodes "$NODE1"
8talosctl health
Export the Kubernetes client configuration and inspect the cluster:
1talosctl kubeconfig ./talos/generated/kubeconfig --nodes "$NODE1"
2export KUBECONFIG="$PWD/talos/generated/kubeconfig"
3
4kubectl cluster-info
5kubectl get nodes -o wide
6kubectl get pods --all-namespaces
For a small lab where control-plane nodes also run applications, apply this machine patch and include it whenever configurations are regenerated:
1cluster:
2 allowSchedulingOnControlPlanes: true
1talosctl --nodes "$NODE1,$NODE2,$NODE3" patch machineconfig \
2 --patch @talos/patches/allow-workloads.yaml
Check Talos, Kubernetes, etcd, DNS, and workload scheduling:
1talosctl health
2talosctl --nodes "$NODE1" etcd members
3kubectl get nodes -o wide
4kubectl get pods --all-namespaces
5
6kubectl create deployment web-test --image=nginx --replicas=3
7kubectl rollout status deployment/web-test
8kubectl expose deployment web-test --port=80
9kubectl run curl-test --rm -it --restart=Never --image=curlimages/curl \
10 -- curl --fail http://web-test
11kubectl delete service web-test
12kubectl delete deployment web-test
To test high availability, begin with all nodes healthy and stop only one:
1talosctl health
2talosctl --nodes "$NODE1" shutdown
3kubectl get nodes
4kubectl cluster-info
The API VIP should remain available through either surviving node. Power the
stopped node back on, wait until all three nodes are Ready, and run
talosctl health again before any further failure test.
Start from the management layer that is still available. The Talos API is
separate from the Kubernetes API, so it can often diagnose a failed control
plane even when kubectl no longer works.
1# Overview
2talosctl --nodes "$NODE1" dashboard
3talosctl health
4talosctl --nodes "$NODE1" service
5
6# Service and kernel logs
7talosctl --nodes "$NODE1" service kubelet
8talosctl --nodes "$NODE1" logs kubelet --tail 100
9talosctl --nodes "$NODE1" dmesg
10
11# Talos resources
12talosctl --nodes "$NODE1" get links
13talosctl --nodes "$NODE1" get addresses
14talosctl --nodes "$NODE1" get routes
15talosctl --nodes "$NODE1" get disks
16talosctl --nodes "$NODE1" get mounts
17
18# Runtime and capacity
19talosctl --nodes "$NODE1" processes
20talosctl --nodes "$NODE1" memory
21talosctl --nodes "$NODE1" stats
22talosctl --nodes "$NODE1" usage
When the Kubernetes API is unavailable, inspect its underlying services and containers through Talos:
1talosctl --nodes "$NODE1" service etcd
2talosctl --nodes "$NODE1" logs etcd --tail 100
3talosctl --nodes "$NODE1" etcd status
4talosctl --nodes "$NODE1" etcd alarm list
5talosctl --nodes "$NODE1" service containerd
6talosctl --nodes "$NODE1" containers --kubernetes
For network incidents, inspect state first and capture packets to the local workstation instead of installing tools on the node:
1talosctl --nodes "$NODE1" netstat --all
2talosctl --nodes "$NODE1" pcap --interface enp1s0 \
3 --output talos-node-1.pcap
Collect a support bundle before rebooting or restarting services, because a recovery action can erase useful evidence:
1talosctl support --nodes "$NODE1,$NODE2,$NODE3" \
2 --output talos-support.zip
Treat support bundles as sensitive operational data. If the Talos API itself is
unreachable, use the local video console, hypervisor console, or out-of-band KVM.
Keep tested copies of the machine configurations, talosconfig, etcd snapshots,
and node replacement procedure outside the cluster.
Cilium can replace the default CNI and kube-proxy, enforce NetworkPolicy, and
provide LoadBalancer IPAM, layer-2 announcements, Gateway API, and Hubble flow
visibility. Plan the following before bootstrap or migration:
If Tailscale traffic enters through tailscale0, include that interface in
Cilium’s device list. Pin the intended Cilium MTU rather than allowing the
Tailscale interface’s smaller MTU to resize the pod network unexpectedly.
Tailscale runs as a Talos system extension, not as a package installed on the immutable host. Build a Talos Image Factory schematic containing the official extension:
1customization:
2 systemExtensions:
3 officialExtensions:
4 - siderolabs/tailscale
1curl --silent --request POST --data-binary @tailscale/schematic.yaml \
2 https://factory.talos.dev/schematics
Use the returned schematic ID to construct the installer image for the exact Talos version being deployed:
1factory.talos.dev/metal-installer/<schematic-id>:<talos-version>
Configure the extension before upgrading a running node to that image. Store the rendered configuration outside Git because it contains the auth key:
1apiVersion: v1alpha1
2kind: ExtensionServiceConfig
3name: tailscale
4environment:
5 - TS_AUTHKEY=<tailscale-auth-key>
6 - TS_HOSTNAME=node-1
7 - TS_ROUTES=192.168.1.111/32,192.168.1.112/32,192.168.1.113/32,192.168.1.200/32,192.168.1.201/32
8 - TS_ACCEPT_DNS=false
9 - TS_AUTH_ONCE=true
Also constrain Kubernetes and etcd to the LAN. Otherwise a node may advertise
its Tailscale 100.x address as its Kubernetes internal IP or etcd peer address:
1machine:
2 kubelet:
3 nodeIP:
4 validSubnets:
5 - 192.168.1.0/24
6cluster:
7 etcd:
8 advertisedSubnets:
9 - 192.168.1.0/24
Apply the node-specific extension configuration and LAN-address patch without a reboot, then upgrade one node at a time with the custom installer image:
1talosctl --nodes "$NODE1" patch machineconfig \
2 --patch @tailscale/node-ip.yaml \
3 --patch @tailscale/node-1.yaml \
4 --mode no-reboot
5
6talosctl --nodes "$NODE1" upgrade \
7 --image factory.talos.dev/metal-installer/<schematic-id>:<talos-version>
8kubectl wait --for=condition=Ready node/node-1 --timeout=10m
Verify that the extension runs while Kubernetes and etcd keep their LAN addresses:
1talosctl --nodes "$NODE1" get extensions
2talosctl --nodes "$NODE1" services
3kubectl get nodes -o wide
4talosctl --nodes "$NODE1" etcd members
Every later Talos upgrade must continue using an installer image containing the required extensions. Revoke the reusable Tailscale auth key after all nodes have joined; the nodes retain their state.
Advertise only the routes remote operators need, preferably individual /32
addresses rather than the entire home LAN:
50000;6443;80 and 443.Approve only those routes in the Tailscale policy and restrict tailnet members to the matching destination ports. Deny access to the router and unrelated LAN devices. Linux clients must enable route acceptance:
1sudo tailscale up --accept-routes
2tailscale status
All three nodes may advertise the same routes so Tailscale can fail over between subnet routers. Test from outside the home network, then reboot the active router and confirm that API and application traffic recover through another node.