
This playground boots a 3-node K3s cluster with gVisor, the agent-sandbox controllers, and the upstream OpenClaw + gVisor example already deployed. Everything is baked into the VM snapshots — no installation steps on start.
Open the OpenClaw tab to reach the Control UI. Give the cluster ~60s after boot for K3s to settle and the sandbox pod to come back; if the tab shows an error page, just reload it.
Cluster — K3s v1.36 on cplane-01, node-01, node-02. dev-machine has kubectl and the cloned repo at ~/agent-sandbox.
gVisor — runsc and containerd-shim-runsc-v1 are installed on all three nodes and wired into K3s's containerd via a drop-in:
/var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.d/runsc.toml
A RuntimeClass/gvisor (handler runsc) is registered. Verify the sandbox really runs on the gVisor kernel:
kubectl exec openclaw-sandbox-claim -- head -1 /proc/version
# Linux version 4.19.0-gvisor ...
agent-sandbox v0.5.6 (core + extensions) in the agent-sandbox-system namespace.
The example from examples/openclaw-gvisor-sandbox: a SandboxTemplate → SandboxWarmPool (1 pre-warmed spare) → SandboxClaim that adopts a sandbox from the pool. The resulting pod, openclaw-sandbox-claim, runs OpenClaw 2026.3.23 as uid 1000 with all capabilities dropped and a 2Gi PVC mounted at /workspace/.openclaw.
kubectl get sandboxtemplate,sandboxwarmpool,sandboxclaim,sandbox,pods
Exposure — a NodePort Service (openclaw-gateway, 18789 → 30789) that the OpenClaw tab points at. Note that kubectl port-forward does not work under gVisor: the app binds inside gVisor's user-space netstack, so the host kernel sees nothing listening in the pod's netns. Always go through a Service.
1. Paste the gateway token. It's stored on dev-machine:
cat ~/openclaw-token.txt
# or read it straight from the running pod:
kubectl exec openclaw-sandbox-claim -- printenv OPENCLAW_GATEWAY_TOKEN
2. Approve the browser pairing. OpenClaw uses a zero-trust device authorization policy, so the first connection from a new browser shows "pairing required". From dev-machine:
~/openclaw-pair.sh
It finds the pending request and approves it — then refresh the tab. The pairing lives on the PVC, so it survives pod restarts.
3. Add a provider API key. The dashboard loads without one, but chat will fail. This part is destructive, because the SandboxClaim snapshots the template's pod spec at adoption time and never re-syncs — you have to tear down claim + warm pool + template together:
cd ~/agent-sandbox/examples/openclaw-gvisor-sandbox
kubectl create secret generic openclaw-provider-keys \
--from-literal=ANTHROPIC_API_KEY="sk-ant-..."
# uncomment the ANTHROPIC_API_KEY env block in openclaw-template.yaml, then:
kubectl delete -f openclaw-claim.yaml -f openclaw-warmpool.yaml -f openclaw-template.yaml
TOKEN="$(cat ~/openclaw-token.txt)"
sed "s/dummy-token-for-sandbox/${TOKEN}/g" openclaw-template.yaml | kubectl apply -f -
kubectl apply -f openclaw-warmpool.yaml
kubectl apply -f openclaw-claim.yaml
Deleting the template drops the PVC, so you'll re-pair the browser afterwards. Swap the env var name for other providers (OPENAI_API_KEY, GEMINI_API_KEY, …), or use envFrom: [secretRef: {name: openclaw-provider-keys}] for several at once.
1. Origin allowlist is wide open. Every play gets a different public hostname, and under --bind=lan OpenClaw refuses requests from unknown origins, so openclaw-config.yaml ships with:
{"gateway": {"controlUi": {"allowedOrigins": ["*"]}}}
That disables origin allowlisting — the gateway token and device pairing are the only gates left. To tighten it for your play, run:
~/openclaw-allow-origin.sh https://<your-play-host>
which pins the allowlist and restarts the pod (the PVC, and therefore the pairing, survives).
2. The template's NetworkPolicy is relaxed. The SandboxTemplate generates a default policy that only allows ingress from the sandbox-router pod and blocks all private-range egress — which dropped NodePort traffic arriving from other nodes and broke cluster DNS. spec.networkPolicy on the template now additionally allows:
0.0.0.0/0kube-dns on port 53 (so the agent can resolve provider APIs)kubectl get netpol openclaw-template-network-policy -o yaml
Egress to private ranges (10/8, 172.16/12, 192.168/16, 169.254/16) is still blocked, so the sandbox cannot reach the cluster's own API server or the node network — only the public internet.
PVCs are named <vctName>-<sandboxName> and owned by the Sandbox CR:
shutdownPolicy: Delete) → the PVC is garbage-collected and the data is gone