[feature] Frontend: add Helm chart for deploying goquery-ui frontend#474
Merged
Conversation
Adds charts/goquery-ui, a frontend-only Helm chart for the goprobe/frontend image (Caddy-served SPA on port 5137). Designed to sit behind a gateway: ships a ClusterIP Service with a named http port and no Ingress/Gateway/ VirtualService — external routing is the deployer's responsibility. - backend.url is required (Caddy reverse-proxy target, GQ_BACKEND_HOST); render fails fast if unset. - Runtime settings (GQ_API_BASE_URL, HOST_RESOLVER_TYPES, SSE_ON_LOAD) come from a ConfigMap with a checksum/config annotation to roll pods on change. - Hardened pod: non-root 1000, drop ALL caps, readOnlyRootFilesystem, seccomp RuntimeDefault, with emptyDir for /var/run/env, /tmp, /data, /config. - Liveness/readiness probe GET / on the http port; dedicated ServiceAccount with token automount off; optional HPA and PDB, both off by default. Records the no-in-cluster-routing decision in docs/adr/0001. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Publish the chart as an OCI artifact on push to main whenever its Chart.yaml version is not already present in ghcr.io/els0r/charts. Existence-checked rather than diff-triggered so a failed run self-heals and a published version is never clobbered. Chart semver stays decoupled from the app's v*.*.* release tags. Add a PR lint-chart job (helm lint + helm template + ct lint with --check-version-increment) so chart breakage and missing version bumps fail review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ct validated the maintainer "goProbe maintainers" as a GitHub user and 404'd. It's a team label with a URL, not a handle, so disable --validate-maintainers. Also drop --charts so ct detects changed charts by diffing the target branch, which is what makes --check-version-increment actually run (it was reported disabled otherwise). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
els0r
added a commit
that referenced
this pull request
Jun 17, 2026
## Problem Deploying the `goquery-ui` Helm chart fails at container startup: ``` env.js written with GQ_API_BASE_URL=<empty/proxy> /docker-entrypoint.sh: exec: line 27: caddy: Operation not permitted ``` The chart runs the pod with a hardened `securityContext` (`allowPrivilegeEscalation: false`, `readOnlyRootFilesystem: true`, `capabilities.drop: [ALL]`, non-root). Two image-level assumptions broke under it. ## Root causes & fixes 1. **Caddy file capability → EPERM.** The `caddy` binary copied from `caddy:2-alpine` carries `cap_net_bind_service=+ep` (so upstream Caddy can bind :80/:443 as non-root). BuildKit's `COPY` preserves that file capability. With `allowPrivilegeEscalation: false`, the kernel's `no_new_privs` flag makes `exec()` of a file bearing file caps return `EPERM` — exactly the error above, at the entrypoint's `exec "$@"`. We listen on `:5137` (non-privileged), so the capability is unnecessary → strip it with `setcap -r`. 2. **Caddy storage on read-only rootfs.** The `alpine` base never sets `XDG_DATA_HOME`/`XDG_CONFIG_HOME`, so Caddy fell back to `$HOME/.local/share` & `$HOME/.config` on the read-only rootfs — producing startup errors and leaving the chart's `/data` & `/config` mounts inert. Point both env vars at `/data` and `/config`. ## Reproduce-locally harness `docker-compose.yml` is now a single default service that runs the production image hardened **1:1** with the chart's `securityContext` (`no-new-privileges`, `read_only`, `cap_drop ALL`, `user 1000`, tmpfs-backed writable paths). No profiles — `docker compose up` just works. This is what surfaced both bugs and would have caught them before the cluster: ``` docker compose up --build # or: make docker-up → http://localhost:8080 ``` Verified end-to-end: container `Up (healthy)`, `/` returns 200, `/env.js` generated, Caddy writes to `/data/caddy` & `/config/caddy` with no read-only errors. Local hot-reload development is `npm run dev` (`:5173`) — the old container-wrapped dev path only ran that same command, so it and its `Dockerfile.dev` / `.env.development` were removed. ## Versioning — no `Chart.yaml` change here This is an **image fix** (Dockerfile) with no chart-template change, so it deliberately leaves `Chart.yaml` untouched. Chart releases follow two avenues: 1. **`version:` bump** — chart-only changes (templates/values), independent of the app's `v*.*.*` tags; published by `publish-chart.yml` on merge to `main`. 2. **Tagged release** — bumps `appVersion` (and `version:`) to the new `goprobe/frontend` image and ships a new chart. This fix belongs to avenue 2: it reaches chart users when **`v4.2.12`** is cut — `build-docker.yml` rebuilds `goprobe/frontend:4.2.12` and the release bumps the chart's `appVersion`. > [!NOTE] > Avenue 2 isn't automated yet — `appVersion` is bumped by hand today (as it was for `4.2.11` in #474). Wiring the `appVersion` bump into the release process is a worthwhile follow-up, but out of scope for this fix. ## Files - `frontend/goquery-ui/Dockerfile` — `setcap -r` + `XDG_DATA_HOME`/`XDG_CONFIG_HOME` - `frontend/goquery-ui/docker-compose.yml` — single hardened prod-parity service (no profiles) - `frontend/goquery-ui/Makefile` — `docker-up`/`-stop`/`-logs` on the default service; drop `docker-dev` - `frontend/goquery-ui/README.md` — `npm run dev` for dev, `docker compose up` for prod-like - removed `Dockerfile.dev` / `.env.development` (+ its `.gitignore` exception) — orphaned by the profile collapse 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Lennart Elsen <lelsen@open-systems.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds charts/goquery-ui, a frontend-only Helm chart for the goprobe/frontend image (Caddy-served SPA on port 5137). Designed to sit behind a gateway: ships a ClusterIP Service with a named http port and no Ingress/Gateway/VirtualService — external routing is the deployer's responsibility.
Records the no-in-cluster-routing decision in docs/adr/0001.
Packaging & distribution
The chart is published from CI so it can be consumed as a dependency instead of copied by directory.
.github/workflows/publish-chart.yml): on push tomaintouchingcharts/goquery-ui/**, packages the chart and pushes it to GHCR as an OCI artifact atoci://ghcr.io/els0r/charts/goquery-ui. Publishing is existence-checked, not diff-triggered — it ships only when theChart.yamlversion:is not already in the registry, so a failed run self-heals on the next push and an already-published version is never clobbered. Uses the built-inGITHUB_TOKEN(packages: write); no new secret required.Chart.yaml, independent of the app'sv*.*.*release tags. Bumpversion:to ship; that field is the single source of truth.lint-chartinci-pr.yml):helm lint+helm template(with a representativebackend.url) +ct lint --check-version-increment, so chart breakage and missing version bumps fail review.Consumers then declare it as a dependency:
Note
One-time GHCR setup after the first publish: the package is created private and owned by the user. Set its visibility to public (anonymous
helm pull) and link it to the repo in the GHCR package settings — this can't be done from the workflow.