Kaniko: Building Container Images Inside Kubernetes

Docker-less image builds that don't need a privileged daemon

There is an awkward chicken-and-egg problem at the heart of running CI inside Kubernetes: you want to build container images, but the traditional way to build a container image is to run docker build, and docker build needs a Docker daemon, and a Docker daemon needs root and a bunch of kernel features that you really, really should not be handing to a CI pod. The old hack — mounting the host’s Docker socket into the build pod — is the security equivalent of leaving your front door open because the lock is fiddly. Anything that can talk to that socket effectively owns the node.

Kaniko, from Google, exists to break that dependency. It builds images from a Dockerfile entirely in userspace, with no Docker daemon and no privileged container, which means it runs as an ordinary pod in an ordinary cluster. You get your image built and pushed to a registry without ever giving CI the keys to the kingdom.

Advertisement

Kaniko ships as a single executor image. You hand it a build context (a directory, a Git repo, or a tarball in object storage), a Dockerfile, and a destination registry. It then reads the Dockerfile, and for each instruction it executes the command and snapshots the resulting filesystem changes in its own userspace, assembling proper image layers as it goes — no daemon, no nested containers.

The catch, and it’s worth understanding, is that Kaniko extracts the base image and runs the build commands directly inside its own container’s filesystem. That’s how it avoids needing privileged mode, but it’s also why you run Kaniko in a disposable pod and never on a machine you care about — it genuinely modifies its own root filesystem during the build. In Kubernetes that’s a non-issue: the pod is ephemeral and gone the moment the build finishes.

The cleanest way to use Kaniko is a one-shot Job. You need three things: the source somewhere Kaniko can reach, registry credentials mounted as a Docker config.json, and the destination tag. Here’s a build pulling its context straight from a Git repo:

apiVersion: batch/v1
kind: Job
metadata:
  name: build-myapp
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: kaniko
          image: gcr.io/kaniko-project/executor:latest
          args:
            - "--context=git://github.com/me/myapp.git#refs/heads/main"
            - "--dockerfile=Dockerfile"
            - "--destination=registry.example.com/myapp:1.4.0"
            - "--cache=true"
            - "--cache-repo=registry.example.com/myapp/cache"
          volumeMounts:
            - name: docker-config
              mountPath: /kaniko/.docker
      volumes:
        - name: docker-config
          secret:
            secretName: registry-credentials
            items:
              - key: .dockerconfigjson
                path: config.json

Note what’s absent: no securityContext: privileged: true, no host socket, no special node. The registry-credentials secret is a standard kubernetes.io/dockerconfigjson secret, the same kind you’d use for an image pull secret. Kaniko reads /kaniko/.docker/config.json to authenticate its push.

Apply it and watch:

$ kubectl apply -f build-job.yaml
job.batch/build-myapp created
$ kubectl logs -f job/build-myapp
INFO[0001] Retrieving image manifest python:3.12-slim
INFO[0004] Building stage 'python:3.12-slim' [idx: '0']
INFO[0011] Taking snapshot of full filesystem...
INFO[0019] RUN pip install -r requirements.txt
INFO[0042] Pushing image to registry.example.com/myapp:1.4.0
INFO[0058] Pushed registry.example.com/myapp:1.4.0

That’s a complete build-and-push cycle, no daemon anywhere in sight.

Kaniko’s biggest weakness is build speed — without help it re-runs every layer on every build, because there’s no local daemon cache persisting between ephemeral pods. The fix is the two flags above. --cache=true makes Kaniko push and pull intermediate layers to a registry-backed cache (--cache-repo), so an unchanged RUN pip install step is fetched rather than re-executed on the next build. It’s not as instant as Docker’s local layer cache, but for CI it turns a five-minute rebuild into a thirty-second one when only your application code changed.

For repeated builds you can also pre-warm base images with the companion kaniko-warmer, which stashes base images in a shared volume so each build doesn’t re-pull python:3.12-slim from scratch.

In practice you rarely write the Job by hand. CI systems that run on Kubernetes — Tekton, Argo Workflows, GitLab CI with the Kubernetes executor, Drone — all have Kaniko steps or examples, because this is the answer to “build images in CI without privilege.” The pattern is always the same: a step that mounts registry credentials and runs the Kaniko executor with --context, --dockerfile, and --destination. GitLab in particular documents Kaniko as its blessed approach for daemon-less builds on Kubernetes runners.

A couple of sharp edges to know. Kaniko doesn’t support every exotic BuildKit feature — if your Dockerfile leans hard on RUN --mount cache mounts or multi-platform buildx magic, check support before committing. And debugging is a little awkward because the executor image is distroless (no shell); use the :debug tag, which includes a busybox shell, when you need to poke around.

If you build images inside Kubernetes and you’ve been mounting the Docker socket to do it, stop and switch to Kaniko — the security upgrade alone is worth the afternoon. It’s the standard, well-supported way to produce OCI images from a Dockerfile without a privileged daemon, it slots into every Kubernetes-native CI system, and registry-backed caching keeps it fast enough for real pipelines. If you’re not on Kubernetes, or you need bleeding-edge BuildKit features, look at Buildah or rootless BuildKit instead. But for “I have a cluster and I need it to build my images safely,” Kaniko is the obvious, boring, correct choice.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.