BuildKit, rootless and tokenless
A Dockerfile is a program a tenant wrote. The Job is built so that running it grants nothing.
The push landed; something has to turn it into an image. BuildKit does, rootless and daemonless, inside a Kubernetes Job — one Job per build, no long-lived builder, no Docker socket anywhere.
The premise. A Dockerfile is a program, written by a tenant, that this cluster is about to execute. Every decision in the Job spec follows from taking that sentence literally.
No ServiceAccount token. automountServiceAccountToken: false is the highest-value line in the file. Without it, a projected token for the Job’s ServiceAccount is mounted at a well-known path, and a RUN step reads it. Even a token with almost no permissions is an authenticated identity inside the cluster and a starting point; the default token also makes the API server reachable and enumerable. Setting this to false means a build has no cluster identity at all — there is nothing at the path to steal.
backoffLimit: 0. One attempt, one verdict. Retrying a build that failed is either pointless, because the Dockerfile is wrong, or actively harmful, because it hides a Dockerfile that is flaky — one that pulls an unpinned dependency, or races on a network fetch — behind an eventual green. It also bounds cost: a retry loop on a build that fails at minute 25 is expensive on a single-node cluster.
Rootless, and under gVisor. No privileged container and no host mounts; the build runs under the same RuntimeClass as any other user code from hop 6. That combination has sharp edges worth warning a class about in advance rather than during the exercise: rootless BuildKit needs user namespaces available on the node, and a few build patterns — RUN --mount=type=bind in some configurations, anything that shells out to mount, builds that expect /proc/sys to be writable — fail under runsc in ways whose error messages do not say "gVisor". An hour disappears there if nobody has said the word first.
apiVersion: batch/v1
kind: Job
spec:
backoffLimit: 0 # one attempt. a retry hides flake.
activeDeadlineSeconds: 1800
template:
spec:
runtimeClassName: gvisor # same sandbox as any user code
automountServiceAccountToken: false # <- the line that matters most
restartPolicy: Never
containers:
- name: buildkit
image: moby/buildkit:v0.27.0-rootless@sha256:9c2b...
command: [buildctl-daemonless.sh]
args:
- build
- --frontend=dockerfile.v0
- --local=context=/src
- --local=dockerfile=/src
- --output=type=image,name=$(IMAGE),push=true
securityContext:
runAsUser: 1000
allowPrivilegeEscalation: false
seccompProfile: { type: RuntimeDefault }
capabilities: { drop: [ALL] }
Check yourself
2 questions. One attempt each is recorded; the explanation is the point, not the score.
Why is automountServiceAccountToken: false the most important line in the build Job?
What is the argument for backoffLimit: 0?