Bring Your Own Cluster

Add Unbounded to an existing Kubernetes cluster and join remote nodes.

This guide adds Unbounded to a Kubernetes cluster you already have running. You’ll label gateway nodes, initialize a site, and join remote machines.

Starting from scratch? See the Getting Started guide to create an AKS cluster with everything pre-configured.

What You’ll Do

  1. Install the prerequisites – kubectl and the unbounded plugin
  2. Prepare gateway nodes – label and open WireGuard ports
  3. Initialize a site – install the operator and create site resources
  4. Add machines – register remote hosts for SSH provisioning
  5. Watch progress – monitor the provisioning lifecycle

1. Install the Prerequisites

You’ll need: A Kubernetes cluster with kubeconfig access, one or more cluster nodes with UDP 51820-51899 open, and remote machines reachable via SSH.

Note

Clusters with an existing CNI (e.g. Cilium, Calico, Azure CNI): Unbounded works alongside your existing CNI. Pass --manage-cni-plugin=false when running site init, or set manageCniPlugin: false on the Site resource after creation. See the manageCniPlugin reference for details.

Clusters without a CNI (created with --network-plugin none): No extra configuration is needed — unbounded-net will serve as the CNI.

Install the kubectl-unbounded plugin:

# Linux (amd64)
curl -sL https://github.com/Azure/unbounded/releases/latest/download/kubectl-unbounded-linux-amd64.tar.gz | tar xz
sudo mv kubectl-unbounded /usr/local/bin/
macOS (Apple Silicon)
curl -sL https://github.com/Azure/unbounded/releases/latest/download/kubectl-unbounded-darwin-arm64.tar.gz | tar xz
sudo mv kubectl-unbounded /usr/local/bin/

Verify:

kubectl unbounded --help

2. Prepare Gateway Nodes

At least one cluster node must be labeled as a WireGuard gateway. Open UDP 51820-51899 on the node’s firewall, then label it:

kubectl label node <node-name> "unbounded-cloud.io/unbounded-net-gateway=true"
Important
kubectl unbounded site init creates a GatewayPool that selects this label. You must ensure UDP ports 51820-51899 are open on the gateway node’s firewall; the init command does not open these ports for you.

3. Initialize a Site

First, bootstrap unbounded-operator on the cluster. This installs the CRDs and the operator that reconciles Unbounded components:

kubectl unbounded install

A Site represents a remote location where machines will run. The site init command requires the operator to already be installed (it errors otherwise). It creates site resources, records the requested components in Site.spec.components, and generates a bootstrap token. The operator deploys unbounded-net, machina, and optional components from the Site specs.

kubectl unbounded site init \
    --name my-site \
    --cluster-node-cidr 10.224.0.0/16 \
    --cluster-pod-cidr 10.244.0.0/16 \
    --node-cidr 192.168.1.0/24 \
    --pod-cidr 10.245.0.0/16
FlagDescription
--nameName of the remote site
--cluster-node-cidrCIDR used by the cluster for node IPs
--cluster-pod-cidrCIDR used by the cluster for pod IPs
--node-cidrSubnet of your remote machines (e.g. 192.168.1.0/24). Must not overlap with the cluster’s node CIDR.
--pod-cidrPod CIDR for the remote site (e.g. 10.245.0.0/16). Must not overlap with the cluster’s pod or service CIDRs.
Optional flags
FlagDescription
--kubeconfigPath to kubeconfig file
--manage-cni-pluginSet to false when the cluster already has a CNI (default: true)
--enable-machinaEnable machina on the cluster Site (default: true)
--enable-metalmanEnable the metalman component in the Site spec
--enable-storageEnable the unbounded-storage component in the Site spec
Tip
AKS users: The aks-quickstart.sh setup command can auto-detect your cluster’s CIDRs and add a properly configured gateway node pool. See the Getting Started guide for details.

4. Add Machines

Register remote machines with machine register. Each machine must be reachable via SSH:

kubectl unbounded machine register \
    --site my-site \
    --host 10.0.0.5 \
    --ssh-username ubuntu \
    --ssh-private-key ~/.ssh/id_rsa
FlagDescription
--siteSite name (must already be initialized)
--hostHost IP, optionally with port (10.0.0.5 or 10.0.0.5:2222)
--ssh-usernameSSH username
Optional flags
FlagDescription
--nameMachine name (derived from host if omitted)
--ssh-private-keyPath to SSH private key
--ssh-secret-nameK8s secret name for SSH credentials (defaults to ssh-$site)
--bastion-hostBastion host and optionally port
--bastion-ssh-usernameBastion SSH username (defaults to --ssh-username)
--bastion-ssh-private-keyBastion SSH key (defaults to --ssh-private-key)
--bastion-ssh-secret-nameBastion SSH secret (defaults to --ssh-secret-name)
--kubeconfigPath to kubeconfig file

5. Watch Progress

Machines transition through phases as they are provisioned:

PendingProvisioningJoiningReady

watch 'kubectl get machines'

Next Steps