{{- $tag := .Chart.AppVersion -}} {{- with .Values.image.variant }}{{ $tag = printf "%s-%s" $tag . }}{{ end -}} {{- $image := printf "%s:%s" .Values.image.repository $tag -}} {{- /* Home-relative dirs kept on the PVC as subPath mounts under /home/node. Not the whole $HOME: the image ships Chromium in ~/.cache/ms-playwright. */ -}} {{- $paths := list ".openclaw" -}} {{- if .Values.claudeCli.enabled }} {{- $paths = concat $paths (list ".claude" ".local/bin" ".local/share/claude" ".local/state/claude") -}} {{- end }} {{- $paths = concat $paths .Values.persistence.extraHomePaths | uniq -}} apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }} labels: app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/component: gateway annotations: version-source: {{ index .Chart.Annotations "version-source" }} spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: {{ .Release.Name }} template: metadata: labels: app: {{ .Release.Name }} annotations: checksum/seed: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} spec: automountServiceAccountToken: false {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} initContainers: # Creates the PVC subPath dirs as uid 1000 (kubelet would create them # root-owned), then seeds config if missing: the PVC copy owns config # after first boot. - name: seed-config image: busybox:1.38 imagePullPolicy: IfNotPresent command: - sh - -c - | {{- range $paths }} mkdir -p /data/{{ . }} {{- end }} mkdir -p /data/.openclaw/workspace [ -f /data/.openclaw/openclaw.json ] || cp /seed/openclaw.json /data/.openclaw/openclaw.json [ -f /data/.openclaw/workspace/AGENTS.md ] || cp /seed/AGENTS.md /data/.openclaw/workspace/AGENTS.md {{- with .Values.containerSecurityContext }} securityContext: {{- toYaml . | nindent 12 }} {{- end }} resources: requests: cpu: 10m memory: 16Mi limits: cpu: 100m memory: 64Mi volumeMounts: - name: data mountPath: /data - name: seed mountPath: /seed {{- if .Values.claudeCli.enabled }} # Claude Code isn't in the image: install it once into the PVC-backed # ~/.local (the native installer self-updates from there afterwards). - name: install-claude image: {{ $image | quote }} imagePullPolicy: {{ .Values.image.pullPolicy }} command: - bash - -c - | if [ -x /home/node/.local/bin/claude ]; then echo "claude already installed: $(/home/node/.local/bin/claude --version || true)" else curl -fsSL {{ .Values.claudeCli.installUrl }} | bash fi env: - name: HOME value: /home/node - name: CLAUDE_CONFIG_DIR value: /home/node/.claude {{- with .Values.containerSecurityContext }} securityContext: {{- toYaml . | nindent 12 }} {{- end }} resources: requests: cpu: 50m memory: 128Mi limits: cpu: "1" memory: 1Gi volumeMounts: {{- range $paths }} - name: data mountPath: /home/node/{{ . }} subPath: {{ . }} {{- end }} - name: tmp mountPath: /tmp {{- end }} containers: - name: {{ .Chart.Name }} image: {{ $image | quote }} imagePullPolicy: {{ .Values.image.pullPolicy }} command: - node - /app/dist/index.js - gateway - run {{- with .Values.containerSecurityContext }} securityContext: {{- toYaml . | nindent 12 }} {{- end }} ports: - containerPort: {{ .Values.httpPort }} name: http env: - name: TZ value: {{ .Values.timezone | quote }} - name: HOME value: /home/node - name: OPENCLAW_CONFIG_DIR value: /home/node/.openclaw - name: NODE_ENV value: production {{- if .Values.claudeCli.enabled }} # Keeps .claude.json inside ~/.claude (a PVC dir) instead of $HOME; # both OpenClaw and the claude CLI honour it. - name: CLAUDE_CONFIG_DIR value: /home/node/.claude {{- end }} {{- range $key, $value := .Values.env }} - name: {{ $key }} value: {{ $value | quote }} {{- end }} {{- with .Values.existingSecret }} envFrom: - secretRef: name: {{ . }} {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} # Probes assert the JSON probe contract, not just the status code: the # Control UI answers unknown paths with a catch-all 200. /startupz # ignores channel health, so one failing channel account can't pull # the Gateway out of Service endpoints (/readyz is channel-aware). startupProbe: exec: command: - node - -e - "require('http').get('http://127.0.0.1:{{ .Values.httpPort }}/startupz', r => { let b = ''; r.on('data', d => b += d); r.on('end', () => { try { process.exit(r.statusCode < 400 && JSON.parse(b).status === 'started' ? 0 : 1); } catch { process.exit(1); } }); }).on('error', () => process.exit(1))" periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 30 readinessProbe: exec: command: - node - -e - "require('http').get('http://127.0.0.1:{{ .Values.httpPort }}/startupz', r => { let b = ''; r.on('data', d => b += d); r.on('end', () => { try { process.exit(r.statusCode < 400 && JSON.parse(b).status === 'started' ? 0 : 1); } catch { process.exit(1); } }); }).on('error', () => process.exit(1))" periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 livenessProbe: exec: command: - node - -e - "require('http').get('http://127.0.0.1:{{ .Values.httpPort }}/healthz', r => { let b = ''; r.on('data', d => b += d); r.on('end', () => { try { process.exit(r.statusCode < 400 && JSON.parse(b).ok === true ? 0 : 1); } catch { process.exit(1); } }); }).on('error', () => process.exit(1))" periodSeconds: 30 timeoutSeconds: 10 failureThreshold: 3 volumeMounts: {{- range $paths }} - name: data mountPath: /home/node/{{ . }} subPath: {{ . }} {{- end }} - name: tmp mountPath: /tmp - name: shm mountPath: /dev/shm volumes: - name: data {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ .Values.persistence.existingClaim | default (printf "%s-data" .Release.Name) }} {{- else }} emptyDir: {} {{- end }} - name: seed configMap: name: {{ .Release.Name }}-seed - name: tmp emptyDir: {} - name: shm emptyDir: medium: Memory sizeLimit: {{ .Values.shmSize }}