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)
  • kubectl connected 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.yaml from loopback to 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: ALL capabilities, 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-forward to http://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