add openclaw

master
Marius Ensrud 2 weeks ago
parent d4f9eb3ab7
commit 26190c21db
No known key found for this signature in database

@ -15,9 +15,6 @@ spec:
solvers:
- selector:
dnsZones:
- 'bassengvegen.com'
- 'app.bassengvegen.com'
- 'dyn.bassengvegen.com'
- 'dogella.com'
dns01:
webhook:
@ -36,6 +33,9 @@ spec:
- 'krystallen-a401.com'
- 'ensup.no'
- 'glossifyme.com'
- 'bassengvegen.com'
- 'app.bassengvegen.com'
- 'dyn.bassengvegen.com'
dns01:
cloudflare:
email: marius@ensrud.net

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

@ -0,0 +1,21 @@
apiVersion: v2
name: openclaw
description: Personal AI assistant with multi-channel support
type: application
version: 1.0.0
appVersion: "2026.2.1"
keywords:
- ai
- assistant
- whatsapp
- telegram
- discord
- claude
- chatgpt
home: https://github.com/openclaw/openclaw
sources:
- https://github.com/openclaw/openclaw
maintainers:
- name: Openclaw Team
url: https://docs.clawd.bot
icon: https://github.com/openclaw/openclaw/raw/main/README-header.png

@ -0,0 +1,253 @@
# Publishing the Openclaw Helm Chart
This guide covers how to publish the Helm chart to make it available for users.
## Quick Start (Automated)
The chart is automatically published to GitHub Pages when you:
1. Update `charts/openclaw/Chart.yaml` version
2. Commit and push to `main` branch
3. GitHub Actions automatically packages and publishes
## Publishing Methods
### Option 1: GitHub Pages (Recommended)
#### Initial Setup
1. **Enable GitHub Pages:**
- Go to repository Settings → Pages
- Source: Deploy from a branch
- Branch: `gh-pages``/ (root)`
- Click Save
2. **Create gh-pages branch (first time only):**
```bash
# Create empty gh-pages branch
git checkout --orphan gh-pages
git rm -rf .
echo "# Openclaw Helm Charts" > README.md
git add README.md
git commit -m "Initial gh-pages"
git push origin gh-pages
git checkout main
```
3. **The workflow will automatically:**
- Package the chart
- Create a GitHub release
- Update the chart repository index
- Publish to GitHub Pages
#### Manual Publishing (if needed)
```bash
# 1. Package the chart
helm package charts/openclaw -d .cr-release-packages
# 2. Create index
helm repo index .cr-release-packages --url https://openclaw.github.io/openclaw
# 3. Commit to gh-pages branch
git checkout gh-pages
cp .cr-release-packages/* .
git add .
git commit -m "Release chart version X.Y.Z"
git push origin gh-pages
git checkout main
```
#### Usage for End Users
Once published, users can install via:
```bash
# Add repo
helm repo add openclaw https://openclaw.github.io/openclaw
helm repo update
# Install
helm install my-openclaw openclaw/openclaw
```
### Option 2: OCI Registry (GitHub Container Registry)
Modern approach using OCI registries:
#### Setup
```bash
# Login to GHCR
echo $GITHUB_TOKEN | helm registry login ghcr.io -u USERNAME --password-stdin
# Package chart
helm package charts/openclaw
# Push to GHCR
helm push openclaw-1.0.0.tgz oci://ghcr.io/openclaw
```
#### Automate in GitHub Actions
```yaml
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push chart to GHCR
run: |
helm package charts/openclaw
helm push openclaw-*.tgz oci://ghcr.io/${{ github.repository_owner }}
```
#### Usage for End Users
```bash
# Install directly from OCI
helm install my-openclaw oci://ghcr.io/openclaw/openclaw --version 1.0.0
```
### Option 3: Artifact Hub
Make your chart discoverable on [Artifact Hub](https://artifacthub.io).
#### Prerequisites
- Chart published to GitHub Pages or OCI registry
- Artifact Hub metadata file
#### Add Artifact Hub metadata
Create `charts/openclaw/artifacthub-repo.yml`:
```yaml
repositoryID: <your-repo-id>
owners:
- name: Openclaw Team
email: team@openclaw.com
```
#### Submit to Artifact Hub
1. Go to https://artifacthub.io
2. Sign in with GitHub
3. Add repository
4. Provide repository URL: `https://openclaw.github.io/openclaw`
5. Wait for verification
### Option 4: ChartMuseum (Self-Hosted)
For private/internal charts:
```bash
# Run ChartMuseum
docker run -d \
-p 8080:8080 \
-v $(pwd)/charts:/charts \
ghcr.io/helm/chartmuseum:latest \
--storage local \
--storage-local-rootdir /charts
# Upload chart
curl --data-binary "@openclaw-1.0.0.tgz" http://localhost:8080/api/charts
```
## Versioning
Follow Semantic Versioning:
- **Chart version** (`version` in Chart.yaml): Chart changes
- **App version** (`appVersion` in Chart.yaml): Openclaw version
### Bumping Versions
```bash
# Update chart version
vim charts/openclaw/Chart.yaml
# Change version: 1.0.0 → 1.1.0
# Update app version (when openclaw version changes)
vim charts/openclaw/Chart.yaml
# Change appVersion: "2026.1.25" → "2026.1.26"
```
### Version Guidelines
- **Major** (1.0.0 → 2.0.0): Breaking changes to values.yaml or behavior
- **Minor** (1.0.0 → 1.1.0): New features, non-breaking changes
- **Patch** (1.0.0 → 1.0.1): Bug fixes, documentation
## Release Checklist
- [ ] Update `version` in `Chart.yaml`
- [ ] Update `appVersion` if openclaw version changed
- [ ] Update `CHANGELOG.md` (if you have one)
- [ ] Test chart locally: `./scripts/test-helm-local.sh`
- [ ] Lint chart: `helm lint charts/openclaw`
- [ ] Commit changes
- [ ] Push to main (triggers automated release)
- [ ] Verify GitHub Pages deployment
- [ ] Test installation from published repo
## Testing Published Chart
```bash
# Add your published repo
helm repo add openclaw https://openclaw.github.io/openclaw
helm repo update
# Search for chart
helm search repo openclaw
# Install from published repo
helm install test openclaw/openclaw --dry-run --debug
```
## Troubleshooting
### Chart not appearing after publish
1. Check GitHub Actions logs
2. Verify gh-pages branch exists
3. Check GitHub Pages settings are enabled
4. Wait 5-10 minutes for GitHub Pages to deploy
### Users getting "not found" error
```bash
# Check index.yaml exists
curl https://openclaw.github.io/openclaw/index.yaml
# Verify chart package exists
curl https://openclaw.github.io/openclaw/openclaw-1.0.0.tgz
```
### Permission denied during publishing
Ensure GitHub Actions has write permissions:
- Settings → Actions → General → Workflow permissions
- Select "Read and write permissions"
## Advanced: Multi-Chart Repository
If you add more charts:
```
charts/
openclaw/
openclaw-operator/
openclaw-monitoring/
```
The `helm/chart-releaser-action` automatically handles multiple charts.
## References
- [Helm Chart Repository Guide](https://helm.sh/docs/topics/chart_repository/)
- [Chart Releaser Action](https://github.com/helm/chart-releaser-action)
- [Artifact Hub](https://artifacthub.io/docs/topics/repositories/)
- [OCI Registry Support](https://helm.sh/docs/topics/registries/)

@ -0,0 +1,302 @@
# Openclaw Helm Chart
Personal AI assistant with multi-channel support (WhatsApp, Telegram, Discord, and more) running on Kubernetes.
## TL;DR
```bash
helm install my-openclaw ./openclaw \
--set secrets.data.anthropicApiKey=sk-ant-xxx \
--set secrets.data.gatewayToken=$(openssl rand -hex 32)
```
## Introduction
This chart deploys Openclaw Gateway on a Kubernetes cluster using the Helm package manager.
**Important:** Openclaw is designed as a single-user personal assistant. The chart enforces `replicas: 1` and does not support horizontal scaling.
## Prerequisites
- Kubernetes 1.19+
- Helm 3.x
- PV provisioner support in the underlying infrastructure (for persistent storage)
- **Optional:** Ingress controller (NGINX, Traefik, etc.) for external access
- **Optional:** cert-manager for automatic TLS certificates
## Installing the Chart
### Basic Installation
```bash
helm install my-openclaw ./openclaw
```
### With Custom Values
```bash
helm install my-openclaw ./openclaw \
--values examples/values-production.yaml \
--set secrets.data.anthropicApiKey=sk-ant-xxx \
--set ingress.hosts[0].host=assistant.example.com
```
### From Examples
```bash
# Basic (local testing)
helm install my-openclaw ./openclaw -f examples/values-basic.yaml
# Production
helm install my-openclaw ./openclaw -f examples/values-production.yaml
# Fly.io-like setup
helm install my-openclaw ./openclaw -f examples/values-fly-like.yaml
```
## Uninstalling the Chart
```bash
helm uninstall my-openclaw
# Also delete PVCs (data will be lost)
kubectl delete pvc -l app.kubernetes.io/instance=my-openclaw
```
## Configuration
### Key Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `replicaCount` | Number of replicas (must be 1) | `1` |
| `image.repository` | Openclaw image repository | `openclaw/openclaw` |
| `image.tag` | Image tag | Chart appVersion |
| `gateway.bind` | Gateway binding mode (`loopback`, `lan`, `auto`) | `lan` |
| `gateway.port` | Gateway port | `18789` |
| `secrets.data.anthropicApiKey` | Anthropic API key | `""` |
| `secrets.data.openaiApiKey` | OpenAI API key | `""` |
| `secrets.data.gatewayToken` | Gateway authentication token (auto-generated if empty) | `""` |
| `persistence.enabled` | Enable persistent storage | `true` |
| `persistence.size` | PVC size | `10Gi` |
| `ingress.enabled` | Enable Ingress | `false` |
| `ingress.className` | Ingress class | `nginx` |
| `resources.limits.memory` | Memory limit | `2Gi` |
| `resources.requests.memory` | Memory request | `512Mi` |
### Full Values Reference
See [values.yaml](values.yaml) for all available parameters.
## Storage
The chart creates a StatefulSet with a persistent volume claim template that mounts storage at two locations:
- `/home/node/.openclaw` (subPath: `openclaw-state`) - Configuration, sessions, device identity, SQLite databases
- `/home/node/clawd` (subPath: `openclaw-workspace`) - Agent workspace files
**Storage Class:** By default, uses the cluster's default storage class. Override with `persistence.storageClass`.
**Size Recommendations:**
- Development/Testing: 5-10Gi
- Production (single user): 20Gi+
- Depends on session history and workspace usage
## Secrets Management
### Option 1: Inline Secrets (Development Only)
```bash
helm install my-openclaw ./openclaw \
--set secrets.data.anthropicApiKey=sk-ant-xxx \
--set secrets.data.gatewayToken=$(openssl rand -hex 32)
```
### Option 2: External Secret (Production)
Create a Kubernetes Secret:
```bash
kubectl create secret generic openclaw-secrets \
--from-literal=gatewayToken=$(openssl rand -hex 32) \
--from-literal=anthropicApiKey=sk-ant-xxx \
--from-literal=discordBotToken=MTQ...
```
Install with existing secret:
```bash
helm install my-openclaw ./openclaw \
--set secrets.create=false \
--set secrets.existingSecret=openclaw-secrets
```
### Option 3: External Secrets Operator
Use External Secrets Operator to sync from Vault, AWS Secrets Manager, etc.
## Ingress
### NGINX Ingress Controller
```yaml
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/websocket-services: "{{ include \"openclaw.fullname\" . }}"
hosts:
- host: assistant.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: openclaw-tls
hosts:
- assistant.example.com
```
**Important:** WebSocket support requires extended timeouts (3600s recommended).
### Traefik
```yaml
ingress:
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
```
## Health Checks
The chart uses exec-based probes that run the CLI health command:
```yaml
livenessProbe:
exec:
command:
- sh
- -c
- node dist/index.js health --token "${CLAWDBOT_GATEWAY_TOKEN}" || exit 1
```
## Upgrading
```bash
# Pull latest chart changes
git pull
# Upgrade with current values
helm upgrade my-openclaw ./openclaw --reuse-values
# Upgrade with new values
helm upgrade my-openclaw ./openclaw -f values-production.yaml
```
## Troubleshooting
### Pod Not Starting
```bash
# Check pod events
kubectl describe pod my-openclaw-0
# Check logs
kubectl logs my-openclaw-0
```
### OOM (Out of Memory)
Increase memory limits:
```yaml
resources:
limits:
memory: 4Gi
requests:
memory: 1Gi
```
**Note:** 512MB is too small for production. 2GB recommended minimum.
### PVC Not Binding
```bash
# Check PVC status
kubectl get pvc
# Check storage class
kubectl get storageclass
# Describe for events
kubectl describe pvc data-my-openclaw-0
```
### WebSocket Connections Timing Out
Ensure Ingress has WebSocket annotations:
```yaml
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
```
### Gateway Lock File Issues
If the gateway won't start due to stale lock files:
```bash
kubectl exec my-openclaw-0 -- rm -f /home/node/.openclaw/gateway.*.lock
kubectl delete pod my-openclaw-0 # Restart pod
```
## Examples
### Local Testing (Minikube/Kind/Docker Desktop)
```bash
# Build local image
docker build -t openclaw:local .
# Load into cluster (example for Minikube)
minikube image load openclaw:local
# Install chart
helm install test ./openclaw \
-f examples/values-basic.yaml \
--set image.repository=openclaw \
--set image.tag=local \
--set image.pullPolicy=Never
```
### Production Deployment
```bash
# Create external secret first
kubectl create secret generic openclaw-secrets \
--from-literal=gatewayToken=$(openssl rand -hex 32) \
--from-literal=anthropicApiKey=$ANTHROPIC_API_KEY
# Install with production values
helm install my-openclaw ./openclaw -f examples/values-production.yaml
```
## Documentation
- [Openclaw Documentation](https://docs.clawd.bot)
- [Kubernetes Installation Guide](https://docs.clawd.bot/install/kubernetes)
- [GitHub Repository](https://github.com/openclaw/openclaw)
## Support
- [GitHub Issues](https://github.com/openclaw/openclaw/issues)
- [Documentation](https://docs.clawd.bot)
## License
See [LICENSE](https://github.com/openclaw/openclaw/blob/main/LICENSE)

@ -0,0 +1,33 @@
# Basic configuration for local testing or development
image:
repository: openclaw
tag: local
pullPolicy: Never
gateway:
bind: lan
port: 18789
allowUnconfigured: false
secrets:
create: true
data:
# Set via --set or environment variables
anthropicApiKey: ""
gatewayToken: ""
persistence:
enabled: true
size: 5Gi
ingress:
enabled: false
resources:
limits:
memory: 2Gi
cpu: 1000m
requests:
memory: 512Mi
cpu: 250m

@ -0,0 +1,55 @@
# Configuration similar to Fly.io deployment
# Mimics fly.toml settings
image:
repository: ghcr.io/openclaw/openclaw
tag: "2026.1.25"
pullPolicy: IfNotPresent
gateway:
bind: lan
port: 3000 # Fly.io uses port 3000
allowUnconfigured: true
extraArgs: []
env:
NODE_ENV: production
CLAWDBOT_STATE_DIR: /home/node/.openclaw
CLAWDBOT_WORKSPACE_DIR: /home/node/clawd
NODE_OPTIONS: "--max-old-space-size=1536" # Fly.io recommendation
secrets:
create: false
existingSecret: openclaw-secrets
persistence:
enabled: true
size: 1Gi # Similar to Fly.io volume size
resources:
limits:
memory: 2Gi # shared-cpu-2x on Fly.io
cpu: 1000m
requests:
memory: 512Mi
cpu: 250m
service:
type: ClusterIP
port: 3000
ingress:
enabled: true
className: nginx
domain: "my-openclaw.example.com"
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/websocket-services: "openclaw"
tls:
enabled: true
secretName: openclaw-tls
certManager:
enabled: true
issuer: letsencrypt-prod

@ -0,0 +1,89 @@
# Production configuration with Ingress and external secrets
image:
repository: ghcr.io/openclaw/openclaw
tag: "2026.1.25"
pullPolicy: IfNotPresent
gateway:
bind: lan
port: 18789
allowUnconfigured: false
# Use external secret in production
secrets:
create: false
existingSecret: openclaw-secrets
config:
create: true
data:
agents:
defaults:
model:
primary: "anthropic/claude-opus-4-5"
fallbacks:
- "anthropic/claude-sonnet-4-5"
maxConcurrent: 4
sandbox:
mode: "off"
list:
- id: main
default: true
auth:
profiles:
"anthropic:default":
mode: token
provider: anthropic
gateway:
mode: local
bind: auto
auth:
mode: token
controlUi:
enabled: true
channels:
discord:
enabled: true
meta:
lastTouchedVersion: "2026.1.25"
persistence:
enabled: true
storageClass: fast-ssd
size: 20Gi
accessMode: ReadWriteOnce
resources:
limits:
memory: 4Gi
cpu: 2000m
requests:
memory: 1Gi
cpu: 500m
ingress:
enabled: true
className: nginx
domain: "assistant.example.com"
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/websocket-services: "openclaw"
tls:
enabled: true
secretName: openclaw-tls
certManager:
enabled: true
issuer: letsencrypt-prod
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: workload-type
operator: In
values:
- stateful

@ -0,0 +1,73 @@
Thank you for installing {{ .Chart.Name }}!
Your Openclaw gateway is starting up. This may take 1-2 minutes.
{{- if .Values.ingress.enabled }}
{{- $domain := .Values.ingress.domain }}
{{- $tlsEnabled := .Values.ingress.tls.enabled }}
{{- if $domain }}
==========================================================
Openclaw is accessible at:
{{- if $tlsEnabled }}
https://{{ $domain }}
{{- else }}
http://{{ $domain }}
{{- end }}
==========================================================
{{- end }}
{{- end }}
1. Check the gateway status:
kubectl get statefulset {{ include "openclaw.fullname" . }} -n {{ .Release.Namespace }}
kubectl logs -f {{ include "openclaw.fullname" . }}-0 -n {{ .Release.Namespace }}
2. Access the Control UI:
{{- if .Values.ingress.enabled }}
{{- $domain := .Values.ingress.domain }}
{{- $tlsEnabled := .Values.ingress.tls.enabled }}
{{- if $domain }}
{{- if $tlsEnabled }}
https://{{ $domain }}
{{- else }}
http://{{ $domain }}
{{- end }}
{{- else }}
{{- range .Values.ingress.hosts }}
https://{{ .host }}
{{- end }}
{{- end }}
{{- else }}
# Port-forward to access locally:
kubectl port-forward {{ include "openclaw.fullname" . }}-0 {{ .Values.gateway.port }}:{{ .Values.gateway.port }} -n {{ .Release.Namespace }}
Then visit: http://localhost:{{ .Values.gateway.port }}
{{- end }}
3. Your gateway token (for authentication):
kubectl get secret {{ include "openclaw.secretName" . }} -n {{ .Release.Namespace }} -o jsonpath='{.data.gatewayToken}' | base64 -d && echo
4. Configure channels:
kubectl exec -it {{ include "openclaw.fullname" . }}-0 -n {{ .Release.Namespace }} -- node dist/index.js channels add --channel discord --token YOUR_BOT_TOKEN
5. Check health:
kubectl exec -it {{ include "openclaw.fullname" . }}-0 -n {{ .Release.Namespace }} -- node dist/index.js health
Documentation: https://docs.clawd.bot
Support: https://github.com/openclaw/openclaw/issues
{{- if not .Values.persistence.enabled }}
WARNING: Persistence is disabled. State will be lost on pod restart.
{{- end }}
{{- if eq .Values.gateway.bind "loopback" }}
WARNING: Gateway is bound to loopback. External access will not work.
{{- end }}

@ -0,0 +1,84 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "openclaw.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "openclaw.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "openclaw.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "openclaw.labels" -}}
helm.sh/chart: {{ include "openclaw.chart" . }}
{{ include "openclaw.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "openclaw.selectorLabels" -}}
app.kubernetes.io/name: {{ include "openclaw.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "openclaw.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "openclaw.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the name of the secret to use
*/}}
{{- define "openclaw.secretName" -}}
{{- if .Values.secrets.existingSecret }}
{{- .Values.secrets.existingSecret }}
{{- else }}
{{- include "openclaw.fullname" . }}
{{- end }}
{{- end }}
{{/*
Create the name of the config map to use
*/}}
{{- define "openclaw.configMapName" -}}
{{- if .Values.config.existingConfigMap }}
{{- .Values.config.existingConfigMap }}
{{- else }}
{{- printf "%s-config" (include "openclaw.fullname" .) }}
{{- end }}
{{- end }}

@ -0,0 +1,11 @@
{{- if .Values.config.create -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "openclaw.configMapName" . }}
labels:
{{- include "openclaw.labels" . | nindent 4 }}
data:
openclaw.json: |
{{ .Values.config.data | toJson | nindent 4 }}
{{- end }}

@ -0,0 +1,62 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "openclaw.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- $domain := .Values.ingress.domain -}}
{{- $tlsEnabled := .Values.ingress.tls.enabled -}}
{{- $tlsSecretName := .Values.ingress.tls.secretName | default (printf "%s-tls" $fullName) -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "openclaw.labels" . | nindent 4 }}
annotations:
{{- if and $tlsEnabled .Values.ingress.tls.certManager.enabled }}
cert-manager.io/cluster-issuer: {{ .Values.ingress.tls.certManager.issuer | quote }}
{{- end }}
{{- with .Values.ingress.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if $tlsEnabled }}
tls:
- hosts:
{{- if $domain }}
- {{ $domain | quote }}
{{- end }}
{{- range .Values.ingress.hosts }}
- {{ .host | quote }}
{{- end }}
secretName: {{ $tlsSecretName }}
{{- end }}
rules:
{{- if $domain }}
- host: {{ $domain | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}

@ -0,0 +1,23 @@
{{- if .Values.secrets.create -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "openclaw.fullname" . }}
labels:
{{- include "openclaw.labels" . | nindent 4 }}
type: Opaque
stringData:
gatewayToken: {{ .Values.secrets.data.gatewayToken | default (randAlphaNum 32) | quote }}
{{- with .Values.secrets.data.anthropicApiKey }}
anthropicApiKey: {{ . | quote }}
{{- end }}
{{- with .Values.secrets.data.openaiApiKey }}
openaiApiKey: {{ . | quote }}
{{- end }}
{{- with .Values.secrets.data.discordBotToken }}
discordBotToken: {{ . | quote }}
{{- end }}
{{- with .Values.secrets.data.telegramBotToken }}
telegramBotToken: {{ . | quote }}
{{- end }}
{{- end }}

@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "openclaw.fullname" . }}
labels:
{{- include "openclaw.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: gateway
protocol: TCP
name: gateway
selector:
{{- include "openclaw.selectorLabels" . | nindent 4 }}

@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "openclaw.serviceAccountName" . }}
labels:
{{- include "openclaw.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

@ -0,0 +1,202 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "openclaw.fullname" . }}
labels:
{{- include "openclaw.labels" . | nindent 4 }}
spec:
serviceName: {{ include "openclaw.fullname" . }}
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "openclaw.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- if .Values.config.create }}
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- end }}
{{- if .Values.secrets.create }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- end }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "openclaw.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "openclaw.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- if or .Values.config.create .Values.initContainers }}
initContainers:
{{- if .Values.config.create }}
- name: init-config
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- sh
- -c
- |
echo "Initializing configuration..."
mkdir -p /home/node/.openclaw
if [ ! -f /home/node/.openclaw/openclaw.json ]; then
echo "Copying default config..."
cp /config/openclaw.json /home/node/.openclaw/openclaw.json
echo "Config initialized"
else
echo "Config already exists, skipping"
fi
volumeMounts:
- name: config
mountPath: /config
- name: data
mountPath: /home/node/.openclaw
subPath: openclaw-state
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
{{- end }}
{{- with .Values.initContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
containers:
- name: gateway
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- node
- dist/index.js
- gateway
- --bind
- {{ .Values.gateway.bind }}
- --port
- {{ .Values.gateway.port | quote }}
{{- if .Values.gateway.allowUnconfigured }}
- --allow-unconfigured
{{- end }}
{{- range .Values.gateway.extraArgs }}
- {{ . }}
{{- end }}
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
- name: CLAWDBOT_GATEWAY_PORT
value: {{ .Values.gateway.port | quote }}
- name: CLAWDBOT_GATEWAY_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "openclaw.secretName" . }}
key: gatewayToken
{{- if .Values.secrets.data.anthropicApiKey }}
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: {{ include "openclaw.secretName" . }}
key: anthropicApiKey
optional: true
{{- end }}
{{- if .Values.secrets.data.openaiApiKey }}
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: {{ include "openclaw.secretName" . }}
key: openaiApiKey
optional: true
{{- end }}
{{- if .Values.secrets.data.discordBotToken }}
- name: DISCORD_BOT_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "openclaw.secretName" . }}
key: discordBotToken
optional: true
{{- end }}
{{- if .Values.secrets.data.telegramBotToken }}
- name: TELEGRAM_BOT_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "openclaw.secretName" . }}
key: telegramBotToken
optional: true
{{- end }}
ports:
- name: gateway
containerPort: {{ .Values.gateway.port }}
protocol: TCP
volumeMounts:
- name: data
mountPath: /home/node/.openclaw
subPath: openclaw-state
- name: data
mountPath: /home/node/clawd
subPath: openclaw-workspace
{{- if .Values.livenessProbe.enabled }}
livenessProbe:
{{- omit .Values.livenessProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
{{- omit .Values.readinessProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.startupProbe.enabled }}
startupProbe:
{{- omit .Values.startupProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- with .Values.lifecycle }}
lifecycle:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
{{- with .Values.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if .Values.config.create }}
- name: config
configMap:
name: {{ include "openclaw.configMapName" . }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.persistence.enabled }}
volumeClaimTemplates:
- metadata:
name: data
{{- with .Values.persistence.annotations }}
annotations:
{{- toYaml . | nindent 10 }}
{{- end }}
spec:
accessModes:
- {{ .Values.persistence.accessMode }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- with .Values.persistence.selector }}
selector:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- end }}

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "openclaw.fullname" . }}-test-connection"
labels:
{{- include "openclaw.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "openclaw.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

@ -0,0 +1,74 @@
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 1,
"default": 1,
"description": "Must be 1 for single-user architecture. Openclaw does not support horizontal scaling."
},
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
},
"tag": {
"type": "string"
}
}
},
"gateway": {
"type": "object",
"properties": {
"bind": {
"type": "string",
"enum": ["loopback", "lan", "auto"],
"description": "Gateway binding mode"
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
}
}
},
"persistence": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"size": {
"type": "string",
"pattern": "^[0-9]+(Gi|Mi|Ti)$"
},
"accessMode": {
"type": "string",
"enum": ["ReadWriteOnce", "ReadOnlyMany", "ReadWriteMany"]
}
}
},
"service": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer"]
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
}
}
}
},
"required": ["replicaCount"]
}

@ -0,0 +1,233 @@
# Default values for openclaw.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# Image configuration
image:
registry: ghcr.io
repository: openclaw/openclaw
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# Replica count - MUST be 1 for single-user architecture
replicaCount: 1
# Service account
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# Pod annotations
podAnnotations: {}
# Pod security context
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
# Container security context
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false # Node needs write access to /tmp
# Gateway configuration
gateway:
# Binding mode: loopback, lan, auto
bind: lan
# Gateway port
port: 18789
# Allow unconfigured startup (creates minimal config)
allowUnconfigured: false
# Additional CLI arguments
extraArgs: []
# Environment variables (non-sensitive)
env:
NODE_ENV: production
CLAWDBOT_STATE_DIR: /home/node/.openclaw
CLAWDBOT_WORKSPACE_DIR: /home/node/clawd
NODE_OPTIONS: "--max-old-space-size=2048"
# Secrets configuration
secrets:
# Create secret from values (dev/testing only - use existingSecret in production)
create: true
# Use existing secret (production)
existingSecret: ""
# Secret data (only used if create is true)
data:
anthropicApiKey: ""
openaiApiKey: ""
discordBotToken: ""
telegramBotToken: ""
gatewayToken: "" # Auto-generated if empty
# Config file (openclaw.json)
config:
# Create ConfigMap from inline config
create: true
# Use existing ConfigMap
existingConfigMap: ""
# Config data (JSON5 format)
data:
agents:
defaults:
model:
primary: "anthropic/claude-opus-4-5"
fallbacks:
- "anthropic/claude-sonnet-4-5"
- "openai/gpt-4o"
maxConcurrent: 4
sandbox:
mode: "off" # Disable Docker-in-Docker for Kubernetes
list:
- id: main
default: true
auth:
profiles:
"anthropic:default":
mode: token
provider: anthropic
"openai:default":
mode: token
provider: openai
gateway:
mode: local
bind: auto
auth:
mode: token
controlUi:
enabled: true
channels: {}
# Persistence
persistence:
enabled: true
# Storage class (use cluster default if empty)
storageClass: ""
# Access mode
accessMode: ReadWriteOnce
# Volume size
size: 10Gi
# Annotations
annotations: {}
# Selector
selector: {}
# Service configuration
service:
type: ClusterIP
port: 18789
annotations: {}
# Ingress configuration
ingress:
enabled: false
className: nginx
# Simplified domain configuration (recommended)
# Set your domain here - TLS will be auto-configured if tls.enabled is true
domain: "" # e.g., "openclaw.yourdomain.com"
# TLS configuration
tls:
enabled: false
# Secret name for TLS certificate (auto-generated name if empty)
secretName: ""
# Use cert-manager for automatic certificate provisioning
certManager:
enabled: false
issuer: "letsencrypt-prod"
# Additional annotations
annotations: {}
# WebSocket support (recommended for real-time features):
# nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
# nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# Advanced: Manual host configuration (overrides domain if set)
hosts: []
# - host: openclaw.example.com
# paths:
# - path: /
# pathType: Prefix
# Resource limits/requests
resources:
limits:
memory: 2Gi
cpu: 1000m
requests:
memory: 512Mi
cpu: 250m
# Node selector
nodeSelector: {}
# Tolerations
tolerations: []
# Affinity rules
affinity: {}
# Init containers (for setup tasks)
initContainers: []
# Extra containers (sidecars)
extraContainers: []
# Lifecycle hooks
lifecycle:
preStop:
exec:
command:
- sh
- -c
- rm -f /home/node/.openclaw/gateway.*.lock; sleep 10
# Probes
# Use tcpSocket for startup/readiness (lightweight), exec for liveness (thorough)
livenessProbe:
enabled: true
exec:
command:
- node
- dist/index.js
- health
initialDelaySeconds: 60
periodSeconds: 60
timeoutSeconds: 30
failureThreshold: 3
readinessProbe:
enabled: true
tcpSocket:
port: 18789
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
enabled: true
tcpSocket:
port: 18789
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30 # 150 seconds max startup time
Loading…
Cancel
Save