Skip to content

fix(helm): expand CLICKHOUSE_PASSWORD in webapp CLICKHOUSE_URL via kubelet#3449

Draft
ThullyoCunha wants to merge 1 commit intotriggerdotdev:mainfrom
ThullyoCunha:fix/clickhouse-external-url-kubelet-expansion
Draft

fix(helm): expand CLICKHOUSE_PASSWORD in webapp CLICKHOUSE_URL via kubelet#3449
ThullyoCunha wants to merge 1 commit intotriggerdotdev:mainfrom
ThullyoCunha:fix/clickhouse-external-url-kubelet-expansion

Conversation

@ThullyoCunha
Copy link
Copy Markdown

Summary

When the official Helm chart is deployed with an external ClickHouse and clickhouse.external.existingSecret set — the documented path for not committing secrets to values.yaml — the webapp pod crash-loops on startup:

goose run: parse "http://default:${CLICKHOUSE_PASSWORD}@<host>:8123?secure=false": net/url: invalid userinfo

Context in vouch request #3443. Re-opening in draft status per bot policy (previous attempt was #3445, closed by automation because it wasn't draft; no changes to the patch).

Root cause

Two pieces interact:

  1. hosting/k8s/helm/templates/_helpers.tpl renders CLICKHOUSE_URL (and RUN_REPLICATION_CLICKHOUSE_URL) with a shell-style literal ${CLICKHOUSE_PASSWORD} expecting bash expansion at container start.
  2. docker/scripts/entrypoint.sh does export GOOSE_DBSTRING="$CLICKHOUSE_URL" — single-pass POSIX sh substitution, so the inner ${...} survives as literal text and goose rejects it.

Reproduces against the latest published chart (oci://ghcr.io/triggerdotdev/charts/trigger:4.0.5) and main.

Fix

Switch the two helpers (external + existingSecret branch) from shell-style ${CLICKHOUSE_PASSWORD} to Kubernetes' $(CLICKHOUSE_PASSWORD). Kubelet substitutes $(VAR) at pod-creation time from earlier env entries, and the chart already declares CLICKHOUSE_PASSWORD from the Secret immediately before CLICKHOUSE_URL, so the URL reaches the entrypoint with the real password already inlined. No entrypoint change, no image change. The plain-password branch (no existingSecret) is unchanged.

Operator caveat added as template comments: CLICKHOUSE_PASSWORD must be URL-userinfo-safe since kubelet substitutes verbatim without percent-encoding. Hex-encoded passwords (e.g. openssl rand -hex 32) are safe by construction.

Verification

  • helm template against external.existingSecret now renders value: "http://default:$(CLICKHOUSE_PASSWORD)@<host>:8123?secure=false" (was ${CLICKHOUSE_PASSWORD}).
  • helm template against the plain-password branch is byte-identical to before.
  • Deployed end-to-end on a staging EKS cluster (Meistrari platform): webapp container reaches goose: successfully migrated database to version: 6, Node.js ClickHouse client connects at runtime.

Alternatives considered

  • Change entrypoint.sh to eval / envsubst the URL — larger surface, touches every deployment mode (Docker Compose + k8s) and every container image.
  • Mirror the Postgres pattern (chart reads the full URL via valueFrom.secretKeyRef, as in trigger-v4.postgres.useSecretUrl) — cleaner long-term but requires a new values.yaml field and a migration path for existing users. Happy to follow up with that as a separate PR if the minimal fix here isn't the preferred direction.

Changeset

None added — the Helm chart isn't versioned through @changesets/cli (docs/chart-only PRs historically merge without a changeset, e.g. #2671). Happy to add one if the policy changed.

Closes #3443.

…belet

When clickhouse.external.existingSecret is set, the chart rendered the
CLICKHOUSE_URL env var with a literal shell-style ${CLICKHOUSE_PASSWORD}
placeholder, expecting bash to expand it at container start. But
docker/scripts/entrypoint.sh hands the value straight to goose with a
single-pass sh expansion (export GOOSE_DBSTRING="$CLICKHOUSE_URL"), so
the inner ${...} reaches goose as literal text and breaks the
ClickHouse migration:

  goose run: parse "http://default:${CLICKHOUSE_PASSWORD}@host:8123?secure=false":
  net/url: invalid userinfo

Switch to Kubernetes' $(VAR) syntax in both clickhouse URL helpers.
Kubelet substitutes $(CLICKHOUSE_PASSWORD) at container-creation time
from the CLICKHOUSE_PASSWORD env var the chart already sets just before
CLICKHOUSE_URL, so the URL arrives at the entrypoint with the real
password already inlined — no entrypoint change needed, works for any
container image / shell.

The plain-password branch (no existingSecret) is unchanged.

Operator caveat: CLICKHOUSE_PASSWORD must be URL-userinfo-safe because
kubelet substitutes verbatim without percent-encoding. Hex-encoded
passwords (e.g. openssl rand -hex 32) are safe by construction.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

⚠️ No Changeset found

Latest commit: d1f4748

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Walkthrough

This change updates the Helm template helpers file to clarify and correct ClickHouse credential handling when using existing Kubernetes Secrets. The documentation is expanded to specify that passwords should use Kubernetes env-var placeholder syntax $(CLICKHOUSE_PASSWORD) instead of shell-style ${CLICKHOUSE_PASSWORD}. The two ClickHouse URL generation templates (for application and replication URLs) are updated to reflect this placeholder syntax in the external.existingSecret branch.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing ClickHouse password expansion in the webapp URL using Kubernetes kubelet syntax instead of shell syntax.
Description check ✅ Passed The PR description is comprehensive and follows the template structure with all key sections: summary, root cause analysis, fix explanation, verification, and alternatives considered.
Linked Issues check ✅ Passed The PR fully addresses the requirements from #3443: switches helper templates from shell-style ${CLICKHOUSE_PASSWORD} to Kubernetes $(CLICKHOUSE_PASSWORD) syntax, preserves plain-password branch behavior, and documents the URL-userinfo-safety caveat.
Out of Scope Changes check ✅ Passed All changes are scoped to the Helm helper templates for ClickHouse URL generation; no unrelated modifications or out-of-scope code changes are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
hosting/k8s/helm/templates/_helpers.tpl (1)

403-415: Consider surfacing the password URL-safety caveat in values.yaml comments too.

The new helper comments are accurate, but operators are more likely to see values.yaml guidance than _helpers.tpl. Mirroring this caveat there would reduce misconfiguration risk.

Based on learnings: In the Trigger.dev v4 Helm chart, the user prefers to rely on documentation and examples in values files rather than implementing defensive coding in templates, particularly for features like extraManifests where proper usage is documented.

Also applies to: 436-438

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hosting/k8s/helm/templates/_helpers.tpl` around lines 403 - 415, Add the same
URL-safety caveat currently in _helpers.tpl into values.yaml comments: mention
that CLICKHOUSE_PASSWORD is expanded using Kubernetes $(VAR) syntax (not shell
${VAR}), that CLICKHOUSE_URL is built from CLICKHOUSE_PASSWORD and passed into
GOOSE_DBSTRING via docker/scripts/entrypoint.sh, and that the password must be
URL-userinfo-safe (avoid @ : / ? # [ ] % or use hex/percent-encoding) so the
generated ClickHouse/Goose DB URL parses correctly; place this comment near the
CLICKHOUSE/connection-related keys and any external+existingSecret examples so
operators see it in values.yaml.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@hosting/k8s/helm/templates/_helpers.tpl`:
- Around line 403-415: Add the same URL-safety caveat currently in _helpers.tpl
into values.yaml comments: mention that CLICKHOUSE_PASSWORD is expanded using
Kubernetes $(VAR) syntax (not shell ${VAR}), that CLICKHOUSE_URL is built from
CLICKHOUSE_PASSWORD and passed into GOOSE_DBSTRING via
docker/scripts/entrypoint.sh, and that the password must be URL-userinfo-safe
(avoid @ : / ? # [ ] % or use hex/percent-encoding) so the generated
ClickHouse/Goose DB URL parses correctly; place this comment near the
CLICKHOUSE/connection-related keys and any external+existingSecret examples so
operators see it in values.yaml.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 30e2cee5-aa87-48d9-ae54-a398d89dd880

📥 Commits

Reviewing files that changed from the base of the PR and between 5693b62 and d1f4748.

📒 Files selected for processing (1)
  • hosting/k8s/helm/templates/_helpers.tpl
📜 Review details
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2195
File: hosting/k8s/helm/values-production-example.yaml:95-102
Timestamp: 2025-06-25T14:14:11.965Z
Learning: In the Trigger.dev Helm chart production examples, the maintainer prefers to include initial/bootstrap credentials with clear warnings that they should be changed after first login, rather than requiring external secret references that could complicate initial setup. This follows their pattern of providing working examples with explicit security guidance.
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2155
File: hosting/docker/.env.example:4-7
Timestamp: 2025-06-06T23:55:01.933Z
Learning: In the trigger.dev project, .env.example files should contain actual example secret values rather than placeholders, as these help users understand the expected format. The files include clear warnings about not using these defaults in production and instructions for generating proper secrets.
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2155
File: hosting/docker/registry/auth.htpasswd:1-1
Timestamp: 2025-06-06T18:49:18.144Z
Learning: Example credentials in self-hosting documentation files are acceptable when they serve as templates or examples for users to understand the setup process, particularly in hosting/docker configuration files.
📚 Learning: 2025-06-25T14:14:11.965Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2195
File: hosting/k8s/helm/values-production-example.yaml:95-102
Timestamp: 2025-06-25T14:14:11.965Z
Learning: In the Trigger.dev Helm chart production examples, the maintainer prefers to include initial/bootstrap credentials with clear warnings that they should be changed after first login, rather than requiring external secret references that could complicate initial setup. This follows their pattern of providing working examples with explicit security guidance.

Applied to files:

  • hosting/k8s/helm/templates/_helpers.tpl
📚 Learning: 2025-06-25T13:18:04.827Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2195
File: hosting/k8s/helm/templates/extra-manifests.yaml:1-4
Timestamp: 2025-06-25T13:18:04.827Z
Learning: In the Trigger.dev v4 Helm chart, the user prefers to rely on documentation and examples in values files rather than implementing defensive coding in templates, particularly for features like extraManifests where proper usage is documented.

Applied to files:

  • hosting/k8s/helm/templates/_helpers.tpl
📚 Learning: 2025-06-06T18:49:18.144Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2155
File: hosting/docker/registry/auth.htpasswd:1-1
Timestamp: 2025-06-06T18:49:18.144Z
Learning: Example credentials in self-hosting documentation files are acceptable when they serve as templates or examples for users to understand the setup process, particularly in hosting/docker configuration files.

Applied to files:

  • hosting/k8s/helm/templates/_helpers.tpl
📚 Learning: 2025-06-25T13:20:17.174Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2195
File: hosting/k8s/helm/values.yaml:22-51
Timestamp: 2025-06-25T13:20:17.174Z
Learning: In the Trigger.dev Helm chart values.yaml, the maintainer prefers to use explicit comprehensive warnings for security-sensitive default values rather than implementing secure-by-default behavior that would fail installation. The project uses deterministic default secrets with clear "TESTING ONLY" warnings and instructions for production deployment.

Applied to files:

  • hosting/k8s/helm/templates/_helpers.tpl
📚 Learning: 2026-04-23T08:44:10.511Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3429
File: hosting/k8s/helm/values.yaml:211-218
Timestamp: 2026-04-23T08:44:10.511Z
Learning: In the Trigger.dev Helm chart helper templates, for both `trigger-v4.webappServiceAccountName` and `trigger-v4.supervisorServiceAccountName`, if `serviceAccount.create` is `false` and `serviceAccount.name` is empty, use Helm `fail` to stop rendering (raise a template error) rather than falling back to the namespace default service account or depending on documentation warnings. This prevents silently producing an invalid/undesired ServiceAccount configuration.

Applied to files:

  • hosting/k8s/helm/templates/_helpers.tpl
🔇 Additional comments (1)
hosting/k8s/helm/templates/_helpers.tpl (1)

426-426: Good fix: kubelet-style substitution is correctly applied in both ClickHouse URL helpers.

Switching to $(CLICKHOUSE_PASSWORD) in both external+existingSecret branches addresses the crash-loop root cause while keeping the plain-password branch behavior intact.

Also applies to: 446-446

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vouch request: fix Helm chart CLICKHOUSE_URL literal-placeholder bug for self-hosters with external ClickHouse

1 participant