Skip to main content

Mix cloud and bare-metal pools

Inspect 1.36

One cluster can run both bare-metal and cloud pools at the same time. A fixed bare-metal base carries the steady load, and an elastic cloud pool absorbs the spikes. This combination is often the cheapest and most reliable way to run production on Hetzner. The main decision is which workload belongs on which tier.

See for how each backend provisions a machine.

Two classes, one cluster#

Add both classes as separate pools under spec.topology.workers.machineDeployments. Each pool has one class, one machine type, and its own labels and scaling.

Class Machines Guide
workeramd64baremetal Dedicated Hetzner Robot servers you own, claimed from and released back to your host inventory
workeramd64hcloud Hetzner Cloud VMs, created and destroyed on demand

"Created" versus "claimed" is the root difference between the two tiers. Cloud is elastic: Syself Autopilot calls the Hetzner API to make a new VM when you scale up, and deletes it when you scale down. Bare metal is a fixed pool: you already bought the servers, so scaling up claims a free HetznerBareMetalHost (the object that represents one physical server), and scaling down releases it for reuse.

Almost every other difference between the two tiers follows from that one fact: provisioning time, rollout order, and self-healing behavior.

Which workload belongs where#

Bare metal Cloud
Stateful workloads, databases, GPU and other compute-intensive work Elastic and bursty work
Local NVMe (fast, tied to that one server) Fast provisioning, ready in a few minutes
Highest raw CPU and memory per machine Scale to zero when idle
No noisy-neighbor risk (a dedicated machine runs only your pods) Pay per VM while running

Bare metal keeps data on local disks that survive a reprovision of that same server, which is exactly what stateful services want. Cloud gives you machines in minutes and hands them back when the spike passes.

Bare-metal nodes also give you the highest raw CPU and memory per machine. That is why GPU workloads belong there too: there is no Hetzner Cloud GPU server type. See .

Storage differs by tier, with a different StorageClass for each:

Tier StorageClass Backing
Cloud standard (default) Hetzner Cloud volumes, network storage, through the CSI (Container Storage Interface) driver
Bare metal local-nvme, local-ssd, local-hdd Local disks, through TopoLVM, a storage driver that turns a server's disks into Kubernetes volumes
Note

Hetzner Cloud volumes attach to cloud servers only; there is no way to attach one to a bare-metal server. A pod claiming the standard class must therefore land on a cloud node, so pin volume-backed workloads to the cloud tier.

Note

TopoLVM is not part of the cluster stack. You install and operate it yourself. See . The standard cloud StorageClass needs no setup.

Warning

A local volume survives a reprovision of the same server: Syself rewrites the OS disk but leaves the data disks alone. It does not follow the workload to a different server. Run an application that replicates its own data across servers, such as CloudNativePG (a PostgreSQL operator) for a database, or back the data up elsewhere. See .

Steer each tier#

Label each pool so workloads land on the right hardware (see ), then select those labels from your pods with . Syself Autopilot also sets system labels you can select on directly, including autopilot.syself.com/machine-type (baremetal or hcloud) and instance.hetzner.cloud/is-root-server: "true" on bare metal.

Tip

Pool labels (metadata.labels on a machineDeployments entry) steer pods to nodes. They are a separate mechanism from workerHostSelectorBareMetal, which steers a bare-metal pool to specific HetznerBareMetalHost objects. By default, any free host can be claimed by any bare-metal pool. If you run more than one bare-metal pool, or share a namespace with another cluster, label your hosts and set workerHostSelectorBareMetal so each pool claims the right machines. See .

Rollout differences#

The two classes roll out differently. This matters when an image update or a class change replaces nodes:

  • Cloud rolls with maxSurge=1: a new node comes up before the old one goes away, so capacity never dips.
  • Bare metal rolls with maxSurge=0 and maxUnavailable=1: the old host is freed first, then reinstalled, one node at a time.
Warning

During a bare-metal rollout the pool runs at reduced capacity, because each host is taken down before its replacement is built. Size the pool so the remaining nodes can carry the load.

A worked hybrid layout#

A steady bare-metal base carries the always-on load. A cloud pool on the autoscaler absorbs peaks. The bare-metal pool uses workerHostSelectorBareMetal so it only claims hosts you labelled for this cluster:

yaml
		workers:
  machineDeployments:
    - class: workeramd64baremetal
      name: md-base
      replicas: 3
      metadata:
        labels:
          node.cluster.x-k8s.io/tier: baseload
      variables:
        overrides:
          - name: workerHostSelectorBareMetal
            value:
              matchLabels:
                cluster: mycluster
    - class: workeramd64hcloud
      name: md-burst
      metadata:
        annotations:
          cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size: "0"
          cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: "10"
        labels:
          node.cluster.x-k8s.io/tier: burst
      variables:
        overrides:
          - name: workerMachineTypeHcloud
            value: cpx42
	

Pin databases and stateful sets to tier: baseload, and let stateless, bursty work spill onto tier: burst. For the economics, see .