Hangar

Docs · Glidepath

Rootless image builds

charts/glidepath-catalog/templates/tasks/build-image.yaml builds images with rootless BuildKit (moby/buildkit:<version>-rootless, run with buildctl-daemonless.sh), one daemon per TaskRun. See ADR-0026 for why it replaced kaniko on 2026-10-17.

What the build step needs

Requirement Why
Pod Security baseline or privileged on the app’s -cicd namespace (Glidepath sets and enforces baseline) rootlesskit creates a user namespace; restricted (allowPrivilegeEscalation: false, RuntimeDefault seccomp) blocks that
kubelet seccompDefault: false, or a Localhost seccomp profile on the step RuntimeDefault seccomp blocks the user-namespace setup
binfmt_misc handlers on the build node for every foreign platform RUN steps in a target-platform stage run emulated; dev registers them with 00-bootstrap/binfmt/ (gitops-cluster-dev)
A registry-credentials dockerconfigjson Secret in the namespace Push credentials, mounted at /home/user/.docker

The step runs as UID 1000, adds no capabilities, mounts nothing from the host and sets no seccomp or AppArmor profile. That is the shape that passes baseline: setting either profile to Unconfined, as BuildKit’s own Kubernetes examples do, is rejected by baseline.

Every -cicd namespace carries pod-security.kubernetes.io/enforce: baseline (with warn and audit at the same level), applied by the tenant-onboarding ApplicationSet’s managedNamespaceMetadata. A Task change that adds a privileged field fails at pod creation, not silently. See security.md.

Cross-architecture builds

The build node is arm64. For an amd64 image, a stage written FROM --platform=$BUILDPLATFORM runs natively and cross-compiles (GOARCH=$TARGETARCH, a Java jar, a pure-JS bundle); every other stage’s RUN steps go through QEMU. Emulation is slow and crashes the Go compiler (known-gaps.md #37), so Containerfiles that compile should compile in a $BUILDPLATFORM stage. See cicd-yaml-reference.md.

Cache

Each app gets a second GHCR package, <image-repo>-buildcache, holding one OCI cache manifest (latest, mode=max). The first build logs failed to configure registry cache importer ... not found and carries on. Deleting the package only costs one cold build.

Checking a cluster

To see whether a build pod would be admitted at a given level, label a scratch namespace and dry-run the step’s pod spec against it:

kubectl create ns pss-check
kubectl label ns pss-check pod-security.kubernetes.io/enforce=baseline
kubectl create --dry-run=server -n pss-check -f build-pod.yaml

Admission is not enough: a restricted-shaped pod is admitted and then fails inside rootlesskit. Run a real build once on a new cluster before onboarding apps.