> ## Documentation Index
> Fetch the complete documentation index at: https://noradocs.solomontsao.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Kubernetes aks

# Azure Kubernetes Service (AKS) provisioner backend

> Provision Nora agents into Azure Kubernetes Service (AKS) using Nora's generic Kubernetes adapter and Admin cluster registry.

AKS uses the same Kubernetes adapter as every Kubernetes provider. Nora stores provider, namespace, exposure, and load-balancer settings on the Admin cluster row; `docker-compose.kubernetes.yml` only mounts kubeconfig files into the control-plane containers.

## Step-by-step setup

### 1. Prerequisites

* Azure subscription with permission to create AKS clusters.
* An existing AKS cluster (or create one — Azure Portal → **Kubernetes services** → **Create**).

Use the cluster **Overview** blade to confirm the AKS cluster name and resource group before
running the CLI commands below.

<Frame caption="Azure Portal — AKS cluster overview (cluster name and resource group)">
  <img src="https://mintcdn.com/sttechnologyllc/A_pk5LchBKKfnyIr/images/provisioner-backends/k8s/aks/cluster-overview.png?fit=max&auto=format&n=A_pk5LchBKKfnyIr&q=85&s=2f8ee4f3f6ef2cd759d0cc0a8b9afe8d" alt="AKS cluster overview" width="1512" height="1353" data-path="images/provisioner-backends/k8s/aks/cluster-overview.png" />
</Frame>

### 2. Install and log in with Azure CLI

Install the Azure CLI on the host that runs Nora, then authenticate against the Azure subscription that owns the AKS cluster. Azure Cloud Shell already includes `az`, but the kubeconfig file still needs to be saved on the Nora host.

```bash theme={null} theme={null}
# macOS
brew update && brew install azure-cli

# Ubuntu/Debian/WSL
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# Windows PowerShell
winget install -e --id Microsoft.AzureCLI
```

After installation, confirm the CLI is available and sign in:

```bash theme={null} theme={null}
az version
az login

# Use this instead when the host has no browser:
az login --use-device-code

az account set --subscription <subscription-id>
az account show --query "{subscription:name, id:id, tenant:tenantId}" --output table
```

For other package managers or locked-down hosts, use Microsoft's [Azure CLI install guide](https://learn.microsoft.com/cli/azure/install-azure-cli) and [Azure CLI authentication guide](https://learn.microsoft.com/cli/azure/authenticate-azure-cli).

### 3. Install kubectl

Install `kubectl` on the same host where you will run Nora setup and verification commands. For AKS, the shortest path is Azure CLI's Kubernetes tool installer:

```bash theme={null} theme={null}
az aks install-cli
kubectl version --client
```

If you prefer an OS package manager:

```bash theme={null} theme={null}
# macOS
brew install kubectl

# Ubuntu with snap
sudo snap install kubectl --classic

# Windows PowerShell
winget install -e --id Kubernetes.kubectl
```

For other Linux distributions or direct binary installs, use the [official Kubernetes kubectl install guide](https://kubernetes.io/docs/tasks/tools/).

### 4. Create the kubeconfig

```bash theme={null} theme={null}
mkdir -p .secrets
az aks get-credentials \
  --resource-group <resource-group> \
  --name <cluster-name> \
  --admin \
  --file ./.secrets/aks-kubeconfig \
  --overwrite-existing
```

You can copy this command directly from the cluster's **Connect** blade in the Azure Portal.

<Frame caption="Azure Portal — Cluster → Connect: copy the get-credentials command">
  <img src="https://mintcdn.com/sttechnologyllc/A_pk5LchBKKfnyIr/images/provisioner-backends/k8s/aks/connect-cluster.png?fit=max&auto=format&n=A_pk5LchBKKfnyIr&q=85&s=3583f4ae3b03abac180288a51870b168" alt="AKS Connect blade" width="1512" height="1389" data-path="images/provisioner-backends/k8s/aks/connect-cluster.png" />
</Frame>

### 5. Verify access from the host

```bash theme={null} theme={null}
kubectl --kubeconfig ./.secrets/aks-kubeconfig get nodes
```

### 6. Configure Nora

Set the generic Kubernetes mount variable in `.env`:

```bash theme={null} theme={null}
mkdir -p ./.secrets/kubeconfigs
cp ./.secrets/aks-kubeconfig ./.secrets/kubeconfigs/aks-eastus2
NORA_KUBECONFIGS_DIR=./.secrets/kubeconfigs
```

For two or more AKS clusters, put each kubeconfig under `NORA_KUBECONFIGS_DIR` and use Admin paths such as `/kubeconfigs/aks-eastus2` and `/kubeconfigs/aks-westus2`.

### 7. Start the stack

```bash theme={null} theme={null}
docker compose -f docker-compose.yml -f docker-compose.kubernetes.yml up -d --build
```

<Warning>
  This smoke-mode command is for the local nginx config (`NGINX_CONFIG_FILE=nginx.conf`, usually
  `NGINX_HTTP_PORT=8080`). If your `.env` already points to the public TLS config
  (`NGINX_CONFIG_FILE=nginx.public.conf`), include the tracked `infra/docker-compose.public-tls.yml`
  layer as shown in [Promote to production](#promote-to-production); otherwise nginx will not mount
  `/etc/letsencrypt` and Cloudflare can return 521 because the origin web server is down.
</Warning>

### 8. Register this cluster in Admin

Open **Admin -> Kubernetes**, click **Add cluster**, and use these values:

| Field                    | Value                                                                                                                               |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| Cluster id               | `aks-eastus2`                                                                                                                       |
| Label                    | `AKS East US 2`                                                                                                                     |
| Provider                 | `AKS`                                                                                                                               |
| Actual cluster name      | The Azure AKS cluster name, for example `nora-dns-vjb9kjjz`                                                                         |
| Credential mode          | `Mounted kubeconfig path`                                                                                                           |
| Kubeconfig path          | `/kubeconfigs/aks-eastus2`                                                                                                          |
| Fallback namespace       | `nora-openclaw-agents`                                                                                                              |
| OpenClaw namespace       | `nora-openclaw-agents`                                                                                                              |
| Hermes namespace         | `nora-hermes-agents`                                                                                                                |
| Exposure mode            | `LoadBalancer`                                                                                                                      |
| Service annotations JSON | `{}` for public load balancers, or `{"service.beta.kubernetes.io/azure-load-balancer-internal":"true"}` for internal load balancers |
| Source ranges            | Your Nora control-plane egress CIDR when you can restrict access                                                                    |
| Load balancer class      | Leave empty for normal AKS Services                                                                                                 |

Do not put `./.secrets/aks-kubeconfig` in the Admin Kubeconfig path when Nora runs in Docker Compose. That is the host-side path; the containers see files from `NORA_KUBECONFIGS_DIR` under `/kubeconfigs`.

### 9. Deploy test agents

Open the dashboard at `http://127.0.0.1:8080`, sign in, and create an OpenClaw agent with the AKS cluster label you registered. To validate Hermes on AKS, create a second agent with **Runtime = Hermes** and the same AKS execution target.

<Frame caption="Nora deploy wizard — Backend dropdown showing Kubernetes on AKS">
  <img src="https://mintcdn.com/sttechnologyllc/hES1KHpUWDvLb_Wr/images/provisioner-backends/k8s/_nora/nora-deploy-backend-picker.png?fit=max&auto=format&n=hES1KHpUWDvLb_Wr&q=85&s=9788405d5b29b781c069611db393e696" alt="Backend picker" width="1512" height="1080" data-path="images/provisioner-backends/k8s/_nora/nora-deploy-backend-picker.png" />
</Frame>

<Frame caption="Nora deploy wizard — Kubernetes on AKS selected">
  <img src="https://mintcdn.com/sttechnologyllc/hES1KHpUWDvLb_Wr/images/provisioner-backends/k8s/_nora/nora-deploy-k8s-selected.png?fit=max&auto=format&n=hES1KHpUWDvLb_Wr&q=85&s=83ffffd500bc21cd168808e522eec88c" alt="Kubernetes selected" width="1512" height="1080" data-path="images/provisioner-backends/k8s/_nora/nora-deploy-k8s-selected.png" />
</Frame>

<Frame caption="Agent detail — status running with the AKS LoadBalancer address">
  <img src="https://mintcdn.com/sttechnologyllc/A_pk5LchBKKfnyIr/images/provisioner-backends/k8s/_nora/nora-agent-running-k8s.png?fit=max&auto=format&n=A_pk5LchBKKfnyIr&q=85&s=9c6d98571b77e4680faf11697341729e" alt="Agent detail K8s" width="1512" height="1080" data-path="images/provisioner-backends/k8s/_nora/nora-agent-running-k8s.png" />
</Frame>

## AKS Service options

For a public load balancer, leave **Service annotations JSON** empty and restrict access with **Source ranges** when possible.

For an internal AKS load balancer:

```json theme={null} theme={null}
{ "service.beta.kubernetes.io/azure-load-balancer-internal": "true" }
```

Confirm the LoadBalancer SKU in the Azure Portal under **Settings → Networking** matches what your subnet policies allow.

<Frame caption="Azure Portal — Cluster → Settings → Networking (LoadBalancer SKU)">
  <img src="https://mintcdn.com/sttechnologyllc/A_pk5LchBKKfnyIr/images/provisioner-backends/k8s/aks/networking.png?fit=max&auto=format&n=A_pk5LchBKKfnyIr&q=85&s=d720e87ac4f5f7e8845d6aa523e28e44" alt="AKS networking settings" width="1024" height="390" data-path="images/provisioner-backends/k8s/aks/networking.png" />
</Frame>

Do not set the Admin **Runtime host** to the AKS API server address. Nora uses the Kubernetes API through kubeconfig, and agent gateway/runtime traffic uses the per-agent Service address.

## Verification

```bash theme={null} theme={null}
kubectl --kubeconfig ./.secrets/aks-kubeconfig -n nora-openclaw-agents get deploy,svc,pods
# If Hermes is enabled and deployed:
kubectl --kubeconfig ./.secrets/aks-kubeconfig -n nora-hermes-agents get deploy,svc,pods
docker compose -f docker-compose.yml -f docker-compose.kubernetes.yml logs worker-provisioner
```

For real e2e, enable the Kubernetes matrix cell:

```bash theme={null} theme={null}
cd e2e
REAL_ENABLE_OPENCLAW_DOCKER=0 REAL_ENABLE_OPENCLAW_K8S=1 \
  REAL_ENABLE_HERMES_DOCKER=0 REAL_ENABLE_HERMES_K8S=1 \
  BASE_URL=http://localhost:8080 npm run test:real:matrix
```

<Frame caption="Logs tab — worker-provisioner output during a successful AKS deploy">
  <img src="https://mintcdn.com/sttechnologyllc/A_pk5LchBKKfnyIr/images/provisioner-backends/k8s/_nora/nora-agent-logs-k8s.png?fit=max&auto=format&n=A_pk5LchBKKfnyIr&q=85&s=fed94088a03606a531285305b6e64662" alt="Agent logs K8s" width="1512" height="1080" data-path="images/provisioner-backends/k8s/_nora/nora-agent-logs-k8s.png" />
</Frame>

## Automated smoke

Once your AKS kubeconfig is in place, run the shared lifecycle smoke. The AKS
script deploys both OpenClaw and Hermes by default:

```bash theme={null} theme={null}
cd e2e
KUBECONFIG_PATH=$PWD/../.secrets/aks-kubeconfig npm run smoke:k8s-aks
```

This script is operator-run only — it provisions real workloads against a live AKS cluster and is not part of CI. Set `K8S_SMOKE_RUNTIME_FAMILIES=openclaw` for a single-runtime check, `K8S_SMOKE_CELLS=openclaw:nemoclaw` plus `NVIDIA_API_KEY` for a NemoClaw run, or `KEEP_ENV=true` to leave the stack running after the script finishes.

## Promote to production

Once the smoke is green, switch from the dev-mode stack to the prod-mode stack. The only changes vs the testing setup are nginx (public config + TLS) and Compose mode (`infra/docker-compose.public-tls.yml` for prod Dockerfiles, TLS mounts, public ports, and restart policies).

### 1. Tighten the LoadBalancer source ranges

Replace the smoke-time `<nora-control-plane-egress-cidr>` placeholder with your production control plane's actual egress CIDR (e.g. the public IP of the host running Nora). Combine with internal-LB annotations if you're keeping agents on a private subnet.

### 2. Switch nginx to public + TLS

In `.env`:

```bash theme={null} theme={null}
NGINX_CONFIG_FILE=nginx.public.conf
NGINX_HTTP_PORT=80
```

Provision Let's Encrypt certs once:

```bash theme={null} theme={null}
sudo ./infra/setup-tls.sh <your-domain>
```

### 3. Stop the smoke-mode stack

```bash theme={null} theme={null}
docker compose -f docker-compose.yml -f docker-compose.kubernetes.yml down
```

### 4. Start the prod-mode stack

```bash theme={null} theme={null}
docker compose \
  -f docker-compose.yml \
  -f infra/docker-compose.public-tls.yml \
  -f docker-compose.kubernetes.yml \
  up -d --build
```

### 5. Confirm

```bash theme={null} theme={null}
docker compose ps
curl -fsS https://<your-domain>/api/health
docker compose logs worker-provisioner | tail -50
```

Deploy a real agent from the UI and confirm it reaches `running` and the AKS LoadBalancer assigns an external IP.

## See also

* [Kind](/configuration/provisioner-backends/kubernetes-kind) — local Kubernetes for development
* [K3s](/configuration/provisioner-backends/kubernetes-k3s) — self-hosted production K8s
* [GKE](/configuration/provisioner-backends/kubernetes-gke) — Google Kubernetes Engine
* [EKS](/configuration/provisioner-backends/kubernetes-eks) — Amazon EKS
