From 94c7fb2d97b218226f8c1ffa5c0979771e9a3e7a Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 13 Jul 2026 16:42:59 +0100 Subject: [PATCH 1/7] fix(hosting): deploy ClickHouse from the official image instead of Bitnami The Bitnami free image catalog is EOL and its frozen bitnamilegacy archive tops out at ClickHouse 25.7.5, below the 25.8 floor the platform requires since v4.5.0. The Docker Compose stack and the Helm chart now run the official clickhouse/clickhouse-server image at 26.2, the same version the platform is developed and tested against. The Helm chart deploys ClickHouse with a chart-owned StatefulSet instead of the Bitnami subchart. Existing deployments keep their data with no manual steps: a config override keeps the on-disk layout compatible with volumes created by the Bitnami-based setup, Compose reuses the same named volume, and the Helm chart automatically adopts the data PVC left behind by the old subchart. --- docs/self-hosting/docker.mdx | 2 + docs/self-hosting/kubernetes.mdx | 14 +- hosting/docker/.env.example | 2 +- hosting/docker/clickhouse/data-paths.xml | 17 ++ hosting/docker/webapp/docker-compose.yml | 18 +- hosting/k8s/helm/Chart.lock | 7 +- hosting/k8s/helm/Chart.yaml | 4 - hosting/k8s/helm/templates/_helpers.tpl | 6 +- hosting/k8s/helm/templates/clickhouse.yaml | 205 ++++++++++++++++++ .../helm/templates/tests/test-clickhouse.yaml | 2 +- .../k8s/helm/values-production-example.yaml | 4 +- hosting/k8s/helm/values.yaml | 68 ++++-- 12 files changed, 313 insertions(+), 36 deletions(-) create mode 100644 hosting/docker/clickhouse/data-paths.xml create mode 100644 hosting/k8s/helm/templates/clickhouse.yaml diff --git a/docs/self-hosting/docker.mdx b/docs/self-hosting/docker.mdx index 6c8de292f17..f409b6ec414 100644 --- a/docs/self-hosting/docker.mdx +++ b/docs/self-hosting/docker.mdx @@ -362,6 +362,8 @@ TRIGGER_IMAGE_TAG=v4.5.0 We patch the latest released version line only, so keep an eye on new releases to receive security fixes. See [Security & vulnerability reporting](/self-hosting/security). +You can also lock the versions of the bundled services, for example with `CLICKHOUSE_IMAGE_TAG`. If you do, or if you bring your own ClickHouse via `CLICKHOUSE_URL`, note that Trigger.dev requires ClickHouse 25.8 or newer. + Trigger.dev 4.5.0 is the last version we officially support for running v3 (SDK v3) tasks. If you still have v3 tasks, pin `TRIGGER_IMAGE_TAG` to exactly `v4.5.0` or [migrate to diff --git a/docs/self-hosting/kubernetes.mdx b/docs/self-hosting/kubernetes.mdx index 33d06f3086c..cf605d1d06e 100644 --- a/docs/self-hosting/kubernetes.mdx +++ b/docs/self-hosting/kubernetes.mdx @@ -279,6 +279,16 @@ redis: #### ClickHouse +Trigger.dev requires ClickHouse 25.8 or newer. + + + When upgrading from a chart version that bundled ClickHouse via the Bitnami subchart, the chart + automatically adopts the existing data volume, so no manual migration is needed. If you render + manifests without cluster access (for example with GitOps tools that use `helm template`), set + `clickhouse.persistence.existingClaim` to the old PVC name + (`data--clickhouse-shard0-0`) to keep your data. + + **Direct configuration:** ```yaml @@ -286,7 +296,7 @@ clickhouse: deploy: false external: host: "my-clickhouse.example.com" - port: 8123 + httpPort: 8123 username: "my-username" password: "my-password" ``` @@ -298,7 +308,7 @@ clickhouse: deploy: false external: host: "my-clickhouse.example.com" - port: 8123 + httpPort: 8123 username: "my-username" existingSecret: "clickhouse-credentials" # existingSecretKey: "clickhouse-password" # default (optional) diff --git a/hosting/docker/.env.example b/hosting/docker/.env.example index 4c7cf11bc70..b1a797f137d 100644 --- a/hosting/docker/.env.example +++ b/hosting/docker/.env.example @@ -136,7 +136,7 @@ OBJECT_STORE_SECRET_ACCESS_KEY= # POSTGRES_IMAGE_TAG=14 # REDIS_IMAGE_TAG=7 # ELECTRIC_IMAGE_TAG=1.0.13 -# CLICKHOUSE_IMAGE_TAG=latest +# CLICKHOUSE_IMAGE_TAG=26.2 # REGISTRY_IMAGE_TAG=2 # MINIO_IMAGE_TAG=latest # DOCKER_PROXY_IMAGE_TAG=latest diff --git a/hosting/docker/clickhouse/data-paths.xml b/hosting/docker/clickhouse/data-paths.xml new file mode 100644 index 00000000000..bfa9c5ce22a --- /dev/null +++ b/hosting/docker/clickhouse/data-paths.xml @@ -0,0 +1,17 @@ + + + /var/lib/clickhouse/data/ + /var/lib/clickhouse/tmp/ + /var/lib/clickhouse/data/user_files/ + /var/lib/clickhouse/data/format_schemas/ + + + /var/lib/clickhouse/data/access/ + + + diff --git a/hosting/docker/webapp/docker-compose.yml b/hosting/docker/webapp/docker-compose.yml index 8d133cbeea5..1ecdd01dc49 100644 --- a/hosting/docker/webapp/docker-compose.yml +++ b/hosting/docker/webapp/docker-compose.yml @@ -157,18 +157,26 @@ services: start_period: 10s clickhouse: - image: bitnamilegacy/clickhouse:${CLICKHOUSE_IMAGE_TAG:-latest} + image: clickhouse/clickhouse-server:${CLICKHOUSE_IMAGE_TAG:-26.2} restart: ${RESTART_POLICY:-unless-stopped} logging: *logging-config ports: - ${CLICKHOUSE_PUBLISH_IP:-127.0.0.1}:9123:8123 - ${CLICKHOUSE_PUBLISH_IP:-127.0.0.1}:9090:9000 + ulimits: + nofile: + soft: 262144 + hard: 262144 environment: - CLICKHOUSE_ADMIN_USER: ${CLICKHOUSE_USER:-default} - CLICKHOUSE_ADMIN_PASSWORD: ${CLICKHOUSE_PASSWORD:?Set CLICKHOUSE_PASSWORD in .env - run ./generate-secrets.sh} + CLICKHOUSE_USER: ${CLICKHOUSE_USER:-default} + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:?Set CLICKHOUSE_PASSWORD in .env - run ./generate-secrets.sh} + CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1 volumes: - - clickhouse:/bitnami/clickhouse - - ../clickhouse/override.xml:/bitnami/clickhouse/etc/config.d/override.xml:ro + # The same volume works across upgrades from the previous Bitnami-based + # setup: data-paths.xml keeps the on-disk layout compatible. + - clickhouse:/var/lib/clickhouse + - ../clickhouse/data-paths.xml:/etc/clickhouse-server/config.d/data-paths.xml:ro + - ../clickhouse/override.xml:/etc/clickhouse-server/config.d/override.xml:ro networks: - webapp healthcheck: diff --git a/hosting/k8s/helm/Chart.lock b/hosting/k8s/helm/Chart.lock index 5a7882cfa0e..338d0db4805 100644 --- a/hosting/k8s/helm/Chart.lock +++ b/hosting/k8s/helm/Chart.lock @@ -5,11 +5,8 @@ dependencies: - name: redis repository: oci://registry-1.docker.io/bitnamicharts version: 21.2.6 -- name: clickhouse - repository: oci://registry-1.docker.io/bitnamicharts - version: 9.4.4 - name: minio repository: oci://registry-1.docker.io/bitnamicharts version: 17.0.9 -digest: sha256:e1b572ab8eca0cc376311398c27b1734d8a598095fccc81dd9c32b2c8b9c1149 -generated: "2026-05-05T10:31:58.493590751+01:00" +digest: sha256:a735954c8b78fcf5b30689bdcdfed66b9be57135368ea696aff2b52ecd731474 +generated: "2026-07-13T16:36:15.800113+01:00" diff --git a/hosting/k8s/helm/Chart.yaml b/hosting/k8s/helm/Chart.yaml index e40b51845c3..61eed76c223 100644 --- a/hosting/k8s/helm/Chart.yaml +++ b/hosting/k8s/helm/Chart.yaml @@ -26,10 +26,6 @@ dependencies: version: "21.2.6" repository: "oci://registry-1.docker.io/bitnamicharts" condition: redis.deploy - - name: clickhouse - version: "9.4.4" - repository: "oci://registry-1.docker.io/bitnamicharts" - condition: clickhouse.deploy - name: minio version: "17.0.9" repository: "oci://registry-1.docker.io/bitnamicharts" diff --git a/hosting/k8s/helm/templates/_helpers.tpl b/hosting/k8s/helm/templates/_helpers.tpl index 0ecc17b9893..c1a7c5a3261 100644 --- a/hosting/k8s/helm/templates/_helpers.tpl +++ b/hosting/k8s/helm/templates/_helpers.tpl @@ -415,7 +415,7 @@ ClickHouse hostname {{- if .Values.clickhouse.host }} {{- .Values.clickhouse.host }} {{- else if .Values.clickhouse.deploy }} -{{- printf "%s-clickhouse" .Release.Name }} +{{- printf "%s-clickhouse" (include "trigger-v4.fullname" .) }} {{- end }} {{- end }} @@ -439,7 +439,7 @@ hex-encoded password or percent-encode before storing in the Secret. {{- if .Values.clickhouse.deploy -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.secure -}} {{- $secure := ternary "true" "false" .Values.clickhouse.secure -}} -{{ $protocol }}://{{ .Values.clickhouse.auth.username }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:8123?secure={{ $secure }} +{{ $protocol }}://{{ .Values.clickhouse.auth.username }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }}?secure={{ $secure }} {{- else if .Values.clickhouse.external.host -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.external.secure -}} {{- $secure := ternary "true" "false" .Values.clickhouse.external.secure -}} @@ -460,7 +460,7 @@ applies to the replication URL. {{- define "trigger-v4.clickhouse.replication.url" -}} {{- if .Values.clickhouse.deploy -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.secure -}} -{{ $protocol }}://{{ .Values.clickhouse.auth.username }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:8123 +{{ $protocol }}://{{ .Values.clickhouse.auth.username }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }} {{- else if .Values.clickhouse.external.host -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.external.secure -}} {{- if .Values.clickhouse.external.existingSecret -}} diff --git a/hosting/k8s/helm/templates/clickhouse.yaml b/hosting/k8s/helm/templates/clickhouse.yaml new file mode 100644 index 00000000000..006b438e0b2 --- /dev/null +++ b/hosting/k8s/helm/templates/clickhouse.yaml @@ -0,0 +1,205 @@ +{{- if .Values.clickhouse.deploy }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "trigger-v4.fullname" . }}-clickhouse-config + labels: + {{- $component := "clickhouse" }} + {{- include "trigger-v4.componentLabels" (dict "Chart" .Chart "Release" .Release "Values" .Values "component" $component) | nindent 4 }} +data: + {{- if not (hasKey .Values.clickhouse.configdFiles "data-paths.xml") }} + {{- /* Keeps the on-disk layout compatible with data volumes created by the + Bitnami subchart this chart used previously, which stored everything + under a data/ subdirectory of the volume. Fresh installs get the same + layout. tmp lives outside data/ because old volumes contain a + dangling tmp symlink there. */}} + data-paths.xml: | + + /var/lib/clickhouse/data/ + /var/lib/clickhouse/tmp/ + /var/lib/clickhouse/data/user_files/ + /var/lib/clickhouse/data/format_schemas/ + + + /var/lib/clickhouse/data/access/ + + + + {{- end }} + {{- range $filename, $content := .Values.clickhouse.configdFiles }} + {{ $filename }}: | + {{- $content | nindent 4 }} + {{- end }} +--- +{{- /* Reuse an existing data PVC instead of creating one via + volumeClaimTemplates. Set explicitly through persistence.existingClaim, + or detected automatically: upgrades from chart versions that bundled + the Bitnami subchart leave their PVC behind under the old name, and + adopting it preserves all ClickHouse data with no manual migration. + (lookup returns nothing during template/dry-run rendering; set + persistence.existingClaim explicitly when pre-rendering manifests, + e.g. with GitOps tools.) */}} +{{- $existingClaim := .Values.clickhouse.persistence.existingClaim }} +{{- if and (not $existingClaim) .Values.clickhouse.persistence.enabled }} +{{- $legacyName := printf "data-%s-clickhouse-shard0-0" .Release.Name }} +{{- if lookup "v1" "PersistentVolumeClaim" .Release.Namespace $legacyName }} +{{- $existingClaim = $legacyName }} +{{- end }} +{{- end }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "trigger-v4.fullname" . }}-clickhouse + labels: + {{- $component := "clickhouse" }} + {{- include "trigger-v4.componentLabels" (dict "Chart" .Chart "Release" .Release "Values" .Values "component" $component) | nindent 4 }} +spec: + replicas: 1 + serviceName: {{ include "trigger-v4.fullname" . }}-clickhouse + selector: + matchLabels: + {{- include "trigger-v4.componentSelectorLabels" (dict "Chart" .Chart "Release" .Release "Values" .Values "component" $component) | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ .Values.clickhouse.configdFiles | toYaml | sha256sum }} + {{- with .Values.clickhouse.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "trigger-v4.componentSelectorLabels" (dict "Chart" .Chart "Release" .Release "Values" .Values "component" $component) | nindent 8 }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.clickhouse.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: clickhouse + {{- with .Values.clickhouse.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.global.imageRegistry | default .Values.clickhouse.image.registry }}/{{ .Values.clickhouse.image.repository }}:{{ .Values.clickhouse.image.tag }}{{ with .Values.clickhouse.image.digest }}@{{ . }}{{ end }}" + imagePullPolicy: {{ .Values.clickhouse.image.pullPolicy }} + env: + - name: CLICKHOUSE_USER + value: {{ .Values.clickhouse.auth.username | quote }} + {{- /* Same chart-managed datastore secret the webapp reads for its + connection URL, so the server credential and the app's URL + always match. */}} + - name: CLICKHOUSE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.clickhouse.auth.existingSecret | default (include "trigger-v4.datastore.secretName" .) }} + key: {{ .Values.clickhouse.auth.existingSecretKey | default "clickhouse-admin-password" }} + - name: CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT + value: "1" + ports: + - name: http + containerPort: 8123 + protocol: TCP + - name: native + containerPort: 9000 + protocol: TCP + {{- if .Values.clickhouse.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: {{ .Values.clickhouse.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.clickhouse.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.clickhouse.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.clickhouse.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.clickhouse.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.clickhouse.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: {{ .Values.clickhouse.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.clickhouse.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.clickhouse.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.clickhouse.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.clickhouse.readinessProbe.successThreshold }} + {{- end }} + resources: + {{- toYaml .Values.clickhouse.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /var/lib/clickhouse + - name: logs + mountPath: /var/log/clickhouse-server + {{- /* Mount each override file individually: shadowing the whole + config.d directory would remove the image's built-in + docker_related_config.xml, which makes the server listen on + 0.0.0.0 instead of localhost only. */}} + {{- if not (hasKey .Values.clickhouse.configdFiles "data-paths.xml") }} + - name: config + mountPath: /etc/clickhouse-server/config.d/data-paths.xml + subPath: data-paths.xml + {{- end }} + {{- range $filename, $_ := .Values.clickhouse.configdFiles }} + - name: config + mountPath: /etc/clickhouse-server/config.d/{{ $filename }} + subPath: {{ $filename }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ include "trigger-v4.fullname" . }}-clickhouse-config + - name: logs + emptyDir: {} + {{- if not .Values.clickhouse.persistence.enabled }} + - name: data + emptyDir: {} + {{- else if $existingClaim }} + - name: data + persistentVolumeClaim: + claimName: {{ $existingClaim }} + {{- end }} + {{- if and .Values.clickhouse.persistence.enabled (not $existingClaim) }} + volumeClaimTemplates: + - metadata: + name: data + {{- if .Values.clickhouse.persistence.retain }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + spec: + accessModes: + - {{ .Values.clickhouse.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.clickhouse.persistence.size }} + {{- $storageClass := .Values.clickhouse.persistence.storageClass | default .Values.global.storageClass }} + {{- if $storageClass }} + storageClassName: {{ $storageClass }} + {{- end }} + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "trigger-v4.fullname" . }}-clickhouse + labels: + {{- $component := "clickhouse" }} + {{- include "trigger-v4.componentLabels" (dict "Chart" .Chart "Release" .Release "Values" .Values "component" $component) | nindent 4 }} +spec: + type: {{ .Values.clickhouse.service.type }} + ports: + - port: {{ .Values.clickhouse.service.ports.http }} + targetPort: http + protocol: TCP + name: http + - port: {{ .Values.clickhouse.service.ports.native }} + targetPort: native + protocol: TCP + name: native + selector: + {{- include "trigger-v4.componentSelectorLabels" (dict "Chart" .Chart "Release" .Release "Values" .Values "component" $component) | nindent 4 }} +{{- end }} diff --git a/hosting/k8s/helm/templates/tests/test-clickhouse.yaml b/hosting/k8s/helm/templates/tests/test-clickhouse.yaml index 9bde62c2ada..3e420d05c1b 100644 --- a/hosting/k8s/helm/templates/tests/test-clickhouse.yaml +++ b/hosting/k8s/helm/templates/tests/test-clickhouse.yaml @@ -16,6 +16,6 @@ spec: args: - | echo "Testing ClickHouse HTTP interface..." - curl -f --user "{{ .Values.clickhouse.auth.adminUser }}:{{ .Values.clickhouse.auth.adminPassword }}" "http://{{ include "trigger-v4.fullname" . }}-clickhouse:{{ .Values.clickhouse.service.ports.http }}/ping" + curl -f --user "{{ .Values.clickhouse.auth.username }}:{{ .Values.clickhouse.auth.password }}" "http://{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }}/ping" echo "ClickHouse test completed successfully" {{- end }} \ No newline at end of file diff --git a/hosting/k8s/helm/values-production-example.yaml b/hosting/k8s/helm/values-production-example.yaml index ee99de58a9b..3f99547fcf0 100644 --- a/hosting/k8s/helm/values-production-example.yaml +++ b/hosting/k8s/helm/values-production-example.yaml @@ -77,12 +77,12 @@ redis: memory: 512Mi # Production ClickHouse +# The bundled ClickHouse serves plain HTTP inside the cluster. For TLS, +# use an external ClickHouse (deploy: false) with secure: true (see below). clickhouse: auth: # Required — no built-in default. The webapp connection string uses clickhouse.auth.password. password: "your-strong-clickhouse-password" - # Set to true to enable TLS/secure connections in production - secure: true persistence: enabled: true size: 100Gi diff --git a/hosting/k8s/helm/values.yaml b/hosting/k8s/helm/values.yaml index e76a921279c..39240f0192a 100644 --- a/hosting/k8s/helm/values.yaml +++ b/hosting/k8s/helm/values.yaml @@ -628,19 +628,24 @@ s2: existingSecretAccessTokenKey: "access-token" # ClickHouse configuration -# Subchart: https://github.com/bitnami/charts/tree/main/bitnami/clickhouse +# Deploys a single-node ClickHouse using the official image: +# https://hub.docker.com/r/clickhouse/clickhouse-server +# For clustered/replicated setups, use an external ClickHouse (deploy: false). clickhouse: deploy: true image: - # Use bitnami legacy repo - repository: bitnamilegacy/clickhouse - # image: docker.io/bitnamilegacy/clickhouse:25.7.5-debian-12-r0 + registry: docker.io + repository: clickhouse/clickhouse-server + # Trigger.dev requires ClickHouse >= 25.8 + tag: "26.2" + # Pinning by digest is strongly recommended for reproducible deployments + digest: "" + pullPolicy: IfNotPresent # TLS/Secure connection configuration secure: false # Set to true to use HTTPS and secure connections - # Bitnami ClickHouse chart configuration (when deploy: true) auth: username: "default" password: "" # Leave empty to auto-generate into the datastore secret, or set to pin. @@ -648,24 +653,61 @@ clickhouse: existingSecret: "trigger-datastore" existingSecretKey: "clickhouse-admin-password" - # Single-node configuration (disable clustering for dev/test) - keeper: - enabled: false + podAnnotations: {} - shards: 1 - replicaCount: 1 + # The official image runs ClickHouse as uid 101. fsGroup makes the persistent + # volume writable by that user without running the container as root, and + # OnRootMismatch relabels volumes carried over from older chart versions + # (different uid) on first mount without rechecking every file on later mounts. + podSecurityContext: + fsGroup: 101 + fsGroupChangePolicy: OnRootMismatch + securityContext: + runAsNonRoot: true + runAsUser: 101 + runAsGroup: 101 + + service: + type: ClusterIP + ports: + http: 8123 + native: 9000 persistence: enabled: true size: 10Gi + accessMode: ReadWriteOnce + storageClass: "" + retain: false + # Name of an existing PVC to use for ClickHouse data instead of creating + # one. Normally left empty: upgrades from chart versions that bundled the + # Bitnami ClickHouse subchart adopt the old data PVC automatically. Set + # this explicitly when rendering manifests without cluster access (e.g. + # GitOps tools that use `helm template`), where auto-detection can't run: + # the old PVC is named data--clickhouse-shard0-0. + existingClaim: "" ## ClickHouse resource requests and limits ## ref: http://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ - ## @param resourcesPreset Set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). This is ignored if resources is set (resources is recommended for production). - ## More information: https://github.com/bitnami/charts/blob/main/bitnami/common/templates/_resources.tpl#L15 - resourcesPreset: "xlarge" + ## ClickHouse can be very resource intensive. Setting explicit limits and + ## requests is strongly recommended for production (see values-production-example.yaml). resources: {} + livenessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + successThreshold: 1 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + successThreshold: 1 + # External ClickHouse connection (when deploy: false) external: host: "" From bfe6e48b94da6ac72a8646d30cb28eb5a7d077c7 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 14 Jul 2026 11:05:44 +0100 Subject: [PATCH 2/7] fix(hosting): store the ClickHouse password in a Secret and URL-encode inline credentials The chart-deployed ClickHouse now reads CLICKHOUSE_PASSWORD from a chart-owned Secret instead of a plaintext env value in the pod spec, and the CLICKHOUSE_URL helpers percent-encode inline usernames and passwords so special characters no longer produce an unparseable URL. --- hosting/k8s/helm/templates/_helpers.tpl | 28 +++++++++++++--------- hosting/k8s/helm/templates/clickhouse.yaml | 1 + 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/hosting/k8s/helm/templates/_helpers.tpl b/hosting/k8s/helm/templates/_helpers.tpl index c1a7c5a3261..efeec79ceb8 100644 --- a/hosting/k8s/helm/templates/_helpers.tpl +++ b/hosting/k8s/helm/templates/_helpers.tpl @@ -422,9 +422,9 @@ ClickHouse hostname {{/* ClickHouse URL for application (with secure parameter) -Note on the external+existingSecret branch: the password is expanded via -Kubernetes' `$(VAR)` syntax, not shell `${VAR}`. Kubelet substitutes -`$(CLICKHOUSE_PASSWORD)` at container-creation time from the +Note on the deploy and external+existingSecret branches: the password is +expanded via Kubernetes' `$(VAR)` syntax, not shell `${VAR}`. Kubelet +substitutes `$(CLICKHOUSE_PASSWORD)` at container-creation time from the CLICKHOUSE_PASSWORD env var declared just before CLICKHOUSE_URL in webapp.yaml. Shell-style `${...}` does not work here because `docker/scripts/entrypoint.sh` assigns CLICKHOUSE_URL to GOOSE_DBSTRING @@ -432,21 +432,27 @@ with a single-pass expansion (`export GOOSE_DBSTRING="$CLICKHOUSE_URL"`), so any inner `${...}` reaches goose verbatim and fails URL parsing. CLICKHOUSE_PASSWORD must contain only URL-userinfo-safe characters — the -value is substituted verbatim, so `@ : / ? # [ ] %` break the URL. Use a -hex-encoded password or percent-encode before storing in the Secret. +value is substituted verbatim, so `@ : / ? # [ ] %` break the URL. The +chart-generated datastore password is hex, which is safe; a pinned +auth.password or external Secret value must be URL-safe too. + +Inline credentials (usernames and the external plain password) are +percent-encoded via urlquery, so special characters are safe there — +except spaces, which urlquery encodes as `+` and userinfo decoding keeps +literal. */}} {{- define "trigger-v4.clickhouse.url" -}} {{- if .Values.clickhouse.deploy -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.secure -}} {{- $secure := ternary "true" "false" .Values.clickhouse.secure -}} -{{ $protocol }}://{{ .Values.clickhouse.auth.username }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }}?secure={{ $secure }} +{{ $protocol }}://{{ .Values.clickhouse.auth.username | urlquery }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }}?secure={{ $secure }} {{- else if .Values.clickhouse.external.host -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.external.secure -}} {{- $secure := ternary "true" "false" .Values.clickhouse.external.secure -}} {{- if .Values.clickhouse.external.existingSecret -}} -{{ $protocol }}://{{ .Values.clickhouse.external.username }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }} +{{ $protocol }}://{{ .Values.clickhouse.external.username | urlquery }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }} {{- else -}} -{{ $protocol }}://{{ .Values.clickhouse.external.username }}:{{ .Values.clickhouse.external.password }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }} +{{ $protocol }}://{{ .Values.clickhouse.external.username | urlquery }}:{{ .Values.clickhouse.external.password | urlquery }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }} {{- end -}} {{- end -}} {{- end }} @@ -460,13 +466,13 @@ applies to the replication URL. {{- define "trigger-v4.clickhouse.replication.url" -}} {{- if .Values.clickhouse.deploy -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.secure -}} -{{ $protocol }}://{{ .Values.clickhouse.auth.username }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }} +{{ $protocol }}://{{ .Values.clickhouse.auth.username | urlquery }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }} {{- else if .Values.clickhouse.external.host -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.external.secure -}} {{- if .Values.clickhouse.external.existingSecret -}} -{{ $protocol }}://{{ .Values.clickhouse.external.username }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }} +{{ $protocol }}://{{ .Values.clickhouse.external.username | urlquery }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }} {{- else -}} -{{ $protocol }}://{{ .Values.clickhouse.external.username }}:{{ .Values.clickhouse.external.password }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }} +{{ $protocol }}://{{ .Values.clickhouse.external.username | urlquery }}:{{ .Values.clickhouse.external.password | urlquery }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }} {{- end -}} {{- end -}} {{- end }} diff --git a/hosting/k8s/helm/templates/clickhouse.yaml b/hosting/k8s/helm/templates/clickhouse.yaml index 006b438e0b2..70e9a793ae7 100644 --- a/hosting/k8s/helm/templates/clickhouse.yaml +++ b/hosting/k8s/helm/templates/clickhouse.yaml @@ -63,6 +63,7 @@ spec: metadata: annotations: checksum/config: {{ .Values.clickhouse.configdFiles | toYaml | sha256sum }} + checksum/secret: {{ .Values.clickhouse.auth.password | sha256sum }} {{- with .Values.clickhouse.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} From 3d68ee09090edada143e2168ade66cb6be6e0d4a Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 14 Jul 2026 13:53:15 +0100 Subject: [PATCH 3/7] fix(hosting): percent-encode spaces correctly in ClickHouse URL credentials urlquery encodes spaces as plus signs, which URL userinfo decoding keeps literal. A shared urlencode helper rewrites them to %20 (a real plus already encodes as %2B), so passwords containing spaces now work. --- hosting/k8s/helm/templates/_helpers.tpl | 26 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/hosting/k8s/helm/templates/_helpers.tpl b/hosting/k8s/helm/templates/_helpers.tpl index efeec79ceb8..226c09b020f 100644 --- a/hosting/k8s/helm/templates/_helpers.tpl +++ b/hosting/k8s/helm/templates/_helpers.tpl @@ -408,6 +408,16 @@ http://{{ include "trigger-v4.fullname" . }}-s2:{{ .Values.s2.service.port }}/v1 {{- end -}} {{- end }} +{{/* +Percent-encode a string for the userinfo part of a URL. urlquery encodes +spaces as `+` (query semantics), which userinfo decoding keeps literal; a +real `+` becomes `%2B`, so any `+` left in the output is a space and can be +rewritten to `%20`. +*/}} +{{- define "trigger-v4.urlencode" -}} +{{- . | urlquery | replace "+" "%20" -}} +{{- end }} + {{/* ClickHouse hostname */}} @@ -437,22 +447,20 @@ chart-generated datastore password is hex, which is safe; a pinned auth.password or external Secret value must be URL-safe too. Inline credentials (usernames and the external plain password) are -percent-encoded via urlquery, so special characters are safe there — -except spaces, which urlquery encodes as `+` and userinfo decoding keeps -literal. +percent-encoded, so any special characters are safe there. */}} {{- define "trigger-v4.clickhouse.url" -}} {{- if .Values.clickhouse.deploy -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.secure -}} {{- $secure := ternary "true" "false" .Values.clickhouse.secure -}} -{{ $protocol }}://{{ .Values.clickhouse.auth.username | urlquery }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }}?secure={{ $secure }} +{{ $protocol }}://{{ include "trigger-v4.urlencode" .Values.clickhouse.auth.username }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }}?secure={{ $secure }} {{- else if .Values.clickhouse.external.host -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.external.secure -}} {{- $secure := ternary "true" "false" .Values.clickhouse.external.secure -}} {{- if .Values.clickhouse.external.existingSecret -}} -{{ $protocol }}://{{ .Values.clickhouse.external.username | urlquery }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }} +{{ $protocol }}://{{ include "trigger-v4.urlencode" .Values.clickhouse.external.username }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }} {{- else -}} -{{ $protocol }}://{{ .Values.clickhouse.external.username | urlquery }}:{{ .Values.clickhouse.external.password | urlquery }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }} +{{ $protocol }}://{{ include "trigger-v4.urlencode" .Values.clickhouse.external.username }}:{{ include "trigger-v4.urlencode" .Values.clickhouse.external.password }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }} {{- end -}} {{- end -}} {{- end }} @@ -466,13 +474,13 @@ applies to the replication URL. {{- define "trigger-v4.clickhouse.replication.url" -}} {{- if .Values.clickhouse.deploy -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.secure -}} -{{ $protocol }}://{{ .Values.clickhouse.auth.username | urlquery }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }} +{{ $protocol }}://{{ include "trigger-v4.urlencode" .Values.clickhouse.auth.username }}:$(CLICKHOUSE_PASSWORD)@{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }} {{- else if .Values.clickhouse.external.host -}} {{- $protocol := ternary "https" "http" .Values.clickhouse.external.secure -}} {{- if .Values.clickhouse.external.existingSecret -}} -{{ $protocol }}://{{ .Values.clickhouse.external.username | urlquery }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }} +{{ $protocol }}://{{ include "trigger-v4.urlencode" .Values.clickhouse.external.username }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }} {{- else -}} -{{ $protocol }}://{{ .Values.clickhouse.external.username | urlquery }}:{{ .Values.clickhouse.external.password | urlquery }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }} +{{ $protocol }}://{{ include "trigger-v4.urlencode" .Values.clickhouse.external.username }}:{{ include "trigger-v4.urlencode" .Values.clickhouse.external.password }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }} {{- end -}} {{- end -}} {{- end }} From 228c4a3cd42455304a9267bcf38501c8850f2340 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 14 Jul 2026 16:14:44 +0100 Subject: [PATCH 4/7] fix(hosting): include the default ClickHouse data-paths config in the restart checksum The data-paths.xml default now lives in a shared helper used by both the ConfigMap and the pod checksum annotation, so changing it in a future chart version restarts the pod (subPath mounts do not update in place). --- hosting/k8s/helm/templates/_helpers.tpl | 22 ++++++++++++++++++++++ hosting/k8s/helm/templates/clickhouse.yaml | 19 ++----------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/hosting/k8s/helm/templates/_helpers.tpl b/hosting/k8s/helm/templates/_helpers.tpl index 226c09b020f..ebd3bf7f021 100644 --- a/hosting/k8s/helm/templates/_helpers.tpl +++ b/hosting/k8s/helm/templates/_helpers.tpl @@ -418,6 +418,28 @@ rewritten to `%20`. {{- . | urlquery | replace "+" "%20" -}} {{- end }} +{{/* +ClickHouse data-paths config. Keeps the on-disk layout compatible with data +volumes created by the Bitnami subchart this chart used previously, which +stored everything under a data/ subdirectory of the volume. Fresh installs +get the same layout. tmp lives outside data/ because old volumes contain a +dangling tmp symlink there. Users can override by defining their own +data-paths.xml in clickhouse.configdFiles. +*/}} +{{- define "trigger-v4.clickhouse.dataPathsConfig" -}} + + /var/lib/clickhouse/data/ + /var/lib/clickhouse/tmp/ + /var/lib/clickhouse/data/user_files/ + /var/lib/clickhouse/data/format_schemas/ + + + /var/lib/clickhouse/data/access/ + + + +{{- end }} + {{/* ClickHouse hostname */}} diff --git a/hosting/k8s/helm/templates/clickhouse.yaml b/hosting/k8s/helm/templates/clickhouse.yaml index 70e9a793ae7..8c1de94c443 100644 --- a/hosting/k8s/helm/templates/clickhouse.yaml +++ b/hosting/k8s/helm/templates/clickhouse.yaml @@ -8,23 +8,8 @@ metadata: {{- include "trigger-v4.componentLabels" (dict "Chart" .Chart "Release" .Release "Values" .Values "component" $component) | nindent 4 }} data: {{- if not (hasKey .Values.clickhouse.configdFiles "data-paths.xml") }} - {{- /* Keeps the on-disk layout compatible with data volumes created by the - Bitnami subchart this chart used previously, which stored everything - under a data/ subdirectory of the volume. Fresh installs get the same - layout. tmp lives outside data/ because old volumes contain a - dangling tmp symlink there. */}} data-paths.xml: | - - /var/lib/clickhouse/data/ - /var/lib/clickhouse/tmp/ - /var/lib/clickhouse/data/user_files/ - /var/lib/clickhouse/data/format_schemas/ - - - /var/lib/clickhouse/data/access/ - - - + {{- include "trigger-v4.clickhouse.dataPathsConfig" . | nindent 4 }} {{- end }} {{- range $filename, $content := .Values.clickhouse.configdFiles }} {{ $filename }}: | @@ -62,7 +47,7 @@ spec: template: metadata: annotations: - checksum/config: {{ .Values.clickhouse.configdFiles | toYaml | sha256sum }} + checksum/config: {{ printf "%s\n%s" (include "trigger-v4.clickhouse.dataPathsConfig" .) (.Values.clickhouse.configdFiles | toYaml) | sha256sum }} checksum/secret: {{ .Values.clickhouse.auth.password | sha256sum }} {{- with .Values.clickhouse.podAnnotations }} {{- toYaml . | nindent 8 }} From 4629ff95311f01c075992dd9b6b29cb6a27973d9 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 24 Jul 2026 14:40:06 +0100 Subject: [PATCH 5/7] fix(hosting): close upgrade gaps found in adversarial review of the ClickHouse chart Keeps existing self-hosted deployments working without manual steps: - Default resource requests/limits match the preset the Bitnami subchart applied, so the ClickHouse pod doesn't silently become BestEffort. - nodeSelector, tolerations, and affinity pass through to the StatefulSet for deployments that schedule ClickHouse onto dedicated nodes. - Optional volumePermissions init container fixes data-volume ownership on storage without fsGroup support (NFS, hostPath). - The helm test reads the password from the datastore secret instead of the usually-empty values key, and the pinned-password restart checksum is omitted when the password is auto-generated. - Docs cover updating a pinned CLICKHOUSE_IMAGE_TAG to an official tag and the GitOps existingClaim step in more detail. --- docs/self-hosting/docker.mdx | 7 +++++ docs/self-hosting/kubernetes.mdx | 9 +++++- hosting/k8s/helm/templates/clickhouse.yaml | 30 +++++++++++++++++++ .../helm/templates/tests/test-clickhouse.yaml | 12 ++++++-- hosting/k8s/helm/values.yaml | 29 ++++++++++++++++-- 5 files changed, 81 insertions(+), 6 deletions(-) diff --git a/docs/self-hosting/docker.mdx b/docs/self-hosting/docker.mdx index f409b6ec414..ed8aaa1bada 100644 --- a/docs/self-hosting/docker.mdx +++ b/docs/self-hosting/docker.mdx @@ -364,6 +364,13 @@ We patch the latest released version line only, so keep an eye on new releases t You can also lock the versions of the bundled services, for example with `CLICKHOUSE_IMAGE_TAG`. If you do, or if you bring your own ClickHouse via `CLICKHOUSE_URL`, note that Trigger.dev requires ClickHouse 25.8 or newer. + + The bundled ClickHouse now uses the official `clickhouse/clickhouse-server` image. Your existing + data volume carries over automatically. If you previously pinned `CLICKHOUSE_IMAGE_TAG` to a + Bitnami tag (for example `25.7.5-debian-12-r0`), update it to an official image tag such as + `26.2` — Bitnami tags don't exist in the official repository. + + Trigger.dev 4.5.0 is the last version we officially support for running v3 (SDK v3) tasks. If you still have v3 tasks, pin `TRIGGER_IMAGE_TAG` to exactly `v4.5.0` or [migrate to diff --git a/docs/self-hosting/kubernetes.mdx b/docs/self-hosting/kubernetes.mdx index cf605d1d06e..6b7dbf3e73d 100644 --- a/docs/self-hosting/kubernetes.mdx +++ b/docs/self-hosting/kubernetes.mdx @@ -286,7 +286,14 @@ redis: automatically adopts the existing data volume, so no manual migration is needed. If you render manifests without cluster access (for example with GitOps tools that use `helm template`), set `clickhouse.persistence.existingClaim` to the old PVC name - (`data--clickhouse-shard0-0`) to keep your data. + (`data--clickhouse-shard0-0`) to keep your data — auto-detection can't run there, and + skipping this starts ClickHouse on a fresh empty volume. + + + + On storage that doesn't support `fsGroup` ownership changes (for example NFS or hostPath), set + `clickhouse.volumePermissions.enabled: true` so a one-time init container fixes the data + volume's ownership for the non-root ClickHouse server. **Direct configuration:** diff --git a/hosting/k8s/helm/templates/clickhouse.yaml b/hosting/k8s/helm/templates/clickhouse.yaml index 8c1de94c443..edc7e62a760 100644 --- a/hosting/k8s/helm/templates/clickhouse.yaml +++ b/hosting/k8s/helm/templates/clickhouse.yaml @@ -48,7 +48,12 @@ spec: metadata: annotations: checksum/config: {{ printf "%s\n%s" (include "trigger-v4.clickhouse.dataPathsConfig" .) (.Values.clickhouse.configdFiles | toYaml) | sha256sum }} + {{- /* Restart on pinned-password changes; when the password is + auto-generated it lives in the retained datastore secret and + never rotates on upgrade, so there is nothing to hash. */}} + {{- if .Values.clickhouse.auth.password }} checksum/secret: {{ .Values.clickhouse.auth.password | sha256sum }} + {{- end }} {{- with .Values.clickhouse.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} @@ -63,6 +68,19 @@ spec: securityContext: {{- toYaml . | nindent 8 }} {{- end }} + {{- if .Values.clickhouse.volumePermissions.enabled }} + initContainers: + - name: volume-permissions + image: "{{ .Values.global.imageRegistry | default .Values.clickhouse.volumePermissions.image.registry }}/{{ .Values.clickhouse.volumePermissions.image.repository }}:{{ .Values.clickhouse.volumePermissions.image.tag }}" + imagePullPolicy: {{ .Values.clickhouse.volumePermissions.image.pullPolicy }} + command: ["sh", "-c", "chown -R 101:101 /var/lib/clickhouse"] + securityContext: + runAsUser: 0 + runAsNonRoot: false + volumeMounts: + - name: data + mountPath: /var/lib/clickhouse + {{- end }} containers: - name: clickhouse {{- with .Values.clickhouse.securityContext }} @@ -134,6 +152,18 @@ spec: mountPath: /etc/clickhouse-server/config.d/{{ $filename }} subPath: {{ $filename }} {{- end }} + {{- with .Values.clickhouse.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.clickhouse.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.clickhouse.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} volumes: - name: config configMap: diff --git a/hosting/k8s/helm/templates/tests/test-clickhouse.yaml b/hosting/k8s/helm/templates/tests/test-clickhouse.yaml index 3e420d05c1b..bb71e381b71 100644 --- a/hosting/k8s/helm/templates/tests/test-clickhouse.yaml +++ b/hosting/k8s/helm/templates/tests/test-clickhouse.yaml @@ -12,10 +12,18 @@ spec: containers: - name: test-clickhouse image: curlimages/curl:8.14.1 + env: + - name: CLICKHOUSE_USER + value: {{ .Values.clickhouse.auth.username | quote }} + - name: CLICKHOUSE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.clickhouse.auth.existingSecret | default (include "trigger-v4.datastore.secretName" .) }} + key: {{ .Values.clickhouse.auth.existingSecretKey | default "clickhouse-admin-password" }} command: ['sh', '-c'] args: - | echo "Testing ClickHouse HTTP interface..." - curl -f --user "{{ .Values.clickhouse.auth.username }}:{{ .Values.clickhouse.auth.password }}" "http://{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }}/ping" + curl -f --user "$CLICKHOUSE_USER:$CLICKHOUSE_PASSWORD" "http://{{ include "trigger-v4.clickhouse.hostname" . }}:{{ .Values.clickhouse.service.ports.http }}/ping" echo "ClickHouse test completed successfully" -{{- end }} \ No newline at end of file +{{- end }} diff --git a/hosting/k8s/helm/values.yaml b/hosting/k8s/helm/values.yaml index 39240f0192a..239c9968683 100644 --- a/hosting/k8s/helm/values.yaml +++ b/hosting/k8s/helm/values.yaml @@ -667,6 +667,22 @@ clickhouse: runAsUser: 101 runAsGroup: 101 + # One-time root init container that chowns the data volume to the ClickHouse + # uid. Only needed on storage that doesn't support fsGroup ownership changes + # (e.g. NFS, hostPath); on such storage a data volume carried over from the + # Bitnami-based chart is otherwise unreadable by the non-root server. + volumePermissions: + enabled: false + image: + registry: docker.io + repository: busybox + tag: "1.35" + pullPolicy: IfNotPresent + + nodeSelector: {} + tolerations: [] + affinity: {} + service: type: ClusterIP ports: @@ -689,9 +705,16 @@ clickhouse: ## ClickHouse resource requests and limits ## ref: http://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ - ## ClickHouse can be very resource intensive. Setting explicit limits and - ## requests is strongly recommended for production (see values-production-example.yaml). - resources: {} + ## ClickHouse can be very resource intensive. The defaults below match the + ## resource preset the chart previously applied; size them to your workload + ## for production (see values-production-example.yaml). + resources: + requests: + cpu: 1000m + memory: 3Gi + limits: + cpu: 3000m + memory: 6Gi livenessProbe: enabled: true From a872bf47033086f4b1949a5fd1224a4fffb58378 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 24 Jul 2026 17:19:44 +0100 Subject: [PATCH 6/7] docs(self-hosting): cover ClickHouse upgrade edge cases Adds the recovery path for GitOps renders that missed existingClaim, the pinned Bitnami image note for the Helm chart, the single-node clustering note, and the one-way rollback caveat for Docker Compose. --- docs/self-hosting/docker.mdx | 4 +++- docs/self-hosting/kubernetes.mdx | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/self-hosting/docker.mdx b/docs/self-hosting/docker.mdx index ed8aaa1bada..065b4ad55da 100644 --- a/docs/self-hosting/docker.mdx +++ b/docs/self-hosting/docker.mdx @@ -368,7 +368,9 @@ You can also lock the versions of the bundled services, for example with `CLICKH The bundled ClickHouse now uses the official `clickhouse/clickhouse-server` image. Your existing data volume carries over automatically. If you previously pinned `CLICKHOUSE_IMAGE_TAG` to a Bitnami tag (for example `25.7.5-debian-12-r0`), update it to an official image tag such as - `26.2` — Bitnami tags don't exist in the official repository. + `26.2` — Bitnami tags don't exist in the official repository. Note the switch is one-way: the + official image takes ownership of the data files, so rolling back to the Bitnami image requires + manually restoring their previous owner (`chown -R 1001:1001` on the volume). diff --git a/docs/self-hosting/kubernetes.mdx b/docs/self-hosting/kubernetes.mdx index 6b7dbf3e73d..e5105ae57b6 100644 --- a/docs/self-hosting/kubernetes.mdx +++ b/docs/self-hosting/kubernetes.mdx @@ -287,7 +287,16 @@ redis: manifests without cluster access (for example with GitOps tools that use `helm template`), set `clickhouse.persistence.existingClaim` to the old PVC name (`data--clickhouse-shard0-0`) to keep your data — auto-detection can't run there, and - skipping this starts ClickHouse on a fresh empty volume. + skipping this starts ClickHouse on a fresh empty volume. If that happened, your old data is + still on the old PVC: delete the ClickHouse StatefulSet with `--cascade=orphan` (its volume + configuration is immutable), set `existingClaim`, and sync again. + + + + If you pinned `clickhouse.image` to a Bitnami repository or tag in your values, update it to + the official `clickhouse/clickhouse-server` image — Bitnami tags don't exist there. The + bundled ClickHouse is single-node: the old Bitnami subchart keys (`shards`, `replicaCount`, + `keeper`) are no longer supported, so use an external ClickHouse for clustered setups. From f7da3f8795ccdceb34057a2eadb2e14b3ce89852 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 24 Jul 2026 17:51:27 +0100 Subject: [PATCH 7/7] fix(hosting): sync webapp to a custom ClickHouse secret and add a startup probe The webapp now resolves CLICKHOUSE_PASSWORD through the same auth.existingSecret/existingSecretKey values the bundled server uses, so pointing the chart at a custom credentials secret keeps both sides on the same password. A startup probe gives ClickHouse up to ten minutes to load metadata on first boot with a large adopted data volume before liveness checks begin. --- hosting/k8s/helm/templates/clickhouse.yaml | 11 +++++++++++ hosting/k8s/helm/templates/webapp.yaml | 6 ++++-- hosting/k8s/helm/values.yaml | 9 +++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/hosting/k8s/helm/templates/clickhouse.yaml b/hosting/k8s/helm/templates/clickhouse.yaml index edc7e62a760..fb42565dee9 100644 --- a/hosting/k8s/helm/templates/clickhouse.yaml +++ b/hosting/k8s/helm/templates/clickhouse.yaml @@ -131,6 +131,17 @@ spec: failureThreshold: {{ .Values.clickhouse.readinessProbe.failureThreshold }} successThreshold: {{ .Values.clickhouse.readinessProbe.successThreshold }} {{- end }} + {{- if .Values.clickhouse.startupProbe.enabled }} + startupProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: {{ .Values.clickhouse.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.clickhouse.startupProbe.periodSeconds }} + timeoutSeconds: {{ .Values.clickhouse.startupProbe.timeoutSeconds }} + failureThreshold: {{ .Values.clickhouse.startupProbe.failureThreshold }} + successThreshold: {{ .Values.clickhouse.startupProbe.successThreshold }} + {{- end }} resources: {{- toYaml .Values.clickhouse.resources | nindent 12 }} volumeMounts: diff --git a/hosting/k8s/helm/templates/webapp.yaml b/hosting/k8s/helm/templates/webapp.yaml index 49acee98f4a..d666237daaf 100644 --- a/hosting/k8s/helm/templates/webapp.yaml +++ b/hosting/k8s/helm/templates/webapp.yaml @@ -419,11 +419,13 @@ spec: name: {{ include "trigger-v4.clickhouse.external.secretName" . }} key: {{ include "trigger-v4.clickhouse.external.passwordKey" . }} {{- else if .Values.clickhouse.deploy }} + {{- /* Same secret reference the bundled ClickHouse server uses, so a + custom auth.existingSecret keeps the app and server in sync. */}} - name: CLICKHOUSE_PASSWORD valueFrom: secretKeyRef: - name: {{ include "trigger-v4.datastore.secretName" . }} - key: clickhouse-admin-password + name: {{ .Values.clickhouse.auth.existingSecret | default (include "trigger-v4.datastore.secretName" .) }} + key: {{ .Values.clickhouse.auth.existingSecretKey | default "clickhouse-admin-password" }} {{- end }} - name: CLICKHOUSE_URL value: {{ include "trigger-v4.clickhouse.url" . | quote }} diff --git a/hosting/k8s/helm/values.yaml b/hosting/k8s/helm/values.yaml index 239c9968683..e6ea51864f1 100644 --- a/hosting/k8s/helm/values.yaml +++ b/hosting/k8s/helm/values.yaml @@ -730,6 +730,15 @@ clickhouse: timeoutSeconds: 5 failureThreshold: 5 successThreshold: 1 + # Generous startup window: first boot on a large adopted data volume can + # spend a while loading metadata before the HTTP listener answers. + startupProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 60 + successThreshold: 1 # External ClickHouse connection (when deploy: false) external: