> ## 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 k3s

# K3s provisioner backend

> Run Nora agents on a lightweight self-hosted K3s cluster using Nora's Kubernetes adapter.

K3s is the recommended self-hosted Kubernetes path when you want cluster scheduling without using a managed cloud provider. Nora still uses the same Kubernetes adapter underneath; the Admin cluster registry stores the provider label so operators can distinguish lightweight self-hosted clusters from cloud deployments in the deploy flow.

## When to use K3s

Use K3s when Nora runs on your own server, homelab, edge node, or private VM fleet and you want agent workloads scheduled as Kubernetes Deployments instead of local Docker containers.

Use AKS, GKE, EKS, or another cloud `k8s` target when you want managed control-plane operations, cloud load balancers, cloud identity, and multi-zone cluster management.

|                      | K3s                                       | Cloud Kubernetes (`k8s`)                            |
| -------------------- | ----------------------------------------- | --------------------------------------------------- |
| **Provider label**   | `K3s`                                     | `AKS`, `GKE`, `EKS`, or `Kubernetes`                |
| **Operator model**   | You own the cluster host and upgrades     | Cloud provider manages the Kubernetes control plane |
| **Typical exposure** | `node-port` from Compose to the K3s node  | `load-balancer` with provider-managed Services      |
| **Best for**         | Self-hosted, edge, lab, and private infra | Production cloud clusters and managed networking    |
| **Adapter behavior** | Same generic Kubernetes adapter           | Same generic Kubernetes adapter                     |

## Step-by-step setup

### 1. Install K3s on the cluster host

```bash theme={null} theme={null}
curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodes
```

### 2. Mint a kubeconfig Nora can mount

```bash theme={null} theme={null}
mkdir -p .secrets
sudo install -m 600 -o "$USER" -g "$USER" \
  /etc/rancher/k3s/k3s.yaml \
  ./.secrets/k3s-kubeconfig
```

If Nora runs in Docker Compose, the kubeconfig server URL must be reachable from the `backend-api` and `worker-provisioner` containers. Replace the default loopback address with the K3s node address:

```bash theme={null} theme={null}
K3S_NODE_IP=<k3s-node-ip>
sed -i "s/127.0.0.1/${K3S_NODE_IP}/g" ./.secrets/k3s-kubeconfig
```

### 3. Verify access from the host

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

### 4. Configure Nora

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

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

Use `host.docker.internal` when K3s and Nora's Docker Compose stack run on the same host. Use the K3s node IP or DNS name when Nora runs on a different host.

For multiple K3s or mixed Kubernetes clusters, put the kubeconfig under `NORA_KUBECONFIGS_DIR` instead and use an Admin path such as `/kubeconfigs/k3s-local`.

### 5. Start the stack

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

### 6. Register this cluster in Admin

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

| Field               | Value                                                                                                |
| ------------------- | ---------------------------------------------------------------------------------------------------- |
| Cluster id          | `k3s-local`                                                                                          |
| Label               | `K3s Local`                                                                                          |
| Provider            | `K3s`                                                                                                |
| Actual cluster name | Your K3s server or cluster name                                                                      |
| Credential mode     | `Mounted kubeconfig path`                                                                            |
| Kubeconfig path     | `/kubeconfigs/k3s-local`                                                                             |
| Fallback namespace  | `openclaw-agents`                                                                                    |
| OpenClaw namespace  | `openclaw-agents`                                                                                    |
| Hermes namespace    | `openclaw-agents`                                                                                    |
| Exposure mode       | `NodePort`                                                                                           |
| Runtime host        | `host.docker.internal` when Nora and K3s run on the same host; otherwise the K3s node IP or DNS name |

Do not put `./.secrets/k3s-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`.

### 7. Deploy a test agent

Open the dashboard at `http://127.0.0.1:8080`, sign in, and create an agent with the K3s cluster label you registered.

<Frame caption="Nora deploy wizard — Backend dropdown showing Kubernetes on K3s">
  <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="Deploy wizard 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 K3s 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="Deploy wizard Kubernetes
selected"
    width="1512"
    height="1080"
    data-path="images/provisioner-backends/k8s/_nora/nora-deploy-k8s-selected.png"
  />
</Frame>

<Frame caption="Agent detail page — status running, K3s NodePort address shown">
  <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>

## Exposure modes

For the standard Compose-hosted control plane, set the Admin exposure mode to `node-port`. Nora stores the allocated NodePorts for each agent and reaches them through the Admin **Runtime host** value.

Use `cluster-ip` only when the Nora control plane itself runs inside the same K3s cluster or otherwise has an in-cluster route to Service DNS names.

Use `load-balancer` only when your K3s installation has a load balancer implementation that can assign a reachable address per agent Service.

## Verification

Check the Kubernetes workload:

```bash theme={null} theme={null}
kubectl --kubeconfig ./.secrets/k3s-kubeconfig -n openclaw-agents get deploy,svc,pods
```

Check Nora worker logs:

```bash theme={null} theme={null}
docker compose -f docker-compose.yml -f docker-compose.kubernetes.yml logs worker-provisioner
```

If an agent reaches `running` but the gateway is unreachable, confirm that the Admin **Runtime host** points to an address the Compose containers can reach and that your firewall allows the allocated NodePort range.

<Frame caption="Logs tab — worker-provisioner output during a successful K3s 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 K3s cluster and kubeconfig are in place, run the shared lifecycle smoke:

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

To include NemoClaw in the same adapter path, enable the sandbox profile on the Nora stack, export
`NVIDIA_API_KEY`, and run:

```bash theme={null} theme={null}
cd e2e
K8S_SMOKE_CELLS=openclaw:nemoclaw \
  KUBECONFIG_PATH=$PWD/../.secrets/k3s-kubeconfig \
  npm run smoke:k8s-k3s
```

The script brings up the Compose stack with `docker-compose.kubernetes.yml`, registers the K3s cluster in Admin, deploys an agent, verifies Kubernetes objects, and exercises stop/start/restart. It exits non-zero on any failure. Set `KEEP_ENV=true` to leave the stack running for inspection.

## Promote to production

Once the smoke is green and you trust the cluster, 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. 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 (the script wires `/etc/letsencrypt` into the nginx container):

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

### 2. Stop the smoke-mode stack

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

### 3. Start the prod-mode stack

Use the tracked TLS compose layer, then layer the K3s backend overlay on top:

```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
```

### 4. 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 NodePort gateway is reachable from your network.

## See also

* [Kind](/configuration/provisioner-backends/kubernetes-kind) — local Kubernetes for development
* [AKS](/configuration/provisioner-backends/kubernetes-aks) — Azure Kubernetes Service
* [GKE](/configuration/provisioner-backends/kubernetes-gke) — Google Kubernetes Engine
* [EKS](/configuration/provisioner-backends/kubernetes-eks) — Amazon EKS
