build: ship the standard and warmer variants from scratch

Dockerfile.standard and Dockerfile.warmer now end in a FROM scratch runtime stage. UBI9 stays the builder: it compiles both binaries and supplies the CA bundle the runtime carries. kaniko unpacks the base image of the build it is running over its own root and does not clear it first, so any file the runtime ships that the base does not shadow stays visible to that build's RUN steps.

Each of the four images holds three paths: the binary, /kaniko/ssl/certs/ca-certificates.crt, and the empty /kaniko/.docker. Upstream's /etc/nsswitch.conf is deliberately absent: Go's resolver already orders files before dns without it, and it is not on kaniko's snapshot ignore list, so it would land in customers' layers. PATH is absolute, /usr/local/bin:/kaniko, org.opencontainers.image.base.name is scratch, and a new io.gitlab.public-sector.kaniko.builder label names the UBI image that compiled the binary. A second commit passes upstream's own version ldflag in all three Dockerfiles, so executor version prints v1.25.19 instead of unset on all six.

The debug variants keep their UBI9-minimal runtime and their shell, which a GitLab CI job image needs. They move to a static busybox next; only their go build line changes here.

nonubi-build-smoke is the permanent control: every other smoke job builds a UBI base on a UBI runtime, where the two coincide. It builds alpine:3.24 and busybox:stable-musl through the standard image, reads the /usr/bin entry count from the base image and passes it in, and fails on a count that disagrees, on an ls that answers as GNU coreutils, or on a Red Hat release file.

Proof, all local with podman on linux/amd64:

  1. All four scratch variants and debug build; executor version prints v1.25.19 on standard, fips and debug, where the published image prints unset.
  2. The smoke arm, run verbatim against both runtimes. On the published image the alpine build reports 225 entries in /usr/bin against alpine's own 142 and the busybox build 138 against 1, and both fail; on the scratch image both report the base's own count and pass.
  3. trivy image finds no operating system and no OS packages on all four, with the Go binary the only result. The published standard image reports Red Hat 9.8, 22 packages, 8 OS findings.
  4. Warmer round trip, stdlib and FIPS pairs: the warmer fills a shared cache volume and the matching executor logs Found sha256:79ff19e9... in local cache.
  5. An alpine build through the standard image resolves docker.io by name over TLS with no /etc/nsswitch.conf. The FIPS executor built the same context under GODEBUG=fips140=only with no X25519 refusal, and both FIPS binaries are statically linked x86-64.
  6. /bin/sh is absent: the runtime refuses to start one.
  7. shellcheck, hadolint, check-versions.sh, glab ci lint and the Renovate config validator pass; vale adds no finding.
  8. scripts/verify-reproducibility.sh standard builds the builder stage twice with --no-cache; both hash 72fa9b67..., as does the binary in the shipped image. It fails against the published image, as it must: that one predates both the ldflag and the x/crypto override.

Standard goes from 76.1 MB to 52.5 MB and the warmer image is 21.8 MB. UBI_MICRO_IMAGE is gone; UBI_IMAGE and UBI_MINIMAL_IMAGE still redirect every base an airgap consumer pulls, and Renovate tracked it through its Dockerfile manager, so renovate.json needs no change.

The README hero now reads "Upstream kaniko, built on UBI9, for GitLab Runner." UBI9 builds every variant, and that is the only claim the tagline makes.

Expect all six images to rebuild.

Merge request reports

Loading
Loading