Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français
Lire en français

Container image build tools: BuildKit, Buildah, Kaniko or Buildpacks?

21 min read
Improve this page

You know how to build an image with docker build or podman build, but your context changes: a CI without a Docker daemon (the service that runs in the background and executes the builds), a Kubernetes cluster, a project without a Dockerfile. This lesson teaches you how to choose the right build tool. BuildKit remains the default choice with Docker, Buildah is the one of the Podman ecosystem and of daemonless builds, Buildpacks builds without a Dockerfile, and Bake drives several BuildKit builds. Kaniko, long recommended for Kubernetes, had its Google repository archived on June 3, 2025, but a maintained fork still publishes it: it is now chosen from that fork, never from Google's frozen image.

What you will learn

  • Tell a build tool apart from an engine that runs containers
  • Compare five tools on the daemon, privileges, input format, cache and multi-architecture
  • Choose the right tool for your workstation, a CI with or without a daemon, a Kubernetes cluster
  • Cleanly move a pipeline that still depends on the original Kaniko image

Prerequisites

This lesson assumes that you can write a Dockerfile (or a Containerfile, the same format under another name) and run docker build or podman build on your workstation. If not, start by writing a Dockerfile or by building an image with Podman (in French). A rough idea of what a CI/CD pipeline is is enough for the continuous integration part: no command on this page requires a cluster.

What is a container image build tool?

A build tool turns a Dockerfile and its context (the files of the directory sent to the build) into an OCI image, the standard format that common container engines know how to run. It does not run your application: it makes the package that other tools will run. This separation explains why you can build with one tool and run with another.

Build or runtime: two distinct jobs

The tools compared here build images; Docker, Podman or containerd run them. The table separates the two roles. The confusion comes from the fact that engines embed a builder: docker build delegates to BuildKit, podman build delegates to Buildah.

ToolRoleOutput
BuildKit, Buildah, Kaniko, BuildpacksBuild imagesOCI image (tar archive or push to a registry)
Docker, Podman, containerdRun containersRunning container

What a build tool does

A build tool always chains the same steps, from reading the Dockerfile to publishing to a registry (the server that stores images, like Docker Hub or GHCR). The table follows that order: the tools differ mostly in how they handle the cache.

FunctionDescription
Parse the DockerfileAnalyze the FROM, RUN, COPY, etc. instructions
Manage layersCreate one layer per instruction that changes the file system
Run the commandsRun RUN apt install, RUN npm ci in a temporary container
Manage the cacheReuse layers already built when nothing has changed
Produce the final imageGenerate the OCI manifest and the compressed layers
Push to a registrySend the image to Docker Hub, GHCR, Harbor, etc.

With or without a Dockerfile?

Two approaches coexist: write the build instructions yourself, or let a tool detect the stack of the project (Node, Python, Java) and infer the image from it. The two tabs show the same application built both ways. The Dockerfile gives you control, Buildpacks give you ready-made conventions.

You write the build instructions yourself:

FROM node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "index.js"]

You keep full control, and the build is reproduced and versioned with the code. In return, you must write and maintain it for every project.

Which criteria set the build tools apart?

Five criteria settle most choices. The first is the presence of a daemon, a service that runs permanently, often as root, and to which the client sends its orders: dockerd or buildkitd. In a shared CI or a Kubernetes pod (the unit that runs your containers in Kubernetes), exposing that daemon amounts to giving root access to the host, which pushes toward daemonless tools.

  • Daemon: does the tool need a permanent service, or is each build a simple process?
  • Privileges: can it run rootless (as an ordinary user) or without the privileged mode of a container?
  • Input format: Dockerfile, shell script, HCL file or automatic detection?
  • Shared cache: can it export its cache to a registry, so that a fresh CI runner does not start from zero?
  • Multi-architecture: does it produce an image for linux/amd64 and linux/arm64 in a single command?

A sixth criterion comes in as soon as you talk about the supply chain: the state of the project. A tool whose repository is archived no longer receives security fixes, and every build runs it with access to your code and to your registry credentials. That criterion rules out Google's frozen Kaniko image and forces you to pull Kaniko from its maintained fork.

The five build tools in detail

Each tool is presented with the same grid: what it does, its strengths, its limits, and the context where to pick it. The order goes from BuildKit, the Docker builder, to the more specialized tools.

BuildKit, the Docker standard

BuildKit is the Docker build engine. Introduced as an option in Docker 18.09, it has been the default builder since Docker 23.0: when you type docker build, it is the one working. The docker buildx command exposes its advanced features, and the buildkitd daemon can also run alone, without Docker, including in a rootless image published by the project.

  • Strengths: fine-grained cache, parallel stages, multi-architecture through --platform, cache exported to a registry (--cache-to), build secrets (--mount=type=secret)
  • Limits: needs a daemon (dockerd or buildkitd); rootless mode has to be configured
  • Best for: a workstation with Docker, a CI where Docker is available, multi-architecture

The lesson BuildKit: build secrets and private access details secrets, SSH access and the mount cache, and the lesson Build an image for several architectures covers builders and multi-platform builds.

Buildah, daemonless and scriptable

Buildah is the build tool of the Podman ecosystem: podman build uses it internally. Each command is an isolated process, without a daemon, and rootless mode is its normal way of working. It reads a Dockerfile (buildah build) or is driven by script (buildah from, buildah run, buildah commit) when a Dockerfile is no longer enough.

  • Strengths: daemonless, rootless, two modes (Dockerfile or script), OCI images readable by Docker and Kubernetes
  • Limits: in a Kubernetes pod, rootless needs some tuning (user namespaces, storage); a smaller CI tooling ecosystem than BuildKit's
  • Best for: a workstation with Podman, a CI without a daemon, scripted builds

The Buildah tutorial (in French) shows both modes and a complete Tekton task.

Buildpacks, zero Dockerfile

Cloud Native Buildpacks (CNB, a CNCF project) detect the stack of the project from its files (package.json, requirements.txt, pom.xml) and assemble the image without a Dockerfile. A builder, like those of Paketo, groups the buildpacks and the base image; the pack CLI runs them.

  • Strengths: no configuration, SBOM (the list of the components of the image) produced on every build, rebase of the base image without rebuilding everything
  • Limits: less control than a Dockerfile; pack relies on a Docker daemon or on the Podman socket
  • Best for: multi-language teams, standard applications, the need for an SBOM

The Buildpacks tutorial (in French) builds a Flask application from end to end.

Bake, several builds declared in a file

Bake is not one more builder: it is a way to drive BuildKit from a docker-bake.hcl file, written in HCL (HashiCorp's configuration language). You declare targets (one per image), groups and variables, then docker buildx bake runs everything in parallel.

  • Strengths: declarative and versioned, inheritance between targets, can read a Compose file
  • Limits: BuildKit only; one more syntax to learn
  • Best for: a monorepo that produces several images, dev, staging and prod variants

The Bake tutorial starts from shell scripts and replaces them with a file of targets.

Kaniko, archived at Google, maintained by a fork

Kaniko builds an image inside a container, without a daemon and without privileged mode, which made it for years the recommended tool for Kubernetes and GitLab CI. But Google archived the GoogleContainerTools/kaniko repository on June 3, 2025: its README announces a project "archived, no longer developed or maintained". The historical registry gcr.io/kaniko-project is frozen at v1.24.0.

Two forks took over the code. osscontainertools/kaniko publishes regular releases (v1.28.6 on October 11, 2026) on ghcr.io/osscontainertools/kaniko; chainguard-forks/kaniko provides security maintenance without publishing an image and with no new feature planned. The site therefore keeps Kaniko among its choices, pulled from the osscontainertools fork and pinned by digest.

Kaniko's strength shows on a CI runner with a Docker executor (the mode where each job runs in a Docker container) left with its default security profiles. Kaniko builds the image there without any tuning, where Buildah and rootless BuildKit fail until you loosen the seccomp and AppArmor filters of the container, as the guide GitLab registries details.

  • Limits specific to Kaniko: it generally runs as root in its container (its README says so: no privileged mode, but with the rights needed to unpack the base image), it builds only one architecture per run, and it does not make a build of untrusted code safe
  • Best for: a CI without a daemon whose security profiles you do not want to loosen, and any existing Kaniko pipeline, repointed to the fork image

Detailed comparison table

The table covers the five tools on the criteria set out above. Read it by column to get a sense of one tool, by row to compare one criterion. The "Daemon required" row separates the daemonless tools (Buildah, Kaniko) from the other three, and the "State of the project" row is a reminder to pull Kaniko from its fork, not from Google.

CriterionBuildKitBuildahBuildpacksBakeKaniko
Daemon requiredYes (dockerd or buildkitd)NoYes for pack (Docker or Podman socket)Yes (BuildKit)No
RootlessPossible (rootless image)DefaultDepends on the engineSame as BuildKitGenerally not (root in its container)
Input formatDockerfileDockerfile or scriptAutomatic detectionHCL + DockerfileDockerfile
Cache to a registry--cache-to / --cache-from--cache-to / --cache-from--cache-image (with --publish)Same as BuildKit--cache-repo
Multi-architecturemultiple --platform--platform + --manifestDepends on the builderSame as BuildKitOne architecture per run
SBOM produced by defaultNo (--sbom option)NoYesSame as BuildKitNo
State of the projectActive (moby, Docker)Active (containers, Red Hat)Active (CNCF)Active (Docker)Google repository archived, osscontainertools fork active

All these tools produce OCI images: an image built by Buildah runs with Docker, a BuildKit image with Podman. Choosing the build tool therefore does not lock in the choice of the engine.

Which build tool should you choose for your context?

The short answer: BuildKit if Docker is there, Buildah if you are on Podman or without a daemon, Buildpacks without a Dockerfile, and Bake to drive several images. The table maps each common situation to a tool, then each subsection gives the command or the fragment that goes with it.

Your situationRecommended toolWhy
Workstation with DockerBuildKitAlready there, cache and multi-architecture built in
Workstation with PodmanBuildah (through podman build)Daemonless, rootless by default
CI where Docker is availableBuildKitReady-made actions and images, exportable cache
CI without a daemon, Kubernetes podKaniko (fork), Buildah or rootless BuildKitNo Docker socket; Kaniko without loosening seccomp or AppArmor
Project without a DockerfileBuildpacksStack detection, SBOM
Monorepo, several imagesBakeDeclared targets, parallel builds
Existing Kaniko pipelineKaniko from the maintained forkLeave Google's frozen image

On your workstation

On a workstation, the simplest tool is the one your engine already embeds: docker build goes through BuildKit, podman build through Buildah. There is no need to install anything else until a constraint requires it. Multi-architecture only requires, when a RUN must execute for the other architecture, that QEMU emulation be registered on the Linux host.

Terminal window
# With Docker: BuildKit
docker build -t my-app:1.0.0 .
docker buildx build --platform linux/amd64,linux/arm64 -t my-app:1.0.0 .
# With Podman: Buildah
podman build -t my-app:1.0.0 .

With the containerd image store, chosen by default on a new installation of Docker Engine 29, the default builder accepts several platforms in --platform: there is no builder to create. On the old image store, you have to create one, with the docker-container driver. The lesson Build an image for several architectures measures it on Docker 29.9.2 and details emulation and cross-compilation.

On the Podman side, an image for several architectures is assembled in a manifest list, as the lesson multi-architecture images (in French) shows.

In CI when Docker is available

On a runner (the machine that executes the CI jobs) that provides Docker, like the hosted Linux runners of GitHub Actions, BuildKit is the natural choice: the official docker/build-push-action action drives it and can export the cache to GitHub's cache service (type=gha). The fragment pins the action on the SHA of its commit, with the version left in a comment.

- name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

Without an exported cache, every fresh runner rebuilds all the layers: the build stays correct, but it gets slower on every run.

In CI without a daemon or in Kubernetes

When the runner offers no Docker daemon, or when the build runs in a pod, mounting the host's Docker socket or launching a privileged container (Docker-in-Docker) gives the job root access to the machine. Three tools avoid that trade-off: Kaniko, pulled from its fork, Buildah and rootless BuildKit. Only Kaniko builds without loosening the security profiles of the container; the other two require disabling the seccomp and AppArmor filters, as the BuildKit example below shows.

Buildah is launched from the quay.io/buildah/stable image, without a daemon. The Buildah tutorial (in French) gives a Tekton task and a GitLab CI job.

Rootless BuildKit runs buildkitd as an ordinary user, from the moby/buildkit image with the -rootless suffix. Its documentation asks you to disable the seccomp and AppArmor filters of the container rather than use --privileged:

Terminal window
docker run --name buildkitd -d \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--security-opt systempaths=unconfined \
moby/buildkit:v0.34.0-rootless@sha256:65eca3c01cd1180c8f64565c4e3c5983af5145818b0e6fa863385d099eb73855

Kubernetes has no equivalent to systempaths=unconfined: the project's Kubernetes examples therefore start buildkitd with the --oci-worker-no-process-sandbox option, which the BuildKit rootless documentation describes along with its trade-offs. Read it before adopting it.

For a project without a Dockerfile

If the team does not want to maintain a Dockerfile, or manages dozens of services in different languages, Buildpacks produces consistent images from the code alone. The pack build command of the "Without a Dockerfile" tab is enough, with a builder pinned by digest. The SBOM ships in the image, which simplifies a compliance audit.

For several images in a single repository

A monorepo that produces a frontend, an API and a worker ends up with a script of ten docker buildx build commands. Bake replaces that script with a file of targets and groups, versioned with the code, and runs the builds in parallel. It assumes BuildKit: it is a way to drive it, not another engine.

If your pipelines still use Kaniko

Start by reading the image that your jobs pull. A gcr.io/kaniko-project/executor reference runs code frozen since mid-2025 and will receive no more fixes. The immediate move is to replace it with the image published by the fork osscontainertools, pinned by digest; the call options (--context, --destination, --cache-repo) stay the same.

# ❌ image frozen when Google's repository was archived, no more fixes
image: gcr.io/kaniko-project/executor:v1.24.0
# Image maintained by the fork, pinned by digest
image: ghcr.io/osscontainertools/kaniko:v1.28.6@sha256:2fa20b0a4b40c7db72b479d57e55c2cf79f2178533b2d3bcbd129d6bd3c087ed

This image replacement is enough: the fork publishes regular releases, and your jobs keep their options. Watch its releases as for any dependency, and refresh the digest on every release. The Kaniko tutorial (in French) details its use in CI and in Kubernetes.

Common pitfalls to avoid

These mistakes come up often when changing build tools. Forgetting the cache in CI breaks nothing: it makes every build slower and goes unnoticed for a long time, unlike a RUN of another architecture without QEMU, which fails right away with exec format error.

Key takeaways

Seven points sum up the lesson: the difference between a build tool and an engine, one point per family of tools, then the cache and OCI portability.

  1. A build tool makes the image, an engine runs it: docker build delegates to BuildKit, podman build to Buildah.
  2. BuildKit is the default choice whenever Docker is available, on the workstation as in CI.
  3. Buildah builds without a daemon and rootless: it is the choice of Podman and of CIs without a daemon.
  4. Buildpacks builds without a Dockerfile and produces an SBOM; Bake drives several BuildKit builds.
  5. Kaniko is pulled from its maintained osscontainertools fork: Google's repository has been archived since June 3, 2025 and the gcr.io image is frozen at v1.24.0.
  6. A cache exported to a registry avoids rebuilding everything on every fresh CI runner.
  7. They all produce OCI images: changing the build tool does not change the runtime engine.

Test your knowledge

The quiz checks that you can match a context (workstation, CI with or without a daemon, Kubernetes, no Dockerfile) with the right build tool, and that you know the current state of Kaniko and its consequences.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
5 min
80% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

FAQ: frequently asked questions

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn