πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Deployments intermediate ⏱ 30 minutes K8s 1.28+

OpenClaw Multi-Environment Deployment with Kustomize

Deploy OpenClaw across dev, staging, and production Kubernetes environments using Kustomize overlays for configs and secrets.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: Use Kustomize overlays to manage OpenClaw deployments across environments. The base contains shared manifests (Deployment, Service, PVC), while overlays customize openclaw.json, resource limits, replica counts, and secrets per environment β€” no Helm chart needed.

The Problem

Running OpenClaw in dev, staging, and production requires different configurations: dev needs debug logging and cheap models, staging mirrors production settings, and production needs HA replicas, strict resource limits, and premium model access. Copy-pasting manifests across environments leads to configuration drift and missed updates.

The Solution

Directory Structure

openclaw-k8s/
β”œβ”€β”€ base/
β”‚   β”œβ”€β”€ kustomization.yaml
β”‚   β”œβ”€β”€ deployment.yaml
β”‚   β”œβ”€β”€ service.yaml
β”‚   β”œβ”€β”€ pvc.yaml
β”‚   └── configmap.yaml
└── overlays/
    β”œβ”€β”€ dev/
    β”‚   β”œβ”€β”€ kustomization.yaml
    β”‚   β”œβ”€β”€ configmap-patch.yaml
    β”‚   └── deployment-patch.yaml
    β”œβ”€β”€ staging/
    β”‚   β”œβ”€β”€ kustomization.yaml
    β”‚   β”œβ”€β”€ configmap-patch.yaml
    β”‚   └── deployment-patch.yaml
    └── production/
        β”œβ”€β”€ kustomization.yaml
        β”œβ”€β”€ configmap-patch.yaml
        β”œβ”€β”€ deployment-patch.yaml
        └── hpa.yaml

Base Manifests

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
  - pvc.yaml
  - configmap.yaml
commonLabels:
  app: openclaw
  app.kubernetes.io/name: openclaw
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openclaw
spec:
  replicas: 1
  selector:
    matchLabels:
      app: openclaw
  template:
    metadata:
      labels:
        app: openclaw
    spec:
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
      containers:
        - name: openclaw
          image: ghcr.io/openclaw/openclaw:latest
          ports:
            - containerPort: 18789
          envFrom:
            - secretRef:
                name: openclaw-secrets
          volumeMounts:
            - name: config
              mountPath: /home/node/.openclaw/openclaw.json
              subPath: openclaw.json
            - name: config
              mountPath: /home/node/.openclaw/workspace/AGENTS.md
              subPath: AGENTS.md
            - name: data
              mountPath: /home/node/.openclaw
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          securityContext:
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
      volumes:
        - name: config
          configMap:
            name: openclaw-config
        - name: data
          persistentVolumeClaim:
            claimName: openclaw-data
# base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: openclaw-config
data:
  openclaw.json: |
    {
      "gateway": {
        "bind": "loopback",
        "port": 18789,
        "auth": true
      }
    }
  AGENTS.md: |
    # OpenClaw Agent
    You are a helpful AI assistant.

Dev Overlay

# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
namespace: openclaw-dev
namePrefix: dev-
patches:
  - path: configmap-patch.yaml
  - path: deployment-patch.yaml
# overlays/dev/configmap-patch.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: openclaw-config
data:
  openclaw.json: |
    {
      "gateway": {
        "bind": "loopback",
        "port": 18789,
        "auth": true
      },
      "defaultModel": "openrouter/google/gemini-2.5-flash",
      "logging": {
        "level": "debug"
      }
    }
  AGENTS.md: |
    # OpenClaw Dev Agent
    You are a development assistant. Be verbose with debugging output.
    Environment: dev β€” safe to experiment.
# overlays/dev/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openclaw
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: openclaw
          resources:
            requests:
              cpu: 50m
              memory: 128Mi
            limits:
              cpu: 250m
              memory: 256Mi

Production Overlay

# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
  - hpa.yaml
namespace: openclaw-prod
namePrefix: prod-
patches:
  - path: configmap-patch.yaml
  - path: deployment-patch.yaml
# overlays/production/configmap-patch.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: openclaw-config
data:
  openclaw.json: |
    {
      "gateway": {
        "bind": "0.0.0.0",
        "port": 18789,
        "auth": true
      },
      "defaultModel": "anthropic/claude-sonnet-4-20250514",
      "controlUI": {
        "allowedOrigins": ["https://openclaw.company.com"]
      }
    }
# overlays/production/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openclaw
spec:
  replicas: 2
  template:
    spec:
      containers:
        - name: openclaw
          image: ghcr.io/openclaw/openclaw:2026.3.1
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "2"
              memory: 1Gi
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: openclaw
# overlays/production/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: openclaw-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: prod-openclaw
  minReplicas: 2
  maxReplicas: 5
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Deploy Commands

# Dev
kubectl apply -k overlays/dev/

# Staging
kubectl apply -k overlays/staging/

# Production
kubectl apply -k overlays/production/

# Diff before applying
kubectl diff -k overlays/production/
graph TD
    A[Base Manifests] --> B[Dev Overlay]
    A --> C[Staging Overlay]
    A --> D[Production Overlay]
    B --> E[openclaw-dev namespace]
    C --> F[openclaw-staging namespace]
    D --> G[openclaw-prod namespace]
    E -->|Cheap model<br>Debug logging| H[1 replica]
    F -->|Prod model<br>Prod config| I[1 replica]
    G -->|Premium model<br>HA + HPA| J[2-5 replicas]

Common Issues

ConfigMap Patch Not Merging Correctly

Kustomize uses strategic merge for ConfigMaps. If your patch replaces the entire data field instead of merging keys, use $patch: replace:

# Force full replacement of data block
apiVersion: v1
kind: ConfigMap
metadata:
  name: openclaw-config
data:
  $patch: replace
  openclaw.json: |
    { ... complete config ... }

PVC Name Conflicts Across Environments

The namePrefix in kustomization.yaml prefixes all resources. The PVC becomes dev-openclaw-data, prod-openclaw-data β€” each environment gets independent storage.

Secrets Not Prefixed

Create secrets per namespace manually since they contain sensitive data:

kubectl create secret generic openclaw-secrets \
  -n openclaw-prod \
  --from-literal=OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 32)" \
  --from-literal=ANTHROPIC_API_KEY="sk-ant-..."

Best Practices

  • Pin production images β€” use specific tags (2026.3.1) not latest
  • Separate namespaces β€” isolate environments completely
  • GitOps integration β€” point ArgoCD at each overlay directory
  • Secrets out of Git β€” use External Secrets Operator or sealed-secrets for API keys
  • Test with diff β€” always run kubectl diff -k before applying production changes
  • Resource limits mandatory β€” prevent OpenClaw from consuming excessive cluster resources

Key Takeaways

  • Kustomize overlays keep environment-specific config separate from shared base manifests
  • Use namePrefix and namespace to isolate dev/staging/production completely
  • Pin container image tags in production, use latest only in dev
  • Integrate with ArgoCD by pointing Application resources at overlay directories
  • Keep secrets out of Git β€” create them imperatively or use External Secrets Operator
#openclaw #kustomize #multi-environment #gitops #deployments
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens