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
- Install the prerequisites – kubectl and the unbounded plugin
- Prepare gateway nodes – label and open WireGuard ports
- Initialize a site – install the operator and create site resources
- Add machines – register remote hosts for SSH provisioning
- Watch progress – monitor the provisioning lifecycle
1. Install the Prerequisites
You’ll need: A Kubernetes cluster with
kubeconfigaccess, one or more cluster nodes with UDP 51820-51899 open, and remote machines reachable via SSH.
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"
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
| Flag | Description |
|---|---|
--name | Name of the remote site |
--cluster-node-cidr | CIDR used by the cluster for node IPs |
--cluster-pod-cidr | CIDR used by the cluster for pod IPs |
--node-cidr | Subnet of your remote machines (e.g. 192.168.1.0/24). Must not overlap with the cluster’s node CIDR. |
--pod-cidr | Pod 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
| Flag | Description |
|---|---|
--kubeconfig | Path to kubeconfig file |
--manage-cni-plugin | Set to false when the cluster already has a CNI (default: true) |
--enable-machina | Enable machina on the cluster Site (default: true) |
--enable-metalman | Enable the metalman component in the Site spec |
--enable-storage | Enable the unbounded-storage component in the Site spec |
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
| Flag | Description |
|---|---|
--site | Site name (must already be initialized) |
--host | Host IP, optionally with port (10.0.0.5 or 10.0.0.5:2222) |
--ssh-username | SSH username |
Optional flags
| Flag | Description |
|---|---|
--name | Machine name (derived from host if omitted) |
--ssh-private-key | Path to SSH private key |
--ssh-secret-name | K8s secret name for SSH credentials (defaults to ssh-$site) |
--bastion-host | Bastion host and optionally port |
--bastion-ssh-username | Bastion SSH username (defaults to --ssh-username) |
--bastion-ssh-private-key | Bastion SSH key (defaults to --ssh-private-key) |
--bastion-ssh-secret-name | Bastion SSH secret (defaults to --ssh-secret-name) |
--kubeconfig | Path to kubeconfig file |
5. Watch Progress
Machines transition through phases as they are provisioned:
Pending → Provisioning → Joining → Ready
watch 'kubectl get machines'
Next Steps
- Project Overview – how the components fit together
- SSH Guide – bastion hosts, troubleshooting, and the full provisioning lifecycle
- PXE Guide – boot bare-metal servers with metalman
- Networking Concepts – cross-site pod networking deep dive
- CLI Reference – all
kubectl unboundedcommands and flags - CRD Reference – Machine and Image API specification