fix(ci): drop the kaniko debug image; record build timestamps in the images #556

Closed
claude-bot wants to merge 4 commits from kaniko-no-debug-image into main
Member

Removes -debug from all eight build steps, and applies the build-timestamp pattern to all nine Containerfiles in the repo.

Why

The -debug variant ships a busybox shell so a failing build can be inspected. It has been the build image since 49018489 (2026-04-29), because the steps ran shell to assemble registry auth and compute a date for the tag — which is not what that variant is for.

The date does not need a shell in the build step

It was being computed in the pipeline and passed to --destination. It does not have to cross between CI steps at all: the base images have a shell, so a RUN writes the timestamp during the build itself. kaniko executes the layer and the value lands in the image.

RUN date -u +%Y-%m-%dT%H:%M:%SZ > /etc/image-build-date

Verified:

$ docker run --rm localtest:jq cat /etc/image-build-date
2026-08-23T18:56:51Z

Applied to all nine: ansible, k8s, tofu, alpine-jq, alpine-openssl, cni-plugins, heating-can, claude, secret-rotator. Always the last layer, so it invalidates only itself. secret-rotator writes it in the rust build stage and copies it across, since its final image is FROM scratch.

On ci-claude and #351

I initially skipped ci-claude over a675bf6c, on a misreading. That commit did not make the whole image reproducible — it made the npm install layer byte-stable so a rebuild of the same version dedups instead of orphaning ~260 MB (#336, #351). A timestamp in its own final layer leaves the npm layer untouched, so that dedup still holds.

Tagging

Tags stay dated literals — ci-ansible:2026-08-23 — bumped in the commit that changes an image, the same trigger the when: path filters use. The existing convention every consumer pin follows (ci-k8s:2026-08-10, ci-ansible:2026-04-29, alpine-jq:2026-07-30) is unchanged.

The tag says which build; /etc/image-build-date says when it ran.

Pipeline changes

The plain executor has no shell, so builds pass flags via entrypoint and read credentials from DOCKER_CONFIG. A single registry-auth step writes .docker/config.json into the workspace once, in alpine, where a shell is the intended interface.

  - name: build-ansible
    image: gcr.io/kaniko-project/executor:v1.24.0
    directory: ci/images/ansible
    depends_on: [registry-auth]
    environment:
      DOCKER_CONFIG: "${CI_WORKSPACE}/.docker"
    entrypoint:
      - /kaniko/executor
      - --context=.
      - --dockerfile=Containerfile
      - --destination=gitea.renner.uno/gitea_admin/ci-ansible:2026-08-23
      - --destination=gitea.renner.uno/gitea_admin/ci-ansible:latest

Also moves off v1.23.2, picking up the go-git fix for CVE-2025-21613 and containerd 1.7.18 -> 1.7.27.

Not converted

ci-claude-refresh.yml still builds with the debug image — its destination tag is a runtime value read from a file written by an earlier step, and the build is conditionally skipped. Its Containerfile gets the timestamp here; the pipeline step is left for #552.

Needs a pipeline run

A manual build-ci-images run confirms ${CI_WORKSPACE} expands in environment: values and that the auth file written by registry-auth reaches the build steps.

Refs #426, #552

🤖 Generated with Claude Code

Removes `-debug` from all eight build steps, and applies the build-timestamp pattern to **all nine Containerfiles** in the repo. ## Why The `-debug` variant ships a busybox shell so a failing build can be inspected. It has been the build image since 49018489 (2026-04-29), because the steps ran shell to assemble registry auth and compute a date for the tag — which is not what that variant is for. ## The date does not need a shell in the build step It was being computed in the pipeline and passed to `--destination`. It does not have to cross between CI steps at all: the base images have a shell, so a `RUN` writes the timestamp during the build itself. kaniko executes the layer and the value lands in the image. ``` RUN date -u +%Y-%m-%dT%H:%M:%SZ > /etc/image-build-date ``` Verified: ``` $ docker run --rm localtest:jq cat /etc/image-build-date 2026-08-23T18:56:51Z ``` Applied to all nine: ansible, k8s, tofu, alpine-jq, alpine-openssl, cni-plugins, heating-can, claude, secret-rotator. Always the last layer, so it invalidates only itself. `secret-rotator` writes it in the rust build stage and copies it across, since its final image is `FROM scratch`. ### On ci-claude and #351 I initially skipped ci-claude over a675bf6c, on a misreading. That commit did not make the whole image reproducible — it made the **npm install layer** byte-stable so a rebuild of the same version dedups instead of orphaning ~260 MB (#336, #351). A timestamp in its own final layer leaves the npm layer untouched, so that dedup still holds. ## Tagging Tags stay dated literals — `ci-ansible:2026-08-23` — bumped in the commit that changes an image, the same trigger the `when:` path filters use. The existing convention every consumer pin follows (`ci-k8s:2026-08-10`, `ci-ansible:2026-04-29`, `alpine-jq:2026-07-30`) is unchanged. The tag says which build; `/etc/image-build-date` says when it ran. ## Pipeline changes The plain executor has no shell, so builds pass flags via `entrypoint` and read credentials from `DOCKER_CONFIG`. A single `registry-auth` step writes `.docker/config.json` into the workspace once, in alpine, where a shell is the intended interface. ```yaml - name: build-ansible image: gcr.io/kaniko-project/executor:v1.24.0 directory: ci/images/ansible depends_on: [registry-auth] environment: DOCKER_CONFIG: "${CI_WORKSPACE}/.docker" entrypoint: - /kaniko/executor - --context=. - --dockerfile=Containerfile - --destination=gitea.renner.uno/gitea_admin/ci-ansible:2026-08-23 - --destination=gitea.renner.uno/gitea_admin/ci-ansible:latest ``` Also moves off v1.23.2, picking up the go-git fix for **CVE-2025-21613** and containerd 1.7.18 -> 1.7.27. ## Not converted `ci-claude-refresh.yml` still builds with the debug image — its destination tag is a runtime value read from a file written by an earlier step, and the build is conditionally skipped. Its Containerfile gets the timestamp here; the pipeline step is left for #552. ## Needs a pipeline run A manual `build-ci-images` run confirms `${CI_WORKSPACE}` expands in `environment:` values and that the auth file written by `registry-auth` reaches the build steps. Refs #426, #552 🤖 Generated with [Claude Code](https://claude.com/claude-code)
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit 3a09e430c278a71aa01bb47bac8686c5d9eab99c.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR swaps all eight kaniko-project/executor:v1.23.2-debug build steps in .woodpecker/build-ci-images.yml for the plain v1.24.0 executor (no shell), moving credential setup into a new registry-auth step and passing kaniko flags via entrypoint: instead of a shell commands: block. The mechanical transformation is consistent across all eight steps and the PR description is candid about the three runtime assumptions it rests on. One of those assumptions — ${CI_WORKSPACE} expanding inside an environment: value — looks unlikely to work based on how the rest of this repo's pipelines handle that variable, which would break credential wiring for all eight builds.

🔴 Blocking

  • environment: DOCKER_CONFIG: "${CI_WORKSPACE}/.docker" likely does not expand — .woodpecker/build-ci-images.yml:57,74,91,115,137,159,181,203 (all eight build steps). Every other place in this repo that reads $CI_WORKSPACE does so from inside a shell commands: block, using the $$CI_WORKSPACE escape so Woodpecker's own template pass leaves a literal $CI_WORKSPACE for the container's shell to expand at runtime (e.g. .woodpecker/ci-claude-refresh.yml:86 mkdir -p "$$CI_WORKSPACE/.ci-refresh", and .woodpecker/pr-review.yml:85 NUM="$${CI_COMMIT_PULL_REQUEST:-}"). There is no precedent anywhere in .woodpecker/*.yml for ${CI_WORKSPACE}-style substitution working as a literal environment: map value, and there is no shell in the plain-executor steps to perform that expansion even if Woodpecker passed the string through untouched. If the k8s backend sets container env directly (as WOODPECKER_BACKEND: kubernetes in kubernetes/apps/woodpecker/agent.yaml:45 suggests), DOCKER_CONFIG would most likely arrive at kaniko as the literal string ${CI_WORKSPACE}/.docker, a path that doesn't exist, and every build step would fail to find registry credentials.
    This is exactly the risk the PR body calls out under "Needs a pipeline run before relying on it" (item 1), with a stated fallback (hardcode the auth path). Given the blast radius — all eight images fail to push, silently, until someone notices — I'd treat resolving this (or at least running the manual build-ci-images trigger and confirming success) as a precondition for merging, not a follow-up. The one check that settles it: trigger the pipeline manually and confirm the build-* steps actually authenticate against the registry rather than erroring on a missing/empty DOCKER_CONFIG path.

🟡 Suggestions

  • registry-auth has no when: gate — .woodpecker/build-ci-images.yml:32-42. Every build step is still individually path-filtered (e.g. build-ansible only runs on ci/images/ansible/** changes), but registry-auth itself runs unconditionally on every pipeline trigger that satisfies the top-level when:. Previously, credential setup was inlined per-step, so a push touching only one image's path never touched the registry secret for the other seven. Now the token is decoded and a config.json written to the shared workspace even on runs where zero build steps end up executing (e.g. a manual PIPELINE=="build-ci-images" run with no matching push paths, if that's ever a mixed case). Low impact — the step is cheap and the token was already available to all eight steps before — but if you want to preserve "only touch the secret when something will actually build," this step would need the union of all eight steps' path filters.
  • The credential blob (.docker/config.json, a reversible base64 of user:token) now sits in the shared CephFS RWX workspace for the duration of the run, readable by any step's container rather than living only inside the single container process that decoded it (as the inline-per-step version did). This is a reasonable trade — the plain kaniko steps have no shell to exfiltrate with, and the raw GITEA_TOKEN env var is now confined to one short-lived alpine container instead of eight — but worth naming since it's a real change in where the derived credential lives, not just how it's produced.

🟢 Nits

  • The literal dated tags (:2026-08-22) are consistent with the PR description and with today's date, and the tradeoff (manual reruns re-push the same dated tag) is well documented in both the top-of-file comment (.woodpecker/build-ci-images.yml:18-21) and the PR body — no action needed, just confirming it's not a stray/incorrect date.
  • --context=., --dockerfile=Containerfile, --destination=... are correctly collapsed into single --flag=value tokens throughout (e.g. line 60-63), which is required now that there's no shell to word-split --context . into two argv entries — good catch, consistent across all eight steps.

Tests

.woodpecker/ci-manifests-test.yml's woodpecker-lint step runs woodpecker-cli lint over every .woodpecker/*.yml on PRs touching .woodpecker/** (per its own path filter), so this PR's YAML will be checked for schema validity (e.g. that entrypoint: as a list and the new registry-auth step are structurally legal). That only proves the file compiles — it does not exercise any of the three runtime assumptions the PR body itself lists as unverified (${CI_WORKSPACE} expansion, cross-step visibility of .docker/config.json over the shared workspace, and the k8s backend honoring a list-form entrypoint). There's no way to unit-test those from the checkout; the only real test is the manual build-ci-images trigger the PR description already proposes. I'd want to see that run (or its output) before merging, given the first blocking point above.

Recommendation

Request changes — 1 blocking issue (verify, or fix, the ${CI_WORKSPACE} expansion in environment: before merging — a manual pipeline run is the fastest way to settle it).

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `3a09e430c278a71aa01bb47bac8686c5d9eab99c`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR swaps all eight `kaniko-project/executor:v1.23.2-debug` build steps in `.woodpecker/build-ci-images.yml` for the plain `v1.24.0` executor (no shell), moving credential setup into a new `registry-auth` step and passing kaniko flags via `entrypoint:` instead of a shell `commands:` block. The mechanical transformation is consistent across all eight steps and the PR description is candid about the three runtime assumptions it rests on. One of those assumptions — `${CI_WORKSPACE}` expanding inside an `environment:` value — looks unlikely to work based on how the rest of this repo's pipelines handle that variable, which would break credential wiring for all eight builds. ## 🔴 Blocking - **`environment: DOCKER_CONFIG: "${CI_WORKSPACE}/.docker"` likely does not expand** — `.woodpecker/build-ci-images.yml:57,74,91,115,137,159,181,203` (all eight build steps). Every other place in this repo that reads `$CI_WORKSPACE` does so from *inside a shell `commands:` block*, using the `$$CI_WORKSPACE` escape so Woodpecker's own template pass leaves a literal `$CI_WORKSPACE` for the container's shell to expand at runtime (e.g. `.woodpecker/ci-claude-refresh.yml:86` `mkdir -p "$$CI_WORKSPACE/.ci-refresh"`, and `.woodpecker/pr-review.yml:85` `NUM="$${CI_COMMIT_PULL_REQUEST:-}"`). There is no precedent anywhere in `.woodpecker/*.yml` for `${CI_WORKSPACE}`-style substitution working as a literal `environment:` map *value*, and there is no shell in the plain-executor steps to perform that expansion even if Woodpecker passed the string through untouched. If the k8s backend sets container env directly (as `WOODPECKER_BACKEND: kubernetes` in `kubernetes/apps/woodpecker/agent.yaml:45` suggests), `DOCKER_CONFIG` would most likely arrive at kaniko as the literal string `${CI_WORKSPACE}/.docker`, a path that doesn't exist, and every build step would fail to find registry credentials. This is exactly the risk the PR body calls out under "Needs a pipeline run before relying on it" (item 1), with a stated fallback (hardcode the auth path). Given the blast radius — all eight images fail to push, silently, until someone notices — I'd treat resolving this (or at least running the manual `build-ci-images` trigger and confirming success) as a precondition for merging, not a follow-up. The one check that settles it: trigger the pipeline manually and confirm the `build-*` steps actually authenticate against the registry rather than erroring on a missing/empty `DOCKER_CONFIG` path. ## 🟡 Suggestions - **`registry-auth` has no `when:` gate** — `.woodpecker/build-ci-images.yml:32-42`. Every build step is still individually path-filtered (e.g. `build-ansible` only runs on `ci/images/ansible/**` changes), but `registry-auth` itself runs unconditionally on every pipeline trigger that satisfies the top-level `when:`. Previously, credential setup was inlined per-step, so a push touching only one image's path never touched the registry secret for the other seven. Now the token is decoded and a config.json written to the shared workspace even on runs where zero build steps end up executing (e.g. a manual `PIPELINE=="build-ci-images"` run with no matching push paths, if that's ever a mixed case). Low impact — the step is cheap and the token was already available to all eight steps before — but if you want to preserve "only touch the secret when something will actually build," this step would need the union of all eight steps' path filters. - The credential blob (`.docker/config.json`, a reversible base64 of `user:token`) now sits in the shared CephFS RWX workspace for the duration of the run, readable by any step's container rather than living only inside the single container process that decoded it (as the inline-per-step version did). This is a reasonable trade — the plain kaniko steps have no shell to exfiltrate with, and the raw `GITEA_TOKEN` env var is now confined to one short-lived alpine container instead of eight — but worth naming since it's a real change in where the derived credential lives, not just how it's produced. ## 🟢 Nits - The literal dated tags (`:2026-08-22`) are consistent with the PR description and with today's date, and the tradeoff (manual reruns re-push the same dated tag) is well documented in both the top-of-file comment (`.woodpecker/build-ci-images.yml:18-21`) and the PR body — no action needed, just confirming it's not a stray/incorrect date. - `--context=.`, `--dockerfile=Containerfile`, `--destination=...` are correctly collapsed into single `--flag=value` tokens throughout (e.g. line 60-63), which is required now that there's no shell to word-split `--context .` into two argv entries — good catch, consistent across all eight steps. ## Tests `.woodpecker/ci-manifests-test.yml`'s `woodpecker-lint` step runs `woodpecker-cli lint` over every `.woodpecker/*.yml` on PRs touching `.woodpecker/**` (per its own path filter), so this PR's YAML will be checked for schema validity (e.g. that `entrypoint:` as a list and the new `registry-auth` step are structurally legal). That only proves the file compiles — it does not exercise any of the three runtime assumptions the PR body itself lists as unverified (`${CI_WORKSPACE}` expansion, cross-step visibility of `.docker/config.json` over the shared workspace, and the k8s backend honoring a list-form `entrypoint`). There's no way to unit-test those from the checkout; the only real test is the manual `build-ci-images` trigger the PR description already proposes. I'd want to see that run (or its output) before merging, given the first blocking point above. ## Recommendation Request changes — 1 blocking issue (verify, or fix, the `${CI_WORKSPACE}` expansion in `environment:` before merging — a manual pipeline run is the fastest way to settle it). <!-- pr-review:sha=3a09e430c278a71aa01bb47bac8686c5d9eab99c -->
claude-bot force-pushed kaniko-no-debug-image from 3a09e430c2 to bbbc42c79f 2026-08-23 18:52:14 +00:00 Compare
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit bbbc42c79fa80daae1ca2d7fe774c457830220f3.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

Refactors .woodpecker/build-ci-images.yml to run all eight kaniko build steps on the plain (shell-less) gcr.io/kaniko-project/executor:v1.24.0 image instead of the -debug variant, driving each build via entrypoint: instead of a commands: shell script. Registry auth is centralized into one new registry-auth step that writes .docker/config.json into the shared workspace once, and downstream steps point kaniko at it via DOCKER_CONFIG: "${CI_WORKSPACE}/.docker". I did not find a concrete defect in the diff — the refactor is a faithful, well-factored translation of the existing eight near-identical blocks, and it reduces the number of containers that see GITEA_TOKEN from eight to one. The one real open question — whether Woodpecker's ${VAR} substitution actually applies inside entrypoint:/environment: values — is already flagged by the PR author as unverified, and I could not settle it from the checkout either (see Suggestions).

🟡 Suggestions

  • The core mechanism of this PR is unverified, and lint won't catch a failure. All eight destinations and DOCKER_CONFIG depend on ${CI_COMMIT_SHA:0:8} and ${CI_WORKSPACE} being substituted inside entrypoint: (.woodpecker/build-ci-images.yml:63 etc.) and environment: (:58 etc.) at pipeline-compile time. I confirmed this exact single-$-brace substitution syntax has no precedent elsewhere in .woodpecker/*.yml — every other use I found (security-scan.yml:79, pr-review.yml:85,91,94, ci-claude-refresh.yml) uses $$CI_WORKSPACE/$${CI_COMMIT_SHA:-manual} inside commands:, i.e. escaped so the container's own shell resolves them at runtime, not Woodpecker's compiler. That's a different mechanism than what this PR relies on for entrypoint:. .woodpecker/ci-manifests-test.yml's woodpecker-lint step (which does run on this PR via its .woodpecker/** path filter) only proves the file compiles against the schema — the same file's own comments note lint "exits 0 on mere warnings," and templating correctness isn't part of what lint checks at all. So a green lint here provides no evidence the substitution resolves correctly. The good news: if it doesn't resolve, the likely failure mode is loud, not silent — a literal ${CI_COMMIT_SHA:0:8} tag contains characters ($, {, }, and a second :) that Docker's reference parser rejects, so kaniko would fail outright rather than push a mistagged or uncredentialed image. Still, worth confirming with the manual build-ci-images run the PR body already says is needed, before treating this as done.
  • Related to the above: registry-auth (:33) has no directory:, so it writes .docker/config.json relative to the workspace root, and downstream steps read it via the absolute ${CI_WORKSPACE}/.docker regardless of their own directory:. That part is consistent and correct if the substitution assumption above holds — just noting it's coupled to the same open question rather than a second independent risk.

Tests

No pipeline run is attached to this PR (0 comments on the PR, nothing indicating build-ci-images was triggered manually), and this class of change — Woodpecker YAML — isn't unit-testable from the checkout; ci-manifests-test.yml only lints/compiles it. The PR body itself is explicit that a manual build-ci-images run is needed to settle the three assumptions it lists (entrypoint substitution, ${CI_WORKSPACE} expansion in environment:, and cross-step visibility of the auth file via the shared workspace). I'd treat that manual run as the actual test for this change and would want to see it green (or at least attempted) before relying on this pipeline for a real image push.

Recommendation

Looks reasonable — no blocking issues; comments above are optional, but I'd strongly encourage running build-ci-images manually before merging, since the whole PR hinges on unverified substitution behavior that a green lint won't confirm.

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `bbbc42c79fa80daae1ca2d7fe774c457830220f3`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary Refactors `.woodpecker/build-ci-images.yml` to run all eight kaniko build steps on the plain (shell-less) `gcr.io/kaniko-project/executor:v1.24.0` image instead of the `-debug` variant, driving each build via `entrypoint:` instead of a `commands:` shell script. Registry auth is centralized into one new `registry-auth` step that writes `.docker/config.json` into the shared workspace once, and downstream steps point kaniko at it via `DOCKER_CONFIG: "${CI_WORKSPACE}/.docker"`. I did not find a concrete defect in the diff — the refactor is a faithful, well-factored translation of the existing eight near-identical blocks, and it reduces the number of containers that see `GITEA_TOKEN` from eight to one. The one real open question — whether Woodpecker's `${VAR}` substitution actually applies inside `entrypoint:`/`environment:` values — is already flagged by the PR author as unverified, and I could not settle it from the checkout either (see Suggestions). ## 🟡 Suggestions - **The core mechanism of this PR is unverified, and lint won't catch a failure.** All eight destinations and `DOCKER_CONFIG` depend on `${CI_COMMIT_SHA:0:8}` and `${CI_WORKSPACE}` being substituted inside `entrypoint:` (`.woodpecker/build-ci-images.yml:63` etc.) and `environment:` (`:58` etc.) at pipeline-compile time. I confirmed this exact single-`$`-brace substitution syntax has no precedent elsewhere in `.woodpecker/*.yml` — every other use I found (`security-scan.yml:79`, `pr-review.yml:85,91,94`, `ci-claude-refresh.yml`) uses `$$CI_WORKSPACE`/`$${CI_COMMIT_SHA:-manual}` *inside `commands:`*, i.e. escaped so the container's own shell resolves them at runtime, not Woodpecker's compiler. That's a different mechanism than what this PR relies on for `entrypoint:`. `.woodpecker/ci-manifests-test.yml`'s `woodpecker-lint` step (which does run on this PR via its `.woodpecker/**` path filter) only proves the file compiles against the schema — the same file's own comments note lint "exits 0 on mere warnings," and templating correctness isn't part of what lint checks at all. So a green lint here provides no evidence the substitution resolves correctly. The good news: if it doesn't resolve, the likely failure mode is loud, not silent — a literal `${CI_COMMIT_SHA:0:8}` tag contains characters (`$`, `{`, `}`, and a second `:`) that Docker's reference parser rejects, so kaniko would fail outright rather than push a mistagged or uncredentialed image. Still, worth confirming with the manual `build-ci-images` run the PR body already says is needed, before treating this as done. - Related to the above: `registry-auth` (`:33`) has no `directory:`, so it writes `.docker/config.json` relative to the workspace root, and downstream steps read it via the absolute `${CI_WORKSPACE}/.docker` regardless of their own `directory:`. That part is consistent and correct *if* the substitution assumption above holds — just noting it's coupled to the same open question rather than a second independent risk. ## Tests No pipeline run is attached to this PR (0 comments on the PR, nothing indicating `build-ci-images` was triggered manually), and this class of change — Woodpecker YAML — isn't unit-testable from the checkout; `ci-manifests-test.yml` only lints/compiles it. The PR body itself is explicit that a manual `build-ci-images` run is needed to settle the three assumptions it lists (entrypoint substitution, `${CI_WORKSPACE}` expansion in `environment:`, and cross-step visibility of the auth file via the shared workspace). I'd treat that manual run as the actual test for this change and would want to see it green (or at least attempted) before relying on this pipeline for a real image push. ## Recommendation Looks reasonable — no blocking issues; comments above are optional, but I'd strongly encourage running `build-ci-images` manually before merging, since the whole PR hinges on unverified substitution behavior that a green lint won't confirm. <!-- pr-review:sha=bbbc42c79fa80daae1ca2d7fe774c457830220f3 -->
claude-bot added 1 commit 2026-08-23 18:57:23 +00:00
build(ci): record the build timestamp in the image instead of the tag
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/push/build-ci-images Pipeline failed
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was canceled
80c3051dc7
The date was previously computed by `date` in the pipeline step and used
as the image tag, which is why the build step needed a shell and hence
the kaniko debug image.

It does not need to cross between CI steps at all. The base images have
a shell, so a RUN in the Containerfile writes the timestamp during the
build itself — kaniko executes the layer and the value lands in the
image. Nothing has to be propagated, and the build step stays
shell-less.

This also separates two things the tag was conflating: the tag says
which source revision was built, /etc/image-build-date says when.

Placed last so it invalidates only its own layer. secret-rotator writes
it in the rust build stage and copies it across, since its final image
is FROM scratch and has no shell.

Verified against ci/images/alpine-jq:

  $ docker run --rm localtest:jq cat /etc/image-build-date
  2026-08-23T18:56:51Z

Note this makes builds non-reproducible byte-for-byte, which a675bf6c
deliberately arranged for ci-claude. ci-claude is built by
ci-claude-refresh.yml and is not touched here.
claude-bot added 1 commit 2026-08-23 18:59:36 +00:00
build(ci): record the build timestamp in ci-claude too
ci/woodpecker/push/build-ci-images Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
aff01d5a32
Completes the pattern across every Containerfile in the repo. ci-claude
was the last one without it.

It was skipped initially over the reproducibility work in a675bf6c, on a
misreading. That commit did not make the whole image reproducible — it
made the npm install layer byte-stable so a rebuild of the same version
dedups in the registry instead of orphaning ~260 MB (#336, #351).

A timestamp added as its own final layer leaves the npm layer untouched,
so that dedup still holds. The cost is a few dozen bytes per build, not
260 MB.
claude-bot changed title from fix(ci): build with the plain kaniko executor instead of the debug image to fix(ci): drop the kaniko debug image; record build timestamps in the images 2026-08-23 18:59:55 +00:00
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit aff01d5a3224493039ba586e449a6c058ecbb479.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR drops the kaniko -debug image from all eight build-ci-images.yml steps (switching to the plain, shell-less v1.24.0 executor with a shared registry-auth step + DOCKER_CONFIG/entrypoint wiring, and CI_COMMIT_SHA-based tags instead of a shell-computed date), and adds a RUN date -u ... > /etc/image-build-date build-timestamp layer to all nine Containerfiles. The pipeline rework is sound and matches how Woodpecker/kaniko's no-shell executor is meant to be driven. There is one concrete build-breaking bug in the Containerfile changes.

🔴 Blocking

  • ci/images/heating-can/Containerfile:119-127: the new RUN date -u +%Y-%m-%dT%H:%M:%SZ > /etc/image-build-date is appended after USER open3e (line 119). Every other instruction after a USER directive runs as that user, and /etc in the python:3.13-slim base is root:root (mode 755) — not writable by the unprivileged open3e account created at line 111. This will fail the build with a permission-denied error on the redirect into /etc, breaking build-heating-can in CI. Every other Containerfile in this PR appends the timestamp line as the last instruction of a still-root final stage (or, for secret-rotator, writes it in the root build stage and COPYs it across specifically because the final stage has no shell) — heating-can is the one case that needs the same treatment: move the RUN date ... line above USER open3e (e.g. right after the self-test RUN block at line 108), or USER root before it and switch back after.

🟡 Suggestions

  • .woodpecker/build-ci-images.yml: the new registry-auth step and the ${CI_WORKSPACE}/.docker / ${CI_COMMIT_SHA:0:8} substitutions rely on Woodpecker's own (non-shell) variable interpolation reaching environment: and entrypoint: list values, since the plain kaniko executor has no shell to expand ${...} itself. The PR body says this was confirmed with a manual build-ci-images run — worth double-checking that run actually covered a step whose path: filter matched (so it exercised a real build, not just registry-auth) before merging, since a silent no-op interpolation would show up only as a kaniko auth/tag failure at build time, not a YAML lint failure.
  • ci/images/*/Containerfile: the build-timestamp comment block is duplicated verbatim eight times (word-for-word in ansible, alpine-jq, alpine-openssl, cni-plugins, heating-can, k8s, tofu, and a slight variant in claude/secret-rotator). Given the repo otherwise tends to keep each Containerfile self-contained with heavy inline rationale, this is probably intentional/consistent with existing style rather than something to fix — flagging only since it's a lot of copy-paste for future edits to keep in sync.

Tests

No automated test covers the build-breaking heating-can issue above: ci-manifests-test.yml lints .woodpecker/** YAML (which would catch the #443-style YAML mistakes it was built for) but does not build Containerfiles, and there's no CI job that does a real (non-registry) docker build/kaniko dry-run of the nine images. The heating-can Containerfile itself has a build-time self-test (lines 101-108) that would have been a natural place to also exercise the final layer, but it runs before the USER switch and before this new line, so it wouldn't have caught this either. Given the repo's own ci-manifests-test.yml comment explicitly calls out wanting to catch build-time breakage before merge, a lightweight docker build smoke test (even just --target per stage, no push) for the nine Containerfiles would have caught this specific regression.

Recommendation

Request changes — 1 blocking issue.

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `aff01d5a3224493039ba586e449a6c058ecbb479`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR drops the kaniko `-debug` image from all eight `build-ci-images.yml` steps (switching to the plain, shell-less `v1.24.0` executor with a shared `registry-auth` step + `DOCKER_CONFIG`/`entrypoint` wiring, and `CI_COMMIT_SHA`-based tags instead of a shell-computed date), and adds a `RUN date -u ... > /etc/image-build-date` build-timestamp layer to all nine Containerfiles. The pipeline rework is sound and matches how Woodpecker/kaniko's no-shell executor is meant to be driven. There is one concrete build-breaking bug in the Containerfile changes. ## 🔴 Blocking - `ci/images/heating-can/Containerfile:119-127`: the new `RUN date -u +%Y-%m-%dT%H:%M:%SZ > /etc/image-build-date` is appended *after* `USER open3e` (line 119). Every other instruction after a `USER` directive runs as that user, and `/etc` in the `python:3.13-slim` base is `root:root` (mode 755) — not writable by the unprivileged `open3e` account created at line 111. This will fail the build with a permission-denied error on the redirect into `/etc`, breaking `build-heating-can` in CI. Every other Containerfile in this PR appends the timestamp line as the last instruction of a still-root final stage (or, for `secret-rotator`, writes it in the root build stage and `COPY`s it across specifically because the final stage has no shell) — `heating-can` is the one case that needs the same treatment: move the `RUN date ...` line above `USER open3e` (e.g. right after the self-test `RUN` block at line 108), or `USER root` before it and switch back after. ## 🟡 Suggestions - `.woodpecker/build-ci-images.yml`: the new `registry-auth` step and the `${CI_WORKSPACE}/.docker` / `${CI_COMMIT_SHA:0:8}` substitutions rely on Woodpecker's own (non-shell) variable interpolation reaching `environment:` and `entrypoint:` list values, since the plain kaniko executor has no shell to expand `${...}` itself. The PR body says this was confirmed with a manual `build-ci-images` run — worth double-checking that run actually covered a step whose `path:` filter matched (so it exercised a real build, not just `registry-auth`) before merging, since a silent no-op interpolation would show up only as a kaniko auth/tag failure at build time, not a YAML lint failure. - `ci/images/*/Containerfile`: the build-timestamp comment block is duplicated verbatim eight times (word-for-word in `ansible`, `alpine-jq`, `alpine-openssl`, `cni-plugins`, `heating-can`, `k8s`, `tofu`, and a slight variant in `claude`/`secret-rotator`). Given the repo otherwise tends to keep each Containerfile self-contained with heavy inline rationale, this is probably intentional/consistent with existing style rather than something to fix — flagging only since it's a lot of copy-paste for future edits to keep in sync. ## Tests No automated test covers the build-breaking `heating-can` issue above: `ci-manifests-test.yml` lints `.woodpecker/**` YAML (which would catch the `#443`-style YAML mistakes it was built for) but does not build Containerfiles, and there's no CI job that does a real (non-registry) `docker build`/`kaniko` dry-run of the nine images. The heating-can Containerfile itself has a build-time self-test (lines 101-108) that would have been a natural place to also exercise the final layer, but it runs before the `USER` switch and before this new line, so it wouldn't have caught this either. Given the repo's own `ci-manifests-test.yml` comment explicitly calls out wanting to catch build-time breakage before merge, a lightweight `docker build` smoke test (even just `--target` per stage, no push) for the nine Containerfiles would have caught this specific regression. ## Recommendation Request changes — 1 blocking issue. <!-- pr-review:sha=aff01d5a3224493039ba586e449a6c058ecbb479 -->
claude-bot added 1 commit 2026-08-23 19:15:00 +00:00
fix(ci): tag images with a dated literal, not the commit SHA
ci/woodpecker/push/build-ci-images Pipeline failed
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
803c741362
The SHA tag was a workaround for not being able to compute a date in a
shell-less build step. That problem is gone — the build timestamp now
lives in the image at /etc/image-build-date — so the tag no longer has
to carry it.

A SHA reads as noise at the point of use and breaks the existing
convention every consumer pin follows (ci-k8s:2026-08-10,
ci-ansible:2026-04-29, alpine-jq:2026-07-30). Dated literals keep the
pins uniform and are bumped in the commit that changes an image, which
is the same trigger the `when:` path filters already use.
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit 803c741362514599a1e52ec4646cb6e8c5ec7eea.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR does two things to build-ci-images.yml and its nine Containerfiles: (1) drops the -debug kaniko executor variant in favor of a plain entrypoint-driven build with a shared registry-auth step writing .docker/config.json once, and (2) replaces the pipeline-computed $(date +%Y-%m-%d) tag with a literal dated tag plus a RUN date -u ... > /etc/image-build-date baked into each image. I traced the new auth flow, the kaniko DOCKER_CONFIG usage, the entrypoint:-only step definitions, and every changed Containerfile against the rest of the repo (in particular the #351 npm-layer-dedup logic in ci/images/claude/Containerfile and the scratch-image copy in secret-rotator/Containerfile), and did not find a concrete defect. The design is coherent and well explained in the pipeline header comment.

No blocking issues found.

🟡 Suggestions

  • .woodpecker/build-ci-images.yml:58 (and the other 7 DOCKER_CONFIG lines) — these rely on Woodpecker substituting ${CI_WORKSPACE} directly inside an environment: value, with no shell in the kaniko container to expand it itself. That differs from the repo's only other use of this variable, .woodpecker/ci-claude-refresh.yml:86 etc., which always double-escapes it ($$CI_WORKSPACE) so a runtime shell resolves it. The double-escaping there is explained by the surrounding script needing all its $$foo shell locals protected from Woodpecker's front-end templating, not by CI_WORKSPACE itself being unsubstitutable at compile time — so a single ${CI_WORKSPACE} in environment: should resolve correctly, and the kaniko DOCKER_CONFIG override + registry-auth-writes-once pattern is a standard shell-less kaniko idiom. I couldn't execute the pipeline to confirm this directly, though. The PR body says a manual build-ci-images run already validated this reaches all 8 build steps — worth making sure that run (or the real CI run once merged) is actually green before relying on this everywhere, since if the substitution silently no-ops, every build step's registry push fails the same way.
  • The literal destination tags (e.g. ci-ansible:2026-08-23 at line 63) are correct for today, but since none of the eight images actually changed in this PR besides gaining the timestamp RUN, all eight get a fresh tag bump in the same commit. That matches the stated convention ("bumped in the commit that changes an image"), just flagging it so a reviewer isn't surprised the diff touches every tag rather than none.

Tests

No automated tests are feasible for a Woodpecker pipeline YAML change; correctness here can only be established by an actual pipeline run. The PR body reports one manual run was used to validate the CI_WORKSPACE/DOCKER_CONFIG plumbing — that verification, or the real CI run on this PR, should be confirmed green before merge given the blast radius (all 8 image builds share the same registry-auth step).

Recommendation

Looks reasonable — no blocking issues; comments above are optional (though I'd want to see the pipeline actually go green before merging, given the scope of the change).

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `803c741362514599a1e52ec4646cb6e8c5ec7eea`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR does two things to `build-ci-images.yml` and its nine Containerfiles: (1) drops the `-debug` kaniko executor variant in favor of a plain `entrypoint`-driven build with a shared `registry-auth` step writing `.docker/config.json` once, and (2) replaces the pipeline-computed `$(date +%Y-%m-%d)` tag with a literal dated tag plus a `RUN date -u ... > /etc/image-build-date` baked into each image. I traced the new auth flow, the kaniko `DOCKER_CONFIG` usage, the `entrypoint:`-only step definitions, and every changed Containerfile against the rest of the repo (in particular the `#351` npm-layer-dedup logic in `ci/images/claude/Containerfile` and the scratch-image copy in `secret-rotator/Containerfile`), and did not find a concrete defect. The design is coherent and well explained in the pipeline header comment. No blocking issues found. ## 🟡 Suggestions - **`.woodpecker/build-ci-images.yml:58` (and the other 7 `DOCKER_CONFIG` lines)** — these rely on Woodpecker substituting `${CI_WORKSPACE}` directly inside an `environment:` value, with no shell in the kaniko container to expand it itself. That differs from the repo's only other use of this variable, `.woodpecker/ci-claude-refresh.yml:86` etc., which always double-escapes it (`$$CI_WORKSPACE`) so a runtime shell resolves it. The double-escaping there is explained by the surrounding script needing *all* its `$$foo` shell locals protected from Woodpecker's front-end templating, not by `CI_WORKSPACE` itself being unsubstitutable at compile time — so a single `${CI_WORKSPACE}` in `environment:` should resolve correctly, and the kaniko `DOCKER_CONFIG` override + registry-auth-writes-once pattern is a standard shell-less kaniko idiom. I couldn't execute the pipeline to confirm this directly, though. The PR body says a manual `build-ci-images` run already validated this reaches all 8 build steps — worth making sure that run (or the real CI run once merged) is actually green before relying on this everywhere, since if the substitution silently no-ops, every build step's registry push fails the same way. - The literal destination tags (e.g. `ci-ansible:2026-08-23` at line 63) are correct for today, but since none of the eight images actually changed in this PR besides gaining the timestamp `RUN`, all eight get a fresh tag bump in the same commit. That matches the stated convention ("bumped in the commit that changes an image"), just flagging it so a reviewer isn't surprised the diff touches every tag rather than none. ## Tests No automated tests are feasible for a Woodpecker pipeline YAML change; correctness here can only be established by an actual pipeline run. The PR body reports one manual run was used to validate the `CI_WORKSPACE`/`DOCKER_CONFIG` plumbing — that verification, or the real CI run on this PR, should be confirmed green before merge given the blast radius (all 8 image builds share the same `registry-auth` step). ## Recommendation Looks reasonable — no blocking issues; comments above are optional (though I'd want to see the pipeline actually go green before merging, given the scope of the change). <!-- pr-review:sha=803c741362514599a1e52ec4646cb6e8c5ec7eea -->
k.renner closed this pull request 2026-08-25 05:16:11 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.