Kubernetes operator for deploying and managing OpenClaw AI agent instances with production-grade security, observability, and lifecycle management.
Kubernetes operator for deploying and managing OpenClaw AI agent instances with production-grade security, observability, and lifecycle management.
Self-host OpenClaw AI agents on Kubernetes with production-grade security, observability, and lifecycle management.
OpenClaw is an AI agent platform that acts on your behalf across Telegram, Discord, WhatsApp, and Signal. It manages your inbox, calendar, smart home, and more through 50+ integrations. While Paperclip Inc. offers fully managed hosting, this operator lets you run OpenClaw on your own infrastructure with the same operational rigor.
Deploying AI agents to Kubernetes involves more than a Deployment and a Service. You need network isolation, secret management, persistent storage, health monitoring, optional browser automation, and config rollouts, all wired correctly. This operator encodes those concerns into a single OpenClawInstance custom resource so you can go from zero to production in minutes:
apiVersion: openclaw.rocks/v1alpha1
kind: OpenClawInstance
metadata:
name: my-agent
spec:
envFrom:
- secretRef:
name: openclaw-api-keys
storage:
persistence:
enabled: true
size: 10Gi
The operator reconciles this into a fully managed stack of 9+ Kubernetes resources: secured, monitored, and self-healing.
Agents can autonomously install skills, patch their config, add environment variables, and seed workspace files - all through the Kubernetes API, validated by the operator on every request.
# 1. Enable self-configure on the instance
spec:
selfConfigure:
enabled: true
allowedActions: [skills, config, envVars, workspaceFiles]
# 2. The agent creates this to install a skill at runtime
apiVersion: openclaw.rocks/v1alpha1
kind: OpenClawSelfConfig
metadata:
name: add-fetch-skill
spec:
instanceRef: my-agent
addSkills:
- "@anthropic/mcp-server-fetch"
Every request is validated against the instance's allowlist policy. Protected config keys cannot be overwritten, and denied requests are logged with a reason. See Self-configure for details.
Note: Without
selfConfigureenabled, config or skill changes made by the agent inside the container won't trigger a pod restart. You'll need to restart the pod manually (e.g.kubectl delete pod) for changes to take effect.
| Feature | Details | |
|---|---|---|
| Declarative | Single CRD | One resource defines the entire stack: StatefulSet, Service, RBAC, NetworkPolicy, PVC, PDB, Ingress, and more |
| Adaptive | Agent self-configure | Agents autonomously install skills, patch config, and adapt their environment via the K8s API - every change validated against an allowlist policy |
| Secure | Hardened by default | Non-root (UID 1000), read-only root filesystem, all capabilities dropped, seccomp RuntimeDefault, default-deny NetworkPolicy, validating webhook |
| Observable | Built-in metrics | Prometheus metrics, ServiceMonitor integration, structured JSON logging, Kubernetes events |
| Flexible | Provider-agnostic config | Use any AI provider (Anthropic, OpenAI, or others) via environment variables and inline or external config |
| Config Modes | Merge or overwrite | overwrite replaces config on restart; merge deep-merges with PVC config, preserving runtime changes. Config is restored on every container restart via init container. |
| Force Paths | Operator-owned paths under merge | config.forcePaths lists dot-paths the init container rebuilds from the CR on every restart even under mergeMode: merge -- lets managed deployers keep operator-owned config (auth, allowed providers, sandbox image) immune to tenant edits while user-owned config persists |
| Skills | Declarative install | Install ClawHub skills, npm packages, or GitHub-hosted skill packs via spec.skills - supports npm: and pack: prefixes. Additional workspaces can declare their own workspace-scoped skills via additionalWorkspaces[].skills |
| Plugins | Declarative install | Install OpenClaw plugins via spec.plugins - resolved through the OpenClaw CLI ClawHub installer in a secure init container |
| Runtime Deps | pnpm & Python/uv | Built-in init containers install pnpm (via corepack) or Python 3.12 + uv for MCP servers and skills |
| Auto-Update | OCI registry polling | Opt-in version tracking: checks the registry for new semver releases, backs up first, rolls out, and auto-rolls back if the new version fails health checks |
| Scalable | Auto-scaling | HPA integration with CPU and memory metrics, min/max replica bounds, automatic StatefulSet replica management |
| Operational | Instance suspension | Scale to zero with spec.suspended: true - all non-runtime resources remain managed, resume instantly with false |
| Resilient | Self-healing lifecycle | PodDisruptionBudgets, health probes, automatic config rollouts via content hashing, 5-minute drift detection |
| Disk-Aware Readiness | Opt-in ENOSPC guard | spec.probes.diskReadiness renders the readiness probe as an exec check that ANDs the gateway /readyz signal with a workspace writability + free-space check, so a full or read-only PVC drains the pod from Service endpoints instead of silently accepting writes it cannot persist. Liveness/startup stay HTTP so a full disk never turns into a CrashLoopBackOff. Defaulted off. |
| Backup/Restore | S3-backed snapshots | Automatic backup to S3-compatible storage on deletion, pre-update, and on a cron schedule; restore into a new instance from any snapshot |
| Workspace Seeding | Initial files & dirs | Pre-populate the workspace with files and directories before the agent starts; reference an external ConfigMap for GitOps workflows |
| Gateway Auth | Auto-generated tokens | Automatic shared-secret gateway authentication with a persistent token Secret per instance |
| Tailscale | Tailnet access | Expose via Tailscale Serve or Funnel with SSO auth - no Ingress needed |
| Extensible | Sidecars & init containers | Chromium for browser automation, Ollama for local LLMs, Tailscale for tailnet access, plus custom init containers and sidecars |
| Cloud Native | SA annotations & CA bundles | AWS IRSA / GCP Workload Identity via ServiceAccount annotations; CA bundle injection for corporate proxies |
| Cluster Defaults | Singleton CR | OpenClawClusterDefaults (name cluster) fills in unset instance fields - ideal for air-gapped / China regions where every instance would otherwise duplicate the same registry + mirror env boilerplate. Per-instance fields always win. |
| Zombie Reaping | Shared PID namespace | spec.shareProcessNamespace defaults to true so the pause container becomes PID 1 and reaps defunct helper processes from QMD, git, plugins, and shells - no custom init image needed |
…
helm install openclaw-operator \
oci://ghcr.io/paperclipinc/charts/openclaw-operator \
--namespace openclaw-operator-system \
--create-namespace
Alternative: install with Kustomize
# Install CRDs
make install
# Deploy the operator
make deploy IMG=ghcr.io/paperclipinc/openclaw-operator:latest
Restrict the operator to specific namespaces
To run the operator with namespaced RBAC instead of cluster-wide permissions,
list the namespaces it should watch. The chart switches the namespace-scoped
permissions from a ClusterRole/ClusterRoleBinding to per-namespace
Role/RoleBinding, and passes --watch-namespaces to the operator so its
informer cache is scoped to that list. The operator's own namespace is added to
the Secret informer only, so it can still read its backup credentials, and
the chart renders a matching Secret-only Role there; no other resource type is
watched or granted in the operator namespace. A ClusterRole/ClusterRoleBinding is still created
for the cluster-scoped OpenClawClusterDefaults resource, which the operator
watches regardless of namespace scoping -- a namespaced Role cannot grant
access to a cluster-scoped resource.
helm install openclaw-operator \
oci://ghcr.io/paperclipinc/charts/openclaw-operator \
--namespace openclaw-operator-system \
--create-namespace \
--set 'watchNamespaces={team-a,team-b}'
Each listed namespace must already exist; the chart does not create them.
To bring your own RBAC entirely (e.g. managed by a separate controller or SecurityCenter policy), disable chart-managed RBAC:
helm install openclaw-operator \
oci://ghcr.io/paperclipinc/charts/openclaw-operator \
--namespace openclaw-operator-system \
--create-namespace \
--set rbac.create=false
The kubebuilder markers in internal/controller/ and the manager rules helper
at charts/openclaw-operator/templates/_helpers.tpl document the minimum
permission set the operator requires.
apiVersion: v1
kind: Secret
metadata:
name: openclaw-api-keys
type: Opaque
stringData:
ANTHROPIC_API_KEY: "sk-ant-..."
apiVersion: openclaw.rocks/v1alpha1
kind: OpenClawInstance
metadata:
name: my-agent
spec:
envFrom:
- secretRef:
name: openclaw-api-keys
storage:
persistence:
enabled: true
size: 10Gi
kubectl apply -f secret.yaml -f openclawinstance.yaml
kubectl get openclawinstances
# NAME PHASE AGE
# my-agent Running 2m
kubectl get pods
# NAME READY STATUS AGE
# my-agent-0 1/1 Running 2m
spec:
config:
raw:
agents:
defaults:
model:
primary: "anthropic/claude-sonnet-4-20250514"
sandbox: true
session:
scope: "per-sender"
spec:
config:
configMapRef:
name: my-openclaw-config
key: openclaw.json
Config changes are detected via SHA-256 hashing and automatically trigger a rolling update. No manual restart needed.
By default, each pod includes an nginx reverse proxy sidecar that forwards traffic to the OpenClaw gateway on loopback. Set spec.gateway.enabled: false to disable it:
spec:
gateway:
image:
repository: docker.io/library/nginx
tag: 1.27-alpine
# digest: sha256:... # takes precedence over tag
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
The image and resource fields are optional. Omit them to retain the defaults above, or set an image digest to make the proxy supply-chain reference immutable.
gateway.bind is set to 0.0.0.0 instead of loopbackgateway-proxy container and its tmp volume are omitted from the podspec.sidecarsgateway.bind: loopback in your config JSON when the proxy is disabled - the gateway will only listen on 127.0.0.1 with nothing forwarding external traffic, making the pod unreachable. The operator emits a GatewayBindConflict warning event if this misconfiguration is detected.ws:// on 0.0.0.0. Ensure your replacement proxy or Ingress handles TLS termination to avoid exposing unencrypted WebSocket traffic (CWE-319).By default the readiness probe is an HTTP GET /readyz against the gateway. For PVC-backed instances, /readyz can stay green while the workspace volume is full or read-only (ENOSPC), so the pod keeps receiving traffic while workspace
No open issues yet, or sync has not completed.