Quickstart Guide
Estimated time: ~10 minutes
Get a working Heir cluster running in minutes using Kind as the management cluster.
This guide assumes Kind and the worker node run on the same machine.
Overview
This guide covers the full setup: creating a management cluster, installing the Heir Operator, deploying the Heir control plane, and joining a worker node. By the end you will have a functioning Heir cluster you can start scheduling workloads on.
To keep things simple, this guide uses mostly default options, a single control plane replica, NodePort exposure, and kine as the storage backend. Once you have a working cluster, visit Configuration Options to see everything you can customize, including the Helm chart values used to install the Operator. If you are planning a production deployment, check the Requirements page first.
Kind is used here for convenience, but any conformant Kubernetes distribution works as the management cluster.
Prerequisites
1. Create the Management Cluster
Create a Kind cluster with two NodePort mappings, one for the Kubernetes API server and one for the plane tunnel, which handles communication between the control plane and worker nodes:
cat <<EOF | kind create cluster --name heir-quickstart --kubeconfig="./management-cluster.yaml" --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080
hostPort: 30080
- containerPort: 30081
hostPort: 30081
EOF
2. Install the Heir Operator
The Heir Operator is the controller-manager that watches Runtime resources and reconciles them into running tenant clusters. Install it into the management cluster with Helm:
helm install heir oci://ghcr.io/tardigradeproj/charts/heir --version 1.36.5-heirv0.2.0 \
--kubeconfig ./management-cluster.yaml \
-n heir-system --create-namespace \
--set manager.image.repository=ghcr.io/tardigradeproj/heir-controller-manager \
--set manager.image.tag=v1.36.5-heirv0.2.0
Verify the operator is running before continuing:
kubectl --kubeconfig ./management-cluster.yaml get pods -n heir-system
See Helm Chart for the full set of values the chart accepts.
3. Deploy the Control Plane
First, create the Runtime resource. This tells the Heir how the cluster should be deployed, scaled, configured and exposed. See Configuration Options for a full reference.
cat <<\EOF > ./heir.yaml
apiVersion: controlplane.tardigrade.runtime.io/v1alpha1
kind: Runtime
metadata:
name: heir-quickstart
namespace: default
spec:
controlPlane:
heir:
image: ghcr.io/tardigradeproj/heir:latest
clusterAgent:
image: ghcr.io/tardigradeproj/clusteragent:latest
deployment:
replicas: 1
service:
serviceType: NodePort
planeTunnel:
server:
image: ghcr.io/tardigradeproj/heir-tunnel:latest
cluster:
controlPlaneExternalEndpoint:
apiServer:
host: "127.0.0.1" #assuming that the worker node is being configured on the machine where the kind is running
port: 30080
planeTunnel:
host: "127.0.0.1"
port: 30081
storage:
type: kine
EOF
Apply it against the management cluster:
kubectl --kubeconfig ./management-cluster.yaml apply -f ./heir.yaml
Wait for the control plane pod to come up:
kubectl --kubeconfig ./management-cluster.yaml get pods -w
The Runtime controller also publishes its own admin kubeconfig as a <name>-kubeconfig Secret. Fetching, decoding, and saving it lets you query the tenant cluster directly:
kubectl --kubeconfig ./management-cluster.yaml get secret heir-quickstart-kubeconfig -o jsonpath='{.data.kubeconfig}' | base64 -d > ./heir-quickstart-kubeconfig.yaml
The server address baked into that kubeconfig is whatever you set as cluster.controlPlaneExternalEndpoint.apiServer.host in heir.yaml.
That works as-is if you run every command on this page from the same machine that's running Kind, since Kind also maps the same NodePort (30080) onto your host.
If the worker node or your kubectl client lives on a different machine, use a host address that your bastion host can actually reach instead, and override the
generated kubeconfig accordingly:
kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml config set-cluster heir-quickstart --server=https://<reachable-address>:30080
Once the command completes, verify the cluster is up:
kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml get pods
4. Join a Worker Node
The following steps run on the machine you want to use as a worker node.
Installing Heir on the worker node is required before joining.
curl -sSL https://raw.githubusercontent.com/tardigradeproj/heir/refs/heads/main/install.sh | HEIR_VERSION=latest sh
A worker can't register with the tenant cluster until it presents a bootstrap credential. Creating a WorkerJoinToken resource tells the
cluster agent to generate that credential and publish it as a Secret for the worker to use. WorkerJoinToken is reconciled against the tenant
cluster itself, so apply it using the tenant kubeconfig you fetched in step 3 (heir-quickstart-kubeconfig.yaml), not the management cluster's.
See WorkerJoinToken for the full reference:
cat <<EOF | kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml apply -f -
apiVersion: clusteragent.tardigrade.runtime.io/v1alpha1
kind: WorkerJoinToken
metadata:
name: new-node
spec:
ttl: 3h
EOF
Wait until it's ready:
kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml wait workerjointoken/new-node --for=condition=Ready --timeout=60s
You can check on it at any point with kubectl get workerjointoken, which reports readiness and expiry:
kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml get workerjointoken
NAME READY REASON EXPIRES AGE
new-node True TokenIssued 20XX-XX-XXT23:25:34Z 9s
The controller publishes the generated bootstrap token as the jointoken key of a <name>-jointoken Secret in the tenant cluster's
kube-system namespace. Fetch it and join the worker node in one step:
JOIN_TOKEN=$(kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml get secret new-node-jointoken -n kube-system -o jsonpath='{.data.jointoken}')
sudo heir provision worker --token="$JOIN_TOKEN"
5. Verify
From your local machine, confirm the worker node has joined:
kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml get nodes
NAME STATUS ROLES AGE VERSION
<hostname> NotReady <none> 9s v1.36.5
Right after joining, the node may briefly show NotReady while the CNI finishes coming up. Give it a few minutes and re-run the
command, it should flip to Ready.
6. Run a Workload
With a Ready node in place, schedule something on it:
kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml run nginx --image=nginx
Watch it come up:
kubectl --kubeconfig ./heir-quickstart-kubeconfig.yaml get pods -w
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 14s
Next Steps
Requirements — Read this before moving to production. It covers hardware, networking, and OS prerequisites that are not enforced in the quickstart.
Configuration Options — Once your cluster is running, use this reference to tune the control plane, switch storage backends, configure networking, or add extra resources at bootstrap time.
Helm Chart — Reference for every value the Operator's Helm chart accepts.