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 [Bare metal and cloud](/docs/hetzner/apalla/concepts/internals/bare-metal-and-cloud) 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 | [Add bare-metal servers](/docs/hetzner/apalla/servers-and-nodes/provision/add-bare-metal-servers) | | `workeramd64hcloud` | Hetzner Cloud VMs, created and destroyed on demand | [Add cloud servers](/docs/hetzner/apalla/servers-and-nodes/provision/add-cloud-servers) | "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 [Add GPU nodes](/docs/hetzner/apalla/servers-and-nodes/provision/gpu-nodes). 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 [Use local storage on bare metal](/docs/hetzner/apalla/storage/local/local-nvme-with-topolvm). 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 [Run databases](/docs/hetzner/apalla/workloads/stateful/run-databases). ## Steer each tier Label each pool so workloads land on the right hardware (see [Label nodes and assign roles](/docs/hetzner/apalla/servers-and-nodes/pools/label-nodes-and-assign-roles)), then select those labels from your pods with [node affinity](/docs/hetzner/apalla/servers-and-nodes/scheduling/node-affinity-and-selectors). 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 [Add bare-metal servers](/docs/hetzner/apalla/servers-and-nodes/provision/add-bare-metal-servers). ## 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. Self-healing also differs by tier. On cloud, Syself Autopilot first tries a reboot through the Hetzner API. If that fails, it deletes the VM and creates a new one. On bare metal, Syself Autopilot first tries a hardware power-cycle through Hetzner Robot. If that fails, it releases the host back to the pool, so a healthy replacement can claim a free machine instead. A bare-metal worker that comes up cleanly is ready in a few minutes. This only helps when another free host matches the pool's selector. If the selector is tight enough that no other host qualifies, the replacement re-claims the same machine, and a hardware fault returns after the reinstall. Self-healing cannot repair hardware; see [Self-healing does not repair hardware](/docs/hetzner/apalla/servers-and-nodes/maintenance/machine-health-checks-and-remediation#self-healing-does-not-repair-hardware) for how to catch one. ## 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 [Size a cluster for cost](/docs/hetzner/apalla/servers-and-nodes/fleet/size-a-cluster-for-cost).