Conversation
The goquery-ui Helm chart runs the frontend pod with a hardened
securityContext (allowPrivilegeEscalation: false, readOnlyRootFilesystem,
cap drop ALL, non-root). Two image-level assumptions broke under it:
1. The Caddy binary copied from caddy:2-alpine carries the
cap_net_bind_service file capability. Under no_new_privs the kernel
refuses to exec() a file with file caps and returns EPERM
("caddy: Operation not permitted"). We listen on :5137, so the cap is
unneeded — strip it with `setcap -r`.
2. The alpine base never sets XDG_DATA_HOME/XDG_CONFIG_HOME, so Caddy
wrote storage to $HOME/.local & $HOME/.config on the read-only rootfs,
erroring on startup and leaving the chart's /data & /config mounts
inert. Point both at /data and /config.
Also add a hardened `prod` profile to docker-compose that mirrors the
chart's securityContext 1:1, so these deployment-time failures surface
locally (`make docker-prod`) instead of in the cluster.
This is an image fix: it ships to chart users via the v4.2.12 release,
which rebuilds goprobe/frontend and bumps the chart's appVersion. No
Chart.yaml change here — appVersion is owned by the release process.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fako1024
left a comment
There was a problem hiding this comment.
No, this is completely overboard, using another profile will just overcomplicate the use. We have to address this properly in the main profile.
Address review: drop the dev/prod compose profiles — they overcomplicate use. docker-compose now has one default service: the production Caddy image, hardened 1:1 with the Helm chart's securityContext. Local hot-reload development is `npm run dev` directly (the old `dev` profile only wrapped that same command in a container on the same :5173). Update Makefile (docker-prod/-stop/-logs use the default service; drop docker-dev) and README accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed in a451ad3. Dropped the dev/prod profiles entirely — |
Dockerfile.dev and .env.development were only used by the docker-compose dev profile, which was dropped in favour of `npm run dev`. Remove them and the now-moot .env.development gitignore exception (.env stays ignored for the prod compose's host-specific runtime overrides). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fako1024
left a comment
There was a problem hiding this comment.
Minor wording feedback, fix it!
| # | ||
| # Usage: | ||
| # docker compose up (or: make docker-dev) | ||
| # docker compose up --build (or: make docker-prod) |
There was a problem hiding this comment.
If there's only one profile, no need to suffix with -prod.
| @echo " make docker-dev - docker compose dev server (hot reload)" | ||
| @echo " make docker-stop - stop docker compose dev server" | ||
| @echo " make docker-logs - view docker compose dev logs" | ||
| @echo " make docker-prod - run hardened prod-like image via docker compose" |
There was a problem hiding this comment.
If there's only one profile, no need to suffix with -prod.
| docker-dev: | ||
| @echo "Starting Docker development server with hot reload..." | ||
| @echo "Access the application at http://localhost:5173" | ||
| .PHONY: docker-prod |
There was a problem hiding this comment.
If there's only one profile, no need to suffix with -prod.
| @echo "Starting Docker development server with hot reload..." | ||
| @echo "Access the application at http://localhost:5173" | ||
| .PHONY: docker-prod | ||
| docker-prod: |
There was a problem hiding this comment.
If there's only one profile, no need to suffix with -prod.
| ```bash | ||
| make docker-dev # same as compose dev profile | ||
| make docker-prod # same as compose prod profile | ||
| make docker-prod # run hardened prod-like image (docker compose up --build) |
There was a problem hiding this comment.
If there's only one profile, no need to suffix with -prod.
Address review: with a single compose service there's no prod/dev split, so the `-prod` suffix is redundant. Rename `make docker-prod` -> `docker-up` (consistent with docker-stop/-logs/-build) and update the compose/README references. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed in ad550f9 — dropped the redundant |
…477) ## What Automates **avenue 2** of the goquery-ui chart release model (PR #476): a tagged app release now retargets the chart's `appVersion`, bumps its `version:`, ships the chart, and records the bump on `main` — no manual edit. Adds a `release-chart` job to [build-docker.yml](.github/workflows/build-docker.yml): - `needs: [build-docker]` — runs only after the `goprobe/frontend` image is pushed, so the chart never pins an image that doesn't exist yet. - `if: !contains(github.ref_name, '-')` — final tags only; prereleases (`-rc*`) build images but never ship a chart. - Reads `appVersion` from `main`; **idempotency guard**: if it already equals the tag, the job is a no-op (re-runs and partial-failure retries converge). - Otherwise patch-bumps `version:`, sets `appVersion` to the tag's `X.Y.Z` (surgical `sed`, preserving the chart's comments/spacing), `helm package`, GHCR existence check, `helm push`, then commits both fields back to `main` with `GITHUB_TOKEN` under a `[trivial]` message. ## Why this shape Publishing straight from the tag via `helm package --app-version` sidesteps the well-known rule that a `GITHUB_TOKEN` push does **not** re-trigger another workflow — so we need no PAT, no `repository_dispatch`, and no second publish path. The "won't re-trigger" behaviour becomes a feature (no double publish). Alternatives weighed (always-`latest`, commit-back-with-PAT) and the rationale are in [ADR 0002](docs/adr/0002-tagged-releases-publish-goquery-ui-chart-pinned-to-appversion.md); shared vocabulary is in [CONTEXT.md](CONTEXT.md). `publish-chart.yml` is unchanged and remains the path for avenue 1 (chart-only changes merged to `main`). ## Test plan Verified locally: - Workflow YAML parses; both jobs recognized. - Version logic: `0.1.1 → 0.1.2`, `appVersion → "4.3.0"`. - `sed` edit touches only the two value lines; `apiVersion` untouched; comments/spacing preserved. Exercised live only on the first real `v*.*.*` tag (helm GHCR push + `GITHUB_TOKEN` push to `main` need a runner). 🤖 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>
Problem
Deploying the
goquery-uiHelm chart fails at container startup: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
Caddy file capability → EPERM. The
caddybinary copied fromcaddy:2-alpinecarriescap_net_bind_service=+ep(so upstream Caddy can bind :80/:443 as non-root). BuildKit'sCOPYpreserves that file capability. WithallowPrivilegeEscalation: false, the kernel'sno_new_privsflag makesexec()of a file bearing file caps returnEPERM— exactly the error above, at the entrypoint'sexec "$@". We listen on:5137(non-privileged), so the capability is unnecessary → strip it withsetcap -r.Caddy storage on read-only rootfs. The
alpinebase never setsXDG_DATA_HOME/XDG_CONFIG_HOME, so Caddy fell back to$HOME/.local/share&$HOME/.configon the read-only rootfs — producing startup errors and leaving the chart's/data&/configmounts inert. Point both env vars at/dataand/config.Reproduce-locally harness
docker-compose.ymlis now a single default service that runs the production image hardened 1:1 with the chart'ssecurityContext(no-new-privileges,read_only,cap_drop ALL,user 1000, tmpfs-backed writable paths). No profiles —docker compose upjust works. This is what surfaced both bugs and would have caught them before the cluster:Verified end-to-end: container
Up (healthy),/returns 200,/env.jsgenerated, Caddy writes to/data/caddy&/config/caddywith 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 itsDockerfile.dev/.env.developmentwere removed.Versioning — no
Chart.yamlchange hereThis is an image fix (Dockerfile) with no chart-template change, so it deliberately leaves
Chart.yamluntouched. Chart releases follow two avenues:version:bump — chart-only changes (templates/values), independent of the app'sv*.*.*tags; published bypublish-chart.ymlon merge tomain.appVersion(andversion:) to the newgoprobe/frontendimage and ships a new chart.This fix belongs to avenue 2: it reaches chart users when
v4.2.12is cut —build-docker.ymlrebuildsgoprobe/frontend:4.2.12and the release bumps the chart'sappVersion.Note
Avenue 2 isn't automated yet —
appVersionis bumped by hand today (as it was for4.2.11in #474). Wiring theappVersionbump 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_HOMEfrontend/goquery-ui/docker-compose.yml— single hardened prod-parity service (no profiles)frontend/goquery-ui/Makefile—docker-up/-stop/-logson the default service; dropdocker-devfrontend/goquery-ui/README.md—npm run devfor dev,docker compose upfor prod-likeDockerfile.dev/.env.development(+ its.gitignoreexception) — orphaned by the profile collapse🤖 Generated with Claude Code