02

Machine config

A Talos node is defined by one YAML document: the machine config. It holds everything a traditional node spreads across cloud-init, /etc, systemd units and kubeadm flags: node identity, network, disk, kubelet settings and the cluster's PKI. You generate it once from a set of secrets, customise it with small patches, and push it with talosctl apply-config. Same inputs, same node, every time.

From secrets to three files

secrets.yaml
talosctl gen secrets

CAs, keys, tokens. The only thing that must stay secret and stable: regenerate it and you have a different cluster.

gen config
cluster name + endpoint + patches

Deterministic: same secrets and patches always yield the same configs.

controlplane.yaml
machine.type: controlplane
worker.yaml
machine.type: worker
talosconfig
client cert for talosctl (os:admin)
generate
talosctl gen secrets -o secrets.yaml        # keep this in a vault / SOPS

talosctl gen config homelab https://10.0.0.5:6443 \
  --with-secrets secrets.yaml \
  --config-patch @patches/install.yaml \
  --config-patch-control-plane @patches/cilium.yaml \
  --output-dir _out

talosctl validate --config _out/controlplane.yaml --mode metal
per-node patches, after generation
# render a node-specific file (no secrets in git if you template them)
talosctl machineconfig patch _out/controlplane.yaml \
  --patch @nodes/cp-1.yaml -o cp-1.yaml

# or patch a running node in place
talosctl -n 10.0.0.10 patch machineconfig --patch @nodes/cp-1.yaml

# or open the live config in $EDITOR
talosctl -n 10.0.0.10 edit machineconfig
v1alpha1 anatomy

Two halves: machine and cluster

machine: is about this node. cluster: is about the Kubernetes cluster and is identical across nodes (workers get a trimmed copy without the CA keys).
machine:
type
controlplane or worker. Decides whether etcd and the control-plane static pods run.
token / ca
The machine token and Talos API CA: how nodes join (via trustd) and how apid trusts clients.
certSANs
Extra names/IPs in the Talos API certificate.
kubelet
Image (= Kubernetes version), extraArgs, extraMounts, nodeIP selection.
network
Hostname, interfaces (static addresses, bonds, VLANs, VIP), routes, nameservers.
install
Target disk (or diskSelector) and installer image. Used at install and upgrade.
features
KubePrism, hostDNS, RBAC for the Talos API, Talos API access from pods…
sysctls, kernel, files, registries
Node-level tuning, kernel modules, registry mirrors and auth.
cluster:
controlPlane.endpoint
The URL every kubelet and client uses for the API server: a VIP, load balancer or DNS name, never one node.
clusterName
Name baked into kubeconfig and certs.
network
cni (flannel / none / custom URLs), podSubnets, serviceSubnets, dnsDomain.
proxy
kube-proxy settings, or disabled: true when a CNI replaces it.
apiServer / controllerManager / scheduler
Images, extraArgs, certSANs, admission config for the static pods.
etcd
CA, extraArgs, advertisedSubnets for multi-homed nodes.
discovery
Discovery registries used for node membership (and KubeSpan).
secrets
ca, aggregatorCA, serviceAccount key, secretboxEncryptionSecret, bootstrap token: the cluster's identity.
strategic merge

Patches: small, composable, reviewable

Instead of editing a 300-line generated file, keep a handful of small patches in git and merge them in. Maps merge key by key, scalars are replaced, lists are appended (network interfaces are matched by name or selector), and $patch: delete removes a key. Toggle patches and watch the merged config; each highlighted line is colored by the patch that introduced it.

Strategic-merge playground

patches (applied in order)
patches/cilium.yaml
cluster:
  network:
    cni:
      name: none
  proxy:
    disabled: true

Talos deploys Flannel and kube-proxy by default. Setting cni.name: none and proxy.disabled leaves the slot free for Cilium with kube-proxy replacement. Nodes stay NotReady until you install it.

talosctl gen config homelab https://10.0.0.5:6443 \
  --with-secrets secrets.yaml \
  --config-patch @patches/cp1-network.yaml \
  --config-patch @patches/cilium.yaml
controlplane.yaml (merged)17 lines from patches
version: v1alpha1
debug: false
persist: true
machine:
type: controlplane
token: 7k2x9f.q8wzr4h1m0c6ab3t
ca:
crt: LS0tLS1CRUdJTi…
key: LS0tLS1CRUdJTi…
certSANs: []
kubelet:
image: ghcr.io/siderolabs/kubelet:v1.34.1
defaultRuntimeSeccompProfileEnabled: true
disableManifestsDirectory: true
network:
hostname: cp-1
nameservers:
- 10.0.0.1
interfaces:
- deviceSelector:
physical: true
addresses:
- 10.0.0.10/24
routes:
- network: 0.0.0.0/0
gateway: 10.0.0.1
vip:
ip: 10.0.0.5
install:
disk: /dev/sda
image: ghcr.io/siderolabs/installer:v1.11.3
wipe: false
features:
rbac: true
stableHostname: true
apidCheckExtKeyUsage: true
diskQuotaSupport: true
kubePrism:
enabled: true
port: 7445
hostDNS:
enabled: true
forwardKubeDNSToHost: true
cluster:
id: Qm9vdHN0cmFwIElE…
secret: c2VjcmV0…
controlPlane:
endpoint: https://10.0.0.5:6443
clusterName: homelab
network:
dnsDomain: cluster.local
podSubnets:
- 10.244.0.0/16
serviceSubnets:
- 10.96.0.0/12
cni:
name: none
token: abc123.0123456789abcdef
secretboxEncryptionSecret: z8x…
ca:
crt: LS0tLS1CRUdJTi…
key: LS0tLS1CRUdJTi…
apiServer:
image: registry.k8s.io/kube-apiserver:v1.34.1
certSANs:
- 10.0.0.5
controllerManager:
image: registry.k8s.io/kube-controller-manager:v1.34.1
proxy:
image: registry.k8s.io/kube-proxy:v1.34.1
disabled: true
scheduler:
image: registry.k8s.io/kube-scheduler:v1.34.1
discovery:
enabled: true
registries:
kubernetes:
disabled: true
service: {}
etcd:
ca:
crt: LS0tLS1CRUdJTi…
key: LS0tLS1CRUdJTi…
Beyond one document
Since Talos 1.5 a config file can contain several YAML documents separated by ---: the classic v1alpha1 document plus typed documents such as ExtensionServiceConfig, and in newer releases VolumeConfig / UserVolumeConfig for disk layout. Recent releases keep moving settings (e.g. parts of networking) into their own documents. Patches can target either kind.
patches/cilium.yaml
cluster:
  network:
    cni:
      name: none      # Talos won't deploy flannel
  proxy:
    disabled: true    # Cilium replaces kube-proxy
machine:
  features:
    kubePrism:
      enabled: true   # default in generated configs
      port: 7445      # Cilium's k8sServiceHost=localhost, k8sServicePort=7445
a multi-document patch
machine:
  nodeLabels:
    node.kubernetes.io/storage: longhorn
---
apiVersion: v1alpha1
kind: UserVolumeConfig
name: longhorn
provisioning:
  diskSelector:
    match: disk.transport == "nvme"
  minSize: 100GiB
---
apiVersion: v1alpha1
kind: ExtensionServiceConfig
name: nut-client
environment:
  - UPS_HOST=10.0.0.2
apply-config --mode

Applying a change: does it need a reboot?

Most of the config (network, kubelet, labels, sysctls, the whole cluster: section) is reconciled live by machined's controllers. A few fields only take effect at boot. The mode decides what happens when you mix them.

Apply modes

what changed in the config
--mode

Default. Apply live if every change supports it, otherwise apply and reboot.

$talosctl -n 10.0.0.21 apply-config -f worker.yaml --mode auto
STATE partition (persisted)
config v1
running config (in machined)
config v1
Pick changes and a mode, then apply.

Change categories follow the Talos docs on which fields support live apply; when in doubt, --dry-run prints the diff and whether a reboot would be needed. Modes shown are those of Talos ≤1.13; Talos 1.14 removed --mode=reboot, and the default now applies without rebooting (reboot-only changes wait for your next reboot). Try-mode timer runs at 10× speed here.

first apply vs later applies
# maintenance mode (fresh ISO boot): no certs yet, so --insecure
talosctl apply-config --insecure -n 10.0.0.10 -f cp-1.yaml

# afterwards: authenticated, and preview first
talosctl -n 10.0.0.10 apply-config -f cp-1.yaml --dry-run
talosctl -n 10.0.0.10 apply-config -f cp-1.yaml --mode try --timeout 2m
  • why it matterssecrets.yaml is the cluster's identity; everything else is derived. Back it up, and you can regenerate every node config at will.
  • why it mattersPatches keep intent small and reviewable: "use Cilium", "static IP for cp-1". The generated base can change between Talos versions without merge conflicts in your repo.
  • why it mattersMost changes apply live because machined reconciles them; use --dry-run to see the diff and --mode try for changes that could cut you off.