Skip to main content

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 servicesCreate).
Use the cluster Overview blade to confirm the AKS cluster name and resource group before running the CLI commands below.
AKS cluster overview

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.
# 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:
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 and Azure CLI authentication guide.

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:
az aks install-cli
kubectl version --client
If you prefer an OS package manager:
# 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.

4. Create the kubeconfig

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.
AKS Connect blade

5. Verify access from the host

kubectl --kubeconfig ./.secrets/aks-kubeconfig get nodes

6. Configure Nora

Set the generic Kubernetes mount variable in .env:
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

docker compose -f docker-compose.yml -f docker-compose.kubernetes.yml up -d --build
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; otherwise nginx will not mount /etc/letsencrypt and Cloudflare can return 521 because the origin web server is down.

8. Register this cluster in Admin

Open Admin -> Kubernetes, click Add cluster, and use these values:
FieldValue
Cluster idaks-eastus2
LabelAKS East US 2
ProviderAKS
Actual cluster nameThe Azure AKS cluster name, for example nora-dns-vjb9kjjz
Credential modeMounted kubeconfig path
Kubeconfig path/kubeconfigs/aks-eastus2
Fallback namespacenora-openclaw-agents
OpenClaw namespacenora-openclaw-agents
Hermes namespacenora-hermes-agents
Exposure modeLoadBalancer
Service annotations JSON{} for public load balancers, or {"service.beta.kubernetes.io/azure-load-balancer-internal":"true"} for internal load balancers
Source rangesYour Nora control-plane egress CIDR when you can restrict access
Load balancer classLeave 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.
Backend picker
Kubernetes selected
Agent detail K8s

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:
{ "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.
AKS networking settings
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

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:
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
Agent logs K8s

Automated smoke

Once your AKS kubeconfig is in place, run the shared lifecycle smoke. The AKS script deploys both OpenClaw and Hermes by default:
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, 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:
NGINX_CONFIG_FILE=nginx.public.conf
NGINX_HTTP_PORT=80
Provision Let’s Encrypt certs once:
sudo ./infra/setup-tls.sh <your-domain>

3. Stop the smoke-mode stack

docker compose -f docker-compose.yml -f docker-compose.kubernetes.yml down

4. Start the prod-mode stack

docker compose \
  -f docker-compose.yml \
  -f infra/docker-compose.public-tls.yml \
  -f docker-compose.kubernetes.yml \
  up -d --build

5. Confirm

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 — local Kubernetes for development
  • K3s — self-hosted production K8s
  • GKE — Google Kubernetes Engine
  • EKS — Amazon EKS