Glidepath · Review · Image builds

Kaniko to BuildKit

How Glidepath built images with kaniko, what rootless BuildKit would change, and a phased plan to switch.

Review October 17, 2026Status switched Oct 17, 2026 (ADR-0026); kaniko removedScope build-image, the catalog chart, Airframe scaffolds, docsBasis glidepath main, the live dev cluster, the Tekton Results performance review

Since then. The switch happened the same day, faster than the five phases planned: ADR-0026 was accepted on October 17, 2026, build-image now runs one rootless BuildKit step for every platform, kaniko and crane are gone, the Go scaffold cross-compiles from $BUILDPLATFORM, and every -cicd namespace enforces Pod Security baseline. This page is the review as written.

Switch · 5 phases · canary first

Kaniko is frozen, slow on amd64, and its security argument doesn't hold live.

  • Upstream is archived. Google archived kaniko in June 2025. The pinned v1.24.0-debug is the last Google release, so there are no more CVE fixes. Chainguard's fork publishes no public images.
  • It causes the worst build cost. Kaniko ignores FROM --platform=$BUILDPLATFORM, so every RUN step of the amd64 build runs under QEMU on the arm64 node. That breaks Go (known gap #37) and costs Backstage 11 of its 25 minutes.
  • The "PSS restricted" guarantee isn't real. On the dev cluster the app-*-cicd build namespaces carry no pod-security enforce label, Tekton runs with set-security-context=false, and kaniko runs as root in the container. ADR-0010 and security.md need correcting whichever builder you keep.

Recommendation. Move to rootless BuildKit, daemonless, one instance per TaskRun (no shared daemon, so tenant isolation holds). Roll it out inside the existing build-image Task behind a per-app build.builder flag, canary on four apps, then flip the default and remove kaniko and crane.

476sbuild stage p50, 1,596 PipelineRuns (Sep 17 to Oct 9)
664samd64 kaniko step p90 under QEMU (p50 106s)
11 / 25Backstage minutes spent in build-image
~11 minSpring scaffold amd64 step, vs 5 min native arm64
0 / 31app build namespaces with a pod-security enforce label

How build-image works today

Each kaniko step uses a busybox gate script to skip itself when its arch isn't requested, because Tekton has no per-step when:. Each step also overrides SSL_CERT_FILE, since Tekton injects a CA directory that doesn't exist in the kaniko image. Labels and HANGAR_VERSION/HANGAR_GIT_REVISION build-args are set on both steps. The cache is kaniko's registry layer cache (--cache=true, 336h).

Findings

K-1

The builder is unmaintained

high

GoogleContainerTools/kaniko was archived in June 2025, and v1.24.0 was the last release. The pin in build-image.yaml carries no CVE fixes from here on. Chainguard's fork is maintained for security only and its images are for Chainguard customers. The osscontainertools fork is community-run.

Evidenceimage: gcr.io/kaniko-project/executor:v1.24.0-debug on both build steps; the step comment already notes no newer tag exists.

K-2

Cross-arch builds are fully emulated

high

With --custom-platform set, kaniko runs every stage as the target arch and ignores a per-stage --platform. Go crashes under QEMU (two separate QEMU bugs, one with no mitigation). Java and Node builds inside a Containerfile run several times slower. The current workaround is to compile in build.sh and keep a thin Containerfile, which the Spring scaffold doesn't do yet.

Evidenceknown-gaps #37 (smoke-ecs, boarding-api); pipeline-performance.md "amd64 builds under QEMU"; flight-api Spring build 17 min with the two steps one after the other.

K-3

The documented privilege model is wrong on the live cluster

medium

ADR-0010, rootless-builds.md and security.md say builds run under PSS restricted. Live, only autopilot-runs and autopilot-system enforce restricted. Kaniko also needs root inside its container, so it could never pass restricted. Today the build pods are root containers with no admission control over their security context.

Evidencekubectl get ns on the dev cluster: 31 app-* namespaces without pod-security.kubernetes.io/enforce; feature-flags set-security-context=false; no Kyverno policy covering pod security for builds.

K-4

Multi-arch needs a second tool and leaves extra tags

low

Kaniko only produces single-platform images, so crane assembles the index and the -arm64/-amd64 tags stay published. It works, but it is one more image pinned by digest to a floating :debug tag, plus a gate script per arch.

Pros and cons

AreaKaniko (today)Rootless BuildKit
Maintenancearchived
v1.24.0 is the last Google release. Forks are security-only or community-run.
active
Maintained by moby/Docker; the engine behind buildx.
Cross-archall emulated
Every stage runs as the target arch.
native compile
--platform=$BUILDPLATFORM is honoured, so Go and Java cross-compile without QEMU. Only steps that truly run on the target arch are emulated.
Multi-arch outputTwo steps one after the other, then crane.One run builds both platforms in parallel and pushes the index.
CacheRegistry layer cache.Registry cache in mode=max, RUN --mount=type=cache, stages run in parallel.
Build featuresNo RUN --mount=type=secret or ssh.Full Dockerfile frontend.
Privilegeroot
Passes baseline, fails restricted.
UID 1000, unconfined
Needs seccomp and AppArmor Unconfined (or Localhost profiles), so it fails baseline as shipped. hostUsers: false may close the gap; the spike tests it.
Tekton fitproven here
Every workaround is documented.
New step image. Re-check the CA path quirk; take the digest from --metadata-file.
Supply chainChains reads IMAGE_URL and IMAGE_DIGEST.Same results. buildctl adds no attestation manifests unless asked, so Conforma and verify-image-provenance are unaffected.

The main con: on paper BuildKit's rootless default is less restricted than kaniko, because seccomp and AppArmor are unconfined. In practice you're trading a non-root user with no syscall filter for root with no filters. If you later enforce baseline on the cicd namespaces, BuildKit needs Localhost profiles or user namespaces.

Alternatives considered

Rootless buildah

ADR-0010's documented fallback. It needs the same privilege relaxations, and multi-arch through buildah manifest is clunkier. No reason to prefer it.

Shared buildkitd Deployment

Gives a warm local cache, but one daemon builds for every tenant, which breaks the ADR-0011 two-namespace isolation. Rejected.

Native amd64 build node

Removes QEMU for any builder, but there's no amd64 hardware for the dev cluster. It complements the switch; it doesn't replace it.

Stay on kaniko, fix around it

Derive platforms from the cluster registry arch and move every scaffold to build.sh. That helps, but the builder stays archived and Go multi-arch stays broken.

Migration plan

P0

Spike on the dev cluster, no shared changes

  • Scratch TaskRun in a cicd namespace with moby/buildkit:<pin>-rootless and buildctl-daemonless.sh.
  • Prove: push with registry-credentials; TLS under Tekton's injected SSL_CERT_DIR; one-run multi-arch index; digest from --metadata-file.
  • Time boarding-api (Go) and flight-api (Spring) cross-compiles against the kaniko numbers; measure a warm rebuild from the registry cache.
  • Confirm no extra attestation manifests, and record which pod-security level each variant passes (Unconfined vs hostUsers: false).
  • Correct ADR-0010, rootless-builds.md and security.md now, whatever the outcome.

Exit Go/no-go for each item, with the exact working step YAML.

P1

Both builders in one Task

  • Add a build-and-push-buildkit step to build-image, using the same exit 0 gate as the arch steps.
  • New build.builder: kaniko | buildkit in cicd.schema.json, default kaniko. The schema change means a toolbox rebuild.
  • The Task name and results stay the same, so Chains, Conforma and Tower are unaffected.
  • Cache at <repo>:buildcache, mode=max, per app and per tenant.

Exit A kaniko app and a buildkit app both pass build, sign and provenance checks.

P2

Canary on four apps

  • boarding-api (Go, gap #37), flight-api (Spring), backstage (amd64-only, the biggest build), and one Node app (thin Containerfile).
  • Compare durations through Tekton Results.
  • Airframe: move the GoApplication and SpringBoot scaffold Containerfiles to FROM --platform=$BUILDPLATFORM with GOARCH=$TARGETARCH. Bump all three airframe chart pin locations.

Exit Every canary is green with a measured improvement on the amd64 path.

P3

Flip the default

  • Make buildkit the default. ADR-0026 supersedes ADR-0010 with the real privilege model.
  • Update rootless-builds.md, security.md, pipeline-performance.md, cicd-yaml-reference.md and the build-image header; close known-gap #37.
  • Fix the "kaniko builds it directly" text in Tower GlidepathTab.tsx.
  • Decide whether to enforce baseline (with Localhost profiles) on the cicd namespaces.

Exit No app sets builder: kaniko, after a full week of builds.

P4

Remove kaniko

  • Delete the two kaniko steps and the crane step; deprecate, then drop, build.builder.
  • The -arm64/-amd64 tags stop being published. Grep the gitops repos for them first.

Risks to watch

  • Unconfined seccomp isn't allowed. Fall back to Localhost profiles or hostUsers: false; containerd 2.3.1 and Kubernetes 1.36 on the dev cluster support user namespaces.
  • Peak memory per build pod goes up. BuildKit runs stages in parallel on a node already at 86% memory. Revisit the 250m/256Mi request.
  • binfmt is still needed. RUN steps that genuinely run on the target arch still emulate; keep the kube-system/binfmt DaemonSet.
  • Cache tags clutter the registry. :buildcache needs to be excluded from Tower's image prune and from release tag lists.

Sources

Glidepath sources: charts/glidepath-catalog/templates/tasks/build-image.yaml, docs/admin/adr/0010-kaniko-rootless-builds.md, docs/admin/known-gaps.md, docs/admin/pipeline-performance.md. Live state from the dev cluster on October 17, 2026.