Every Cluster API release before v1.12 gave you exactly two verbs for changing a node: create and delete. Rotate a credential, bump a disk size, patch a config file — it didn't matter how small the change was, Cluster API's answer was the same: delete the Machine, create a new one, wait for it to join. On a cloud autoscaling group that's a shrug. On a bare-metal Hetzner box, it's a real re-provision cycle, and a small self-hosted fleet usually doesn't have spare capacity sitting idle to surge into while the replacement boots.
Cluster API v1.12, released January 27, 2026, ships two features aimed directly at that gap: in-place updates, which let a Machine change without being deleted, and chained upgrades, which let a fleet jump several Kubernetes minor versions in a single declared step instead of walking through each one by hand. Here's how both actually work, what they don't cover yet, and what changes operationally for a fleet running on owned Hetzner machines through Cluster API Provider Hetzner (CAPH).
Why node-replacement-only was a bare-metal tax
Cluster API's original design leaned on exactly two primitives — create and delete — because that model is simple and provider-agnostic. It doesn't care about a Machine's OS, bootstrap mechanism, or hardware; it just tears down the old one and stands up a new one that matches the desired spec. On a cloud provider where a VM is an API call away, that simplicity is close to free.
Bare metal breaks that assumption. In CAPH, a cloud VM is an HCloudMachine you can provision on demand, but a dedicated Hetzner server is a HetznerBareMetalHost — a physical box tied to your account's inventory, not something that spins up fresh on request. That's also why CAPH's own remediation logic treats replacement as the expensive fallback: custom remediation for bare-metal control planes tries a reboot first and only unprovisions and re-provisions the host if the reboot doesn't recover it. If reboot-before-replace is the remediation order for a failure, replace was never going to be the cheap path for a routine change either.
Cluster API v1.12 also shipped two changes that soften this without eliminating a rollout entirely: a delete-first rollout strategy that makes immutable rollouts easier to sequence on bare metal and other resource-constrained environments, and tainting outdated nodes with PreferNoSchedule so pods drift off a node marked for replacement gradually, instead of every pod on the fleet getting rescheduled at once when a rollout starts. Both help. Neither is the same as not replacing the node at all — that's what in-place updates are for.
How in-place updates actually work
In-place updates are built around a new Lifecycle Hook called ExternalUpdate, described in the in-place updates proposal. It exposes two runtime extension endpoints:
CanUpdateMachine— given a Machine's current spec, desired spec, and the list of changed fields, an external updater reports back which of those changes it knows how to apply in place.UpdateMachine— given the desired spec, the updater actually performs the change and reportsSuccess,Error, orInProgress(with a retry interval) back to Cluster API.
Multiple updaters can register at once, each covering a different subset of changes — one might own credential rotation, another might own resource sizing. Cluster API asks every registered updater what it can handle; if the combined set of updaters covers every changed field, it runs the in-place path. If any field isn't covered, Cluster API falls back to the immutable rollout it always used — delete and recreate, now with the softer delete-first sequencing and PreferNoSchedule tainting described above.
The whole mechanism sits behind a feature gate, InPlaceUpdates, and it's off by default — a fleet operator has to explicitly enable it. That's a deliberate signal about maturity: this is new machinery, not a drop-in replacement for the rollout path yet.
The scope is intentionally narrow. Eligible in-place changes are things that don't require draining the node or restarting its running pods — Kubernetes version bumps, memory/CPU adjustments, config file edits, credential updates. Anything touching the underlying VM/machine template — an OS image swap, for instance — still isn't a candidate; Cluster API routes those to immutable rollout automatically, because no updater is expected to claim them.
The part that matters if you're running CAPH specifically: the reference implementation shipped alongside v1.12 is a Kubeadm updater for CAPD — Cluster API's Docker-based development and testing provider, not a production infrastructure provider. There's no published ExternalUpdate implementation for CAPH as of this writing. In practice that means a Hetzner-backed fleet gets the InPlaceUpdates feature gate and the CanUpdateMachine/UpdateMachine contract to build against, but not an out-of-the-box in-place path yet — either CAPH ships an updater covering the changes you care about, or a self-hosted operator writes one against the same runtime extension contract CAPD's reference implementation uses.
How chained upgrades compute a plan
Chained upgrades solve a different, provider-agnostic problem: getting an entire fleet from Kubernetes v1.30 to v1.35, say, without the operator manually declaring v1.31, checking health, declaring v1.32, checking health, and repeating that four more times.
The mechanism is a runtime hook called GenerateUpgradePlan. Cluster API calls it — repeatedly, recomputing as the upgrade progresses — with a request containing the cluster's current control-plane and worker versions (fromControlPlaneKubernetesVersion, fromWorkersKubernetesVersion) and the operator's declared target (toKubernetesVersion). The hook responds with two arrays: controlPlaneUpgrades and workersUpgrades.
Control-plane machines have to step through every intermediate minor — that's a hard constraint of how kube-apiserver version skew works, and the response is validated accordingly (each entry strictly greater than the last, ending at the target). Worker machines don't have that constraint to the same degree.
KEP-3935 extended the kubelet-to-apiserver version skew window, and Cluster API's plan generation takes advantage of it: if the response omits workersUpgrades entirely, Cluster API computes the minimal set of steps automatically, skipping intermediate minors whenever the skew policy allows it. Concretely, a v1.30→v1.35 jump might route control-plane machines through v1.31, v1.32, v1.33, v1.34, v1.35 — five sequential steps — while worker machines skip straight from v1.30 to v1.32 to v1.35, or even further in a single hop, because the skew policy tolerates a wider gap on the worker side than it does between two control-plane components talking to each other directly.
The result: an operator who used to babysit five sequential minor bumps across a fleet now declares one target version, and the controllers walk the compliant path — including skipping steps on workers where the skew policy allows it — without a human re-triggering each hop.
What this changes for a Hetzner-backed fleet operator
Put both features against a concrete small fleet — three control-plane nodes and five workers, all bare-metal Hetzner boxes managed by CAPH, which is close to a typical footprint for a self-hosted PaaS control plane.
Before v1.12: a Kubernetes version bump from v1.30 to v1.35 means declaring v1.31, waiting for CAPI to drain and replace all eight nodes sequentially (bare metal, so each replacement is a real reboot-or-reprovision cycle, not an elastic VM swap), confirming cluster health, then repeating that four more times. A routine change like rotating a node credential means the exact same drain-and-replace cycle as a full version bump — Cluster API has no cheaper path for a one-line config change than it does for a five-minor jump.
After v1.12, with InPlaceUpdates enabled and a suitable ExternalUpdate extension wired up: the credential rotation and config edits that a CanUpdateMachine implementation claims apply directly to the running Machine — no drain, no reboot, no reprovision cycle at all. Changes no extension claims — an OS image swap, for instance — still fall back to the immutable rollout, but now with delete-first sequencing and PreferNoSchedule tainting softening the blast radius across the fleet. As covered above, CAPH doesn't ship that extension yet, so this half of the win is available to build against today, not to flip on today.
Independently of that, the v1.30→v1.35 jump becomes one declared target version instead of five manually-triggered hops — this part works today, regardless of provider, because chained upgrades live in core Cluster API's plan computation and version-skew handling, not in a provider-supplied extension. That's the piece a CAPH-managed fleet gets immediately.
Why this is the line, not a nice-to-have
A single-box Docker deploy tool doesn't have this problem because it doesn't have a fleet lifecycle to manage in the first place — there's one box, and "upgrade" means SSH in and run a command. The moment a platform is managing a fleet of Machines declaratively, node-replacement-only starts costing real wall-clock time and real risk on every routine change, and that cost scales with fleet size and how far from an elastic cloud VM the underlying hardware is. Chained upgrades and in-place updates are Cluster API admitting that "just delete and recreate" was never actually free — it was a simplifying assumption that held up fine on cloud VMs and got expensive exactly where bare metal and small self-hosted fleets live.
Bex.co is the open-source, AI-native Render alternative, built on Cluster API and Cluster API Provider Hetzner — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.
Sources
- Cluster API v1.12: Introducing In-place Updates and Chained Upgrades — Kubernetes Blog
- Cluster API v1.12: Introducing in-place updates and chained upgrades — CNCF Blog
- In-place updates proposal — kubernetes-sigs/cluster-api
- Chained and efficient upgrades for clusters with managed topologies — proposal
- Implementing Upgrade Plan Runtime Extensions — The Cluster API Book
- Kubernetes Version Skew Policy
- Cluster API Provider Hetzner bare-metal host reference
- Cluster API v1.12.0 release notes — GitHub