Deploy OpenClaw Gateway to Kubernetes with Kustomize
This page covers deploying OpenClaw Gateway to a Kubernetes cluster using Kustomize. It is intended for developers needing a minimal, non-production setup with essential resources.
Read this when
- You want to run OpenClaw on a Kubernetes cluster
- You want to test OpenClaw in a Kubernetes environment
A minimal setup for running OpenClaw on Kubernetes, not intended for production use. It includes the essential resources and should be customized for your specific environment.
Why not Helm
OpenClaw runs as a single container with several configuration files. The main customizations involve agent content such as Markdown files, skills, and config overrides, rather than infrastructure templates. Kustomize provides overlay management without needing a full Helm chart. You can add a Helm chart on top of these manifests if your deployment becomes more advanced.
What you need
- A working Kubernetes cluster (AKS, EKS, GKE, k3s, kind, OpenShift, or similar)
kubectlconnected to your cluster- An API key from at least one model provider
Quick start
# Replace with your provider: ANTHROPIC, GEMINI, OPENAI, or OPENROUTER
export <PROVIDER>_API_KEY="..."
./scripts/k8s/deploy.sh
kubectl port-forward svc/openclaw 18789:18789 -n openclaw
open http://localhost:18789
deploy.sh enables token-based authentication by default. Get the generated gateway token for the Control UI:
kubectl get secret openclaw-secrets -n openclaw -o jsonpath='{.data.OPENCLAW_GATEWAY_TOKEN}' | base64 -d
For local debugging, ./scripts/k8s/deploy.sh --show-token shows the token after deployment.
Local testing with Kind
If you lack a cluster, set one up locally using Kind:
./scripts/k8s/create-kind.sh # auto-detects docker or podman
./scripts/k8s/create-kind.sh --delete # tear down
Then proceed with deployment using ./scripts/k8s/deploy.sh.
Step by step
1) Deploy
Option A: Set API key in environment (single step)
# Replace with your provider: ANTHROPIC, GEMINI, OPENAI, or OPENROUTER
export <PROVIDER>_API_KEY="..."
./scripts/k8s/deploy.sh
This script generates a Kubernetes Secret containing the API key and an auto-generated gateway token, then deploys everything. If the Secret already exists, it keeps the current gateway token and any provider keys that are not being modified.
Option B: Create the secret separately
export <PROVIDER>_API_KEY="..."
./scripts/k8s/deploy.sh --create-secret
./scripts/k8s/deploy.sh
Add --show-token to either command to output the token to stdout for local testing.
2) Access the gateway
kubectl port-forward svc/openclaw 18789:18789 -n openclaw
open http://localhost:18789
What gets deployed
Namespace: openclaw (configurable via OPENCLAW_NAMESPACE)
├── Deployment/openclaw # Single pod, init container + gateway
├── Service/openclaw # ClusterIP on port 18789
├── PersistentVolumeClaim # 10Gi for agent state and config
├── ConfigMap/openclaw-config # openclaw.json + AGENTS.md
└── Secret/openclaw-secrets # Gateway token + API keys
Customization
Agent instructions
Modify the AGENTS.md entry in scripts/k8s/manifests/configmap.yaml and redeploy:
./scripts/k8s/deploy.sh
Gateway config
Change openclaw.json in scripts/k8s/manifests/configmap.yaml. For complete details, see Gateway configuration.
Add providers
Rerun with extra keys exported:
export ANTHROPIC_API_KEY="..."
export OPENAI_API_KEY="..."
./scripts/k8s/deploy.sh --create-secret
./scripts/k8s/deploy.sh
Existing provider keys remain in the Secret unless you overwrite them.
Alternatively, patch the Secret directly:
kubectl patch secret openclaw-secrets -n openclaw \
-p '{"stringData":{"<PROVIDER>_API_KEY":"..."}}'
kubectl rollout restart deployment/openclaw -n openclaw
Custom namespace
OPENCLAW_NAMESPACE=my-namespace ./scripts/k8s/deploy.sh
Custom image
Adjust the image field in scripts/k8s/manifests/deployment.yaml:
image: ghcr.io/openclaw/openclaw:slim # primary; official Docker Hub mirror: openclaw/openclaw
Expose beyond port-forward
The default manifests configure the gateway to bind to loopback within the pod. This works with kubectl port-forward but not with a Kubernetes Service or Ingress path that must reach the pod IP directly.
To make the gateway accessible through an Ingress or load balancer:
- Update the gateway bind in
scripts/k8s/manifests/configmap.yamlfromloopbackto a non-loopback address suitable for your deployment. - Keep gateway authentication active and use a proper TLS-terminated entry point.
- Set up the Control UI for remote access following the supported web security model (for example, HTTPS/Tailscale Serve with explicit allowed origins when required).
Re-deploy
./scripts/k8s/deploy.sh
This applies all manifests and restarts the pod to pick up any configuration or secret changes.
Teardown
./scripts/k8s/deploy.sh --delete
This removes the namespace and all resources within it, including the PVC.
Architecture notes
- The gateway binds to loopback inside the pod by default, so the provided setup is intended for
kubectl port-forward. - No cluster-scoped resources are used; everything resides in a single namespace.
- Security hardening includes
readOnlyRootFilesystem,drop: ALLcapabilities, and a non-root user (UID 1000). - The default configuration keeps the Control UI on the safer local-access path: loopback bind plus
kubectl port-forwardtohttp://127.0.0.1:18789. - If you move beyond localhost access, use the supported remote model: HTTPS/Tailscale with the appropriate gateway bind and Control UI origin settings.
- Secrets are generated in a temporary directory and applied directly to the cluster; no secret data is written to the repository checkout.
File structure
scripts/k8s/
├── deploy.sh # Creates namespace + secret, deploys via kustomize
├── create-kind.sh # Local Kind cluster (auto-detects docker/podman)
└── manifests/
├── kustomization.yaml # Kustomize base
├── configmap.yaml # openclaw.json + AGENTS.md
├── deployment.yaml # Pod spec with security hardening
├── pvc.yaml # 10Gi persistent storage
└── service.yaml # ClusterIP on 18789