Merge pull request #62 from offendingcommit/worktree-feat+helm-chart

feat(helm): add Helm chart for self-hosted openconcho web UI
This commit is contained in:
Offending Commit
2026-06-03 16:35:58 -05:00
committed by GitHub
18 changed files with 897 additions and 0 deletions

View File

@@ -44,3 +44,34 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
publish-chart:
name: Package & push Helm chart to GHCR
runs-on: ubuntu-latest
needs: [publish]
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- uses: azure/setup-helm@v4
- name: Derive chart version
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR (Helm OCI)
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io \
--username "${{ github.actor }}" \
--password-stdin
- name: Package chart
run: |
helm package charts/openconcho \
--version "${{ steps.version.outputs.VERSION }}" \
--app-version "${{ steps.version.outputs.VERSION }}"
- name: Push chart
run: |
helm push "openconcho-${{ steps.version.outputs.VERSION }}.tgz" \
oci://ghcr.io/${{ github.repository_owner }}/charts

View File

@@ -38,6 +38,7 @@ Frontend UI for self-hosted Honcho instances — browse memories, peers, session
| `packages/web/src/test/` | Vitest unit/integration tests + setup |
| `packages/web/e2e/` | Playwright e2e specs |
| `packages/desktop/` | Tauri shell that bundles the built web app |
| `charts/openconcho/` | Helm 3 chart for self-hosting on Kubernetes (OCI artifact on GHCR) |
| `.claude/rules/` | Coding conventions (auto-loaded; stack-agnostic, applies to all agents) |
| `docs/` | Architecture and references |

View File

@@ -114,6 +114,34 @@ profiles: `make up` runs the `dev` profile (`build: .`), `make prod` runs the
(comma-separated host globs) for when you expose the proxy. Full details and env
vars are in [`docs/docker.md`](docs/docker.md).
### Kubernetes (Helm)
The chart is published as an OCI artifact to GHCR on every tagged release.
```bash
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
--version 0.14.0 \
--create-namespace --namespace openconcho \
--set honcho.defaultUrl=https://honcho.example.com
```
Enable an Ingress and TLS:
```bash
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
--version 0.14.0 \
--create-namespace --namespace openconcho \
--set honcho.defaultUrl=https://honcho.example.com \
--set ingress.enabled=true \
--set ingress.className=nginx \
--set 'ingress.hosts[0].host=openconcho.example.com' \
--set 'ingress.hosts[0].paths[0].path=/' \
--set 'ingress.tls[0].secretName=openconcho-tls' \
--set 'ingress.tls[0].hosts[0]=openconcho.example.com'
```
Full chart documentation, configuration reference, and an ArgoCD Application example are in [`charts/openconcho/README.md`](charts/openconcho/README.md).
### Connecting to your instance
1. Enter the base URL of your Honcho instance (e.g. `http://localhost:8000`)

View File

@@ -0,0 +1,16 @@
apiVersion: v2
name: openconcho
description: Self-hosted UI for Honcho — browse memories, peers, sessions, conclusions, and chat with memory context.
type: application
version: 0.14.0
appVersion: "0.14.0"
keywords:
- honcho
- memory
- ai
home: https://github.com/offendingcommit/openconcho
sources:
- https://github.com/offendingcommit/openconcho
maintainers:
- name: offendingcommit
url: https://github.com/offendingcommit

229
charts/openconcho/README.md Normal file
View File

@@ -0,0 +1,229 @@
# openconcho Helm Chart
Helm 3 chart for self-hosting the [openconcho](https://github.com/offendingcommit/openconcho) web UI on Kubernetes.
The chart deploys a single nginx-unprivileged container (port 8080, UID 101) that serves the React SPA and reverse-proxies Honcho API calls under `/api` to avoid browser CORS issues.
## Prerequisites
- Kubernetes 1.25+
- Helm 3.10+
- A running [Honcho](https://github.com/plastic-labs/honcho) instance reachable from within the cluster (or via a configured ingress)
## Installing
Add the chart repository:
```bash
helm registry login ghcr.io --username <github-username> --password <github-token>
```
Install the chart:
```bash
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
--version 0.14.0 \
--set honcho.defaultUrl=https://honcho.example.com
```
Or with a values file (recommended):
```bash
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
--version 0.14.0 \
-f my-values.yaml
```
## Upgrading
```bash
helm upgrade openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
--version <new-version> \
-f my-values.yaml
```
## Uninstalling
```bash
helm uninstall openconcho
```
## Configuration
All values with their defaults are documented in [`values.yaml`](values.yaml). Key options:
| Value | Default | Description |
|---|---|---|
| `replicaCount` | `1` | Number of pod replicas |
| `image.repository` | `ghcr.io/offendingcommit/openconcho-web` | Container image |
| `image.tag` | `""` | Tag; defaults to chart `appVersion` |
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
| `honcho.defaultUrl` | `""` | Honcho URL pre-seeded in the UI |
| `honcho.upstreamAllowlist` | `""` | SSRF guard (comma-separated host globs) |
| `service.type` | `ClusterIP` | `ClusterIP` / `NodePort` / `LoadBalancer` |
| `service.port` | `80` | Service port |
| `ingress.enabled` | `false` | Enable Ingress resource |
| `ingress.className` | `""` | IngressClass name |
| `autoscaling.enabled` | `false` | Enable HorizontalPodAutoscaler |
| `podDisruptionBudget.enabled` | `false` | Enable PodDisruptionBudget |
| `networkPolicy.enabled` | `false` | Enable NetworkPolicy (same-namespace only) |
| `resources.requests.memory` | `32Mi` | Memory request |
| `resources.limits.memory` | `128Mi` | Memory limit |
## Examples
### Minimal (ClusterIP, no ingress)
```yaml
honcho:
defaultUrl: http://honcho.honcho.svc.cluster.local:8000
```
### With Ingress and TLS (cert-manager)
```yaml
honcho:
defaultUrl: https://honcho.example.com
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: openconcho.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: openconcho-tls
hosts:
- openconcho.example.com
```
### With autoscaling and disruption budget
```yaml
replicaCount: 2
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
```
### With NetworkPolicy
> **Note:** When `networkPolicy.enabled=true` and `ingress.enabled=true`, you must add
> a policy that allows traffic from the ingress-controller namespace. Run
> `helm status <release>` for the exact `kubectl edit` command after install.
```yaml
networkPolicy:
enabled: true
ingress:
enabled: true
className: nginx
hosts:
- host: openconcho.example.com
paths:
- path: /
pathType: Prefix
```
### Private registry
```yaml
image:
repository: registry.example.com/myorg/openconcho-web
tag: "0.14.0"
pullPolicy: Always
imagePullSecrets:
- name: registry-credentials
```
## ArgoCD Application
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: openconcho
namespace: argocd
spec:
project: default
source:
repoURL: ghcr.io/offendingcommit/charts
chart: openconcho
targetRevision: 0.14.0
helm:
valuesObject:
honcho:
defaultUrl: https://honcho.example.com
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: openconcho.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: openconcho-tls
hosts:
- openconcho.example.com
destination:
server: https://kubernetes.default.svc
namespace: openconcho
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
```
> OCI chart sources require ArgoCD 2.10+ (OCI Helm support GA).
## Helm tests
After install, run the bundled tests to verify the deployment is healthy:
```bash
helm test openconcho
```
Two test pods run and exit 0 on success:
| Test | What it checks |
|---|---|
| `test-healthz` | `GET /healthz` body equals `ok` |
| `test-spa-root` | `GET /` returns HTTP 200 |
Pass `--logs` to see output from failing pods:
```bash
helm test openconcho --logs
```
## Security posture
| Control | Value |
|---|---|
| Run as UID/GID | 101 (nginx-unprivileged) |
| `runAsNonRoot` | `true` |
| `readOnlyRootFilesystem` | `true` |
| Linux capabilities | all dropped |
| `seccompProfile` | `RuntimeDefault` |
| `allowPrivilegeEscalation` | `false` |
| `automountServiceAccountToken` | `false` |
| Writable paths | `/var/cache/nginx`, `/var/run`, `/tmp` (tmpfs) |

View File

@@ -0,0 +1,40 @@
OpenConcho {{ .Chart.AppVersion }} deployed to namespace {{ .Release.Namespace }}.
{{- if .Values.ingress.enabled }}
Access:
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}
{{- end }}
{{- else if eq .Values.service.type "NodePort" }}
Access (NodePort):
export NODE_PORT=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} -o jsonpath="{.spec.ports[0].nodePort}")
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo "http://$NODE_IP:$NODE_PORT"
{{- else if eq .Values.service.type "LoadBalancer" }}
Access (LoadBalancer — IP may take a few minutes):
export LB_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} --template '{{ "{{" }}range (index .status.loadBalancer.ingress 0){{ "}}" }}{{ "{{" }}.{{ "}}" }}{{ "{{" }}end{{ "}}" }}')
echo "http://$LB_IP:{{ .Values.service.port }}"
{{- else }}
Access (port-forward):
kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "openconcho.fullname" . }} 8080:{{ .Values.service.port }}
Then open http://localhost:8080
{{- end }}
Run Helm tests to verify the deployment:
helm test {{ .Release.Name }}
{{- if and .Values.networkPolicy.enabled .Values.ingress.enabled }}
WARNING: NetworkPolicy + Ingress are both enabled.
The default NetworkPolicy allows port {{ .Values.service.containerPort }} only from pods within
namespace {{ .Release.Namespace }}. Ingress controllers typically run in a separate
namespace (ingress-nginx, kube-system, etc.) and will be blocked.
To allow ingress-controller traffic, add a namespaceSelector rule:
kubectl edit networkpolicy --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }}
# Under spec.ingress[0].from, add:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: <ingress-controller-namespace>
{{- end }}

View File

@@ -0,0 +1,46 @@
{{- define "openconcho.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "openconcho.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 }}
{{- define "openconcho.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "openconcho.labels" -}}
helm.sh/chart: {{ include "openconcho.chart" . }}
{{ include "openconcho.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "openconcho.selectorLabels" -}}
app.kubernetes.io/name: {{ include "openconcho.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "openconcho.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "openconcho.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{- define "openconcho.imageTag" -}}
{{- .Values.image.tag | default .Chart.AppVersion }}
{{- end }}

View File

@@ -0,0 +1,84 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "openconcho.fullname" . }}
labels:
{{- include "openconcho.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "openconcho.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "openconcho.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "openconcho.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ include "openconcho.imageTag" . }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.containerPort }}
protocol: TCP
env:
- name: OPENCONCHO_DEFAULT_HONCHO_URL
value: {{ .Values.honcho.defaultUrl | quote }}
- name: OPENCONCHO_UPSTREAM_ALLOWLIST
value: {{ .Values.honcho.upstreamAllowlist | quote }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if .Values.tmpfsMounts }}
volumeMounts:
{{- range .Values.tmpfsMounts }}
- name: {{ .mountPath | trimPrefix "/" | replace "/" "-" | trunc 63 | trimSuffix "-" }}
mountPath: {{ .mountPath }}
{{- end }}
{{- end }}
{{- if .Values.tmpfsMounts }}
volumes:
{{- range .Values.tmpfsMounts }}
- name: {{ .mountPath | trimPrefix "/" | replace "/" "-" | trunc 63 | trimSuffix "-" }}
emptyDir:
medium: Memory
{{- end }}
{{- 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 }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,24 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "openconcho.fullname" . }}
labels:
{{- include "openconcho.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "openconcho.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,39 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "openconcho.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "openconcho.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- toYaml .Values.ingress.tls | nindent 4 }}
{{- end }}
{{- if .Values.ingress.hosts }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,20 @@
{{- if .Values.networkPolicy.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "openconcho.fullname" . }}
labels:
{{- include "openconcho.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "openconcho.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
ingress:
- ports:
- port: {{ .Values.service.containerPort }}
protocol: TCP
from:
- podSelector: {}
{{- end }}

View File

@@ -0,0 +1,17 @@
{{- if .Values.podDisruptionBudget.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "openconcho.fullname" . }}
labels:
{{- include "openconcho.labels" . | nindent 4 }}
spec:
{{- if not (kindIs "invalid" .Values.podDisruptionBudget.maxUnavailable) }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
{{- else }}
minAvailable: {{ .Values.podDisruptionBudget.minAvailable | default 1 }}
{{- end }}
selector:
matchLabels:
{{- include "openconcho.selectorLabels" . | nindent 6 }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "openconcho.fullname" . }}
labels:
{{- include "openconcho.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "openconcho.selectorLabels" . | nindent 4 }}

View File

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

View File

@@ -0,0 +1,25 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "openconcho.fullname" . }}-test-healthz"
labels:
{{- include "openconcho.labels" . | nindent 4 }}
annotations:
helm.sh/hook: test
helm.sh/hook-delete-policy: before-hook-creation
spec:
restartPolicy: Never
activeDeadlineSeconds: 60
containers:
- name: healthz
image: busybox:1.36
command:
- sh
- -c
- |
RESPONSE=$(wget -T 10 -qO- http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/healthz)
if [ "$RESPONSE" != "ok" ]; then
echo "FAIL: expected 'ok', got '$RESPONSE'"
exit 1
fi
echo "PASS: /healthz returned 'ok'"

View File

@@ -0,0 +1,24 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "openconcho.fullname" . }}-test-spa-root"
labels:
{{- include "openconcho.labels" . | nindent 4 }}
annotations:
helm.sh/hook: test
helm.sh/hook-delete-policy: before-hook-creation
spec:
restartPolicy: Never
activeDeadlineSeconds: 60
containers:
- name: spa-root
image: busybox:1.36
command:
- sh
- -c
- |
if ! wget -T 10 -q --spider http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/; then
echo "FAIL: GET / did not return HTTP 200"
exit 1
fi
echo "PASS: / returned HTTP 200"

View File

@@ -0,0 +1,67 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1
},
"image": {
"type": "object",
"required": ["repository", "pullPolicy"],
"properties": {
"repository": { "type": "string", "minLength": 1 },
"tag": { "type": "string" },
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
}
},
"service": {
"type": "object",
"required": ["type", "port", "containerPort"],
"properties": {
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer"]
},
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
"containerPort": { "type": "integer", "minimum": 1, "maximum": 65535 }
}
},
"honcho": {
"type": "object",
"properties": {
"defaultUrl": { "type": "string" },
"upstreamAllowlist": { "type": "string" }
}
},
"autoscaling": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"minReplicas": { "type": "integer", "minimum": 1 },
"maxReplicas": { "type": "integer", "minimum": 1 },
"targetCPUUtilizationPercentage": {
"type": "integer",
"minimum": 1,
"maximum": 100
}
}
},
"podDisruptionBudget": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"minAvailable": { "type": "integer", "minimum": 0 }
}
},
"networkPolicy": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" }
}
}
}
}

View File

@@ -0,0 +1,178 @@
# Number of pod replicas. Increase for high availability or use autoscaling instead.
replicaCount: 1
image:
# Container image repository. Override to use a custom registry or fork.
repository: ghcr.io/offendingcommit/openconcho-web
# Image tag. Defaults to the chart appVersion when left empty.
tag: ""
pullPolicy: IfNotPresent
# Secrets for pulling images from private registries.
# Example: [{ name: my-registry-secret }]
imagePullSecrets: []
# Override the name portion used in resource names and labels.
nameOverride: ""
# Override the full resource name (normally release-name + chart-name).
fullnameOverride: ""
serviceAccount:
# Create a dedicated ServiceAccount for the pod.
create: true
# Disable automatic ServiceAccount token mounting — the app never calls the Kubernetes API.
automount: false
# Annotations to add to the ServiceAccount (e.g. for IRSA, Workload Identity, Vault).
annotations: {}
# Use a pre-existing ServiceAccount instead of creating one. Ignored when create is true.
name: ""
# Annotations applied to every pod (not the Deployment). Useful for Prometheus scraping,
# Vault agent injection, Datadog unified service tagging, etc.
podAnnotations: {}
# Extra labels applied to every pod.
podLabels: {}
# Pod-level security context shared by all containers.
# UID/GID 101 matches the nginx-unprivileged base image — do not change without rebuilding.
podSecurityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
fsGroup: 101
seccompProfile:
type: RuntimeDefault
# Container-level security context.
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
# Directories mounted as ephemeral tmpfs (in-memory) to satisfy nginx's write requirements
# when the root filesystem is read-only. Add entries for any additional writable paths.
tmpfsMounts:
- mountPath: /var/cache/nginx
- mountPath: /var/run
- mountPath: /tmp
service:
# Kubernetes Service type. Options: ClusterIP | NodePort | LoadBalancer
type: ClusterIP
# Port exposed by the Service (what the Ingress or other pods target).
port: 80
# Port the container actually listens on (nginx-unprivileged default).
containerPort: 8080
ingress:
enabled: false
# IngressClass name. Leave empty to accept the cluster default.
# Examples: nginx | traefik | alb | kong
className: ""
# Annotations forwarded verbatim to the Ingress resource.
# Example (cert-manager + nginx-ingress):
# kubernetes.io/ingress.class: nginx
# cert-manager.io/cluster-issuer: letsencrypt-prod
annotations: {}
hosts:
- host: openconcho.example.com
paths:
- path: /
pathType: Prefix
# TLS configuration. Provide a Secret containing the certificate.
# Example:
# - secretName: openconcho-tls
# hosts:
# - openconcho.example.com
tls: []
honcho:
# Default Honcho instance URL pre-populated in the UI on first load.
# Users can change or add instances at runtime; this only seeds the initial value.
# Example: https://honcho.example.com
defaultUrl: ""
# Optional SSRF guard: comma-separated host globs the nginx proxy is allowed to forward to.
# Leave empty to allow any upstream. Applies only when the proxy is publicly reachable.
# Example: honcho.example.com,*.internal.example.com
upstreamAllowlist: ""
# CPU and memory requests / limits for the web container.
# The SPA is static HTML/JS, so memory is the primary concern and CPU is negligible at rest.
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 128Mi
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 5
# Scale up when average CPU utilization across pods exceeds this percentage.
targetCPUUtilizationPercentage: 80
podDisruptionBudget:
enabled: false
# Minimum number of pods that must remain available during voluntary disruptions
# (node drains, rolling upgrades). Set maxUnavailable instead to flip the direction.
minAvailable: 1
# Uncomment to use maxUnavailable instead — cannot set both simultaneously.
# maxUnavailable: 1
# Restrict pod-to-pod traffic at the network layer. When enabled, only pods in the
# same namespace may reach the web container.
# WARNING: if ingress is also enabled, you must add a policy that allows traffic from
# the ingress-controller namespace — run `helm status <release>` to see the reminder.
networkPolicy:
enabled: false
# Spread pods across failure domains (availability zones, nodes, etc.) to reduce
# the blast radius of a single-node or single-zone failure.
# Example (zone spread):
# - maxSkew: 1
# topologyKey: topology.kubernetes.io/zone
# whenUnsatisfiable: DoNotSchedule
# labelSelector:
# matchLabels:
# app.kubernetes.io/name: openconcho
topologySpreadConstraints: []
# Constrain pods to nodes whose labels match these key/value pairs.
# Example: kubernetes.io/arch: amd64
nodeSelector: {}
# Allow pods to be scheduled on tainted nodes.
# Example:
# - key: dedicated
# operator: Equal
# value: web
# effect: NoSchedule
tolerations: []
# Advanced pod affinity / anti-affinity rules.
# Example (soft anti-affinity — prefer spreading across different nodes):
# podAntiAffinity:
# preferredDuringSchedulingIgnoredDuringExecution:
# - weight: 100
# podAffinityTerm:
# labelSelector:
# matchLabels:
# app.kubernetes.io/name: openconcho
# topologyKey: kubernetes.io/hostname
affinity: {}