Generate the cluster.yaml
The `clusterctl generate cluster` command returns a YAML template for creating a workload cluster.
It generates a YAML file named `my-cluster.yaml` with a predefined list of Cluster API objects (`Cluster`, `Machines`, `MachineDeployments`, etc.) to be deployed in the current namespace.
```shell
clusterctl generate cluster my-cluster --kubernetes-version v1.36.0 --control-plane-machine-count=3 --worker-machine-count=3 > my-cluster.yaml
```
> [!NOTE]
> With the `--target-namespace` flag, you can specify a different target namespace.
>
> Run the `clusterctl generate cluster --help` command for more information.
> [!NOTE]
> Please note that ready-to-use Kubernetes configurations, production-ready node images, kubeadm configuration, cluster add-ons like CNI, and similar services need to be separately prepared or acquired to ensure a comprehensive and secure Kubernetes deployment. This is where **Syself Autopilot** comes into play, taking on these challenges to offer you a seamless, worry-free Kubernetes experience. Feel free to contact us via e-mail: .
Apply the workload cluster
The following command applies the configuration of the workload cluster:
```shell
kubectl apply -f my-cluster.yaml
```
Access the workload cluster
The cluster will now start provisioning. You can check status with:
```shell
kubectl get cluster
```
You can also view the cluster and its resources at a glance by running:
```shell
clusterctl describe cluster my-cluster
```
To verify the first control plane is up, use the following command:
```shell
kubectl get kubeadmcontrolplane
```
Wait until the `INITIALIZED` column reports `True` before moving on. The `READY` column will stay `False` until we install a CNI in the next step — that is expected. If you'd like to follow the progression live, run `kubectl get kubeadmcontrolplane -w`.
> [!NOTE]
> If you fetch the kubeconfig (next step) before `INITIALIZED` is `True`, kube-apiserver will not be listening yet and `helm` / `kubectl` calls will fail with `Kubernetes cluster unreachable: ... EOF`.
Once initialized, retrieve the kubeconfig of the workload cluster:
```shell
export CAPH_WORKER_CLUSTER_KUBECONFIG=/tmp/workload-kubeconfig
clusterctl get kubeconfig my-cluster > $CAPH_WORKER_CLUSTER_KUBECONFIG
```
A quick reachability check before installing the CNI:
```shell
KUBECONFIG=$CAPH_WORKER_CLUSTER_KUBECONFIG kubectl get --raw='/readyz'
```
You should see `ok` (a few component-level failures are still acceptable at this stage). If the call hangs or returns an EOF, give it another minute and retry.
Deploy the CNI solution
Cilium is used as a CNI solution in this guide. The values file lives in this repo at `templates/cilium/values.yaml`; we install it directly from the raw URL:
```shell
helm repo add cilium https://helm.cilium.io/
KUBECONFIG=$CAPH_WORKER_CLUSTER_KUBECONFIG helm upgrade --install cilium cilium/cilium \
--namespace kube-system \
-f https://raw.githubusercontent.com/syself/cluster-api-provider-hetzner/main/templates/cilium/values.yaml
```
You can, of course, also install an alternative CNI, e.g., calico.
Deploy the Cloud Controller Manager (CCM)
The CCM runs in the workload cluster and integrates Kubernetes with the Hetzner APIs. In practice, it is responsible for tasks such as setting the `ProviderID` on nodes and managing load balancers. You need to install a CCM in this flow so the workload cluster can properly interact with Hetzner infrastructure.
You have two options here:
- **Syself CCM**: the `ccm-hetzner` chart maintained by Syself.
- **Hetzner CCM**: the upstream `hcloud-cloud-controller-manager` from Hetzner Cloud.
### Syself CCM
CAPH already syncs the `hcloud` secret you created on the previous page into the workload cluster's `kube-system` namespace, so the CCM only needs to be pointed at it (the chart default is `hetzner`, so we override `secret.name`):
```shell
helm repo add syself https://charts.syself.com
helm repo update syself
KUBECONFIG=$CAPH_WORKER_CLUSTER_KUBECONFIG helm upgrade --install ccm syself/ccm-hetzner \
--version 2.0.6 \
--namespace kube-system \
--set secret.name=hcloud
```
### Hetzner CCM (upstream)
The upstream `hcloud-cloud-controller-manager` static manifest (`deploy/ccm.yaml`) is hardcoded to read the token from secret `hcloud` with key `token`. Even though the management secret you created on the previous page only has the key `hcloud`, CAPH's compatibility shim writes the workload-cluster copy with **both** keys (`hcloud` and `token`) — so the upstream manifest works as-is, no patching required:
```shell
KUBECONFIG=$CAPH_WORKER_CLUSTER_KUBECONFIG kubectl apply \
-f https://raw.githubusercontent.com/hetznercloud/hcloud-cloud-controller-manager/main/deploy/ccm.yaml
```
If you install the upstream CCM via its Helm chart instead, the defaults also point at secret `hcloud` / key `token`, so no `--set` overrides are needed.
For upstream CCM details and bare-metal `ProviderID` format, see the [Baremetal Docs](/docs/caph/topics/baremetal/creating-workload-cluster#deploying-the-hetzner-cloud-controller-manager).
Deploy the CSI (optional)
The Hetzner CSI chart lives in the `hcloud` Helm repo, which we add first. The chart's default `controller.hcloudToken.existingSecret` already points at secret `hcloud` with key `token`, which matches what CAPH wrote into the workload cluster — so no overrides are needed beyond the storage class definition:
```shell
helm repo add hcloud https://charts.hetzner.cloud
helm repo update hcloud
cat << EOF > csi-values.yaml
storageClasses:
- name: hcloud-volumes
defaultStorageClass: true
reclaimPolicy: Retain
EOF
KUBECONFIG=$CAPH_WORKER_CLUSTER_KUBECONFIG helm upgrade --install csi hcloud/hcloud-csi \
--namespace kube-system -f csi-values.yaml
```
Clean up (optional)
If you want to continue with the next step and move the Cluster API components to your workload
cluster (so it becomes the new management cluster), do not run the cleanup command.
If you want to stop here, delete the workload cluster and remove all of the components by using:
```shell
kubectl delete cluster my-cluster
```
> [!IMPORTANT]
> In order to ensure a proper clean-up of your infrastructure, you must always delete the cluster object. Deleting the entire cluster template with the `kubectl delete -f my-cluster.yaml` command might lead to pending resources that have to be cleaned up manually.
Delete management cluster with the following command:
```shell
kind delete cluster
```