Skip to content

Deploy a Kubernetes Cluster

This guide covers two paths: provisioning a new Gardener-managed cluster and registering an existing cluster via kubeconfig. Both end with a verified connection and a running workload.

Time to complete: ~20 minutes (Gardener), ~5 minutes (generic)
Prerequisites: An organization, a project, and a bearer token with garden:create permission.


Path A — Gardener-managed cluster

Step 1 — Discover available options

GET /k8s/cloudprofiles
Authorization: Bearer <token>

The response lists available Kubernetes versions, machine types, and regions. Note the values you want for version, cloud, and machine_type.

Step 2 — Create the cluster

POST /k8s/clusters?project_id=<uuid>
Authorization: Bearer <token>
Content-Type: application/json

{
  "name": "prod-cluster",
  "project_id": "<uuid>",
  "cloud": "openstack",
  "version": "1.29",
  "node_pools": [
    {
      "name": "workers",
      "machine_type": "m1.large",
      "min": 2,
      "max": 5
    }
  ]
}

Returns 201 Created. Note the cluster id.

Step 3 — Wait for the cluster to be ready

Poll until creating: false and status.lastOperation.state is "Succeeded":

GET /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>

Provisioning typically takes 5–10 minutes. While it runs, creating: true and lastOperation tracks progress.

Step 4 — Download the kubeconfig

Once the cluster is ACTIVE, download the kubeconfig from the dashboard or through the Gardener API (available via the dashboard's Download kubeconfig button). The api_server field in the cluster show response gives you the Kubernetes API server URL.

Step 5 — Deploy a workload

export KUBECONFIG=~/Downloads/kubeconfig-prod-cluster.yaml

kubectl get nodes
# NAME           STATUS   ROLES    AGE   VERSION
# worker-abc123  Ready    <none>   3m    v1.29.4

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
      - name: hello
        image: nginx:alpine
        ports:
        - containerPort: 80
EOF

kubectl rollout status deployment/hello-world

Path B — Register an existing cluster (generic)

Use this path to bring an existing cluster (k3s, EKS, GKE, on-prem) under platform management.

Step 1 — Register the cluster

POST /k8s/clusters?project_id=<uuid>
Authorization: Bearer <token>
Content-Type: application/json

{
  "kind": "generic",
  "name": "on-prem-k3s",
  "kubeconfig": "<raw-kubeconfig-yaml-or-base64>"
}

The kubeconfig field is write-only — it is never returned in responses. The response includes has_kubeconfig: true to confirm it was stored.

Step 2 — Verify connectivity

GET /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>

The connection_status field reports reachable or unreachable based on an API server probe. If unreachable, check that the cluster's API server is publicly accessible or that you have provided a kubeconfig with the correct server URL.

Step 3 — Rotate the kubeconfig

When credentials expire or you rotate your cluster's service account:

PATCH /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>
Content-Type: application/json

{ "kubeconfig": "<new-kubeconfig-yaml>" }

Scale node pools (Gardener clusters only)

To scale an existing node pool:

PATCH /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>
Content-Type: application/json

{
  "node_pools": [
    { "name": "workers", "min": 3, "max": 10 }
  ]
}

Gardener applies the change asynchronously. Watch lastOperation to track rollout progress.


Delete a cluster

DELETE /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>

For Gardener clusters, this triggers deprovisioning of the underlying cloud resources. The cluster disappears from list results once teardown is complete.