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.
| Tool | Role | Output |
|---|---|---|
| BuildKit, Buildah, Kaniko, Buildpacks | Build images | OCI image (tar archive or push to a registry) |
| Docker, Podman, containerd | Run containers | Running 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.
| Function | Description |
|---|---|
| Parse the Dockerfile | Analyze the FROM, RUN, COPY, etc. instructions |
| Manage layers | Create one layer per instruction that changes the file system |
| Run the commands | Run RUN apt install, RUN npm ci in a temporary container |
| Manage the cache | Reuse layers already built when nothing has changed |
| Produce the final image | Generate the OCI manifest and the compressed layers |
| Push to a registry | Send 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:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1WORKDIR /appCOPY package*.json ./RUN npm ci --omit=devCOPY . .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.
The tool detects your stack automatically:
pack build my-app:1.0.0 \ --builder paketobuildpacks/builder-jammy-base:0.4.653@sha256:1a695e9719bfc2a7c202e6df3339e9e02cfda1f56fde8b1d291680417cfc7cfdThe buildpack spots package.json, installs Node and the dependencies, then
generates the image.
You write no configuration, best practices are included and an SBOM is produced. In return, you have less control and the images are sometimes heavier.
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
privilegedmode 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/amd64andlinux/arm64in 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 (
dockerdorbuildkitd); 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;
packrelies 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.
| Criterion | BuildKit | Buildah | Buildpacks | Bake | Kaniko |
|---|---|---|---|---|---|
| Daemon required | Yes (dockerd or buildkitd) | No | Yes for pack (Docker or Podman socket) | Yes (BuildKit) | No |
| Rootless | Possible (rootless image) | Default | Depends on the engine | Same as BuildKit | Generally not (root in its container) |
| Input format | Dockerfile | Dockerfile or script | Automatic detection | HCL + Dockerfile | Dockerfile |
| Cache to a registry | --cache-to / --cache-from | --cache-to / --cache-from | --cache-image (with --publish) | Same as BuildKit | --cache-repo |
| Multi-architecture | multiple --platform | --platform + --manifest | Depends on the builder | Same as BuildKit | One architecture per run |
| SBOM produced by default | No (--sbom option) | No | Yes | Same as BuildKit | No |
| State of the project | Active (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 situation | Recommended tool | Why |
|---|---|---|
| Workstation with Docker | BuildKit | Already there, cache and multi-architecture built in |
| Workstation with Podman | Buildah (through podman build) | Daemonless, rootless by default |
| CI where Docker is available | BuildKit | Ready-made actions and images, exportable cache |
| CI without a daemon, Kubernetes pod | Kaniko (fork), Buildah or rootless BuildKit | No Docker socket; Kaniko without loosening seccomp or AppArmor |
| Project without a Dockerfile | Buildpacks | Stack detection, SBOM |
| Monorepo, several images | Bake | Declared targets, parallel builds |
| Existing Kaniko pipeline | Kaniko from the maintained fork | Leave 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.
# With Docker: BuildKitdocker build -t my-app:1.0.0 .docker buildx build --platform linux/amd64,linux/arm64 -t my-app:1.0.0 .
# With Podman: Buildahpodman 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=maxWithout 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:
docker run --name buildkitd -d \ --security-opt seccomp=unconfined \ --security-opt apparmor=unconfined \ --security-opt systempaths=unconfined \ moby/buildkit:v0.34.0-rootless@sha256:65eca3c01cd1180c8f64565c4e3c5983af5145818b0e6fa863385d099eb73855Kubernetes 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 fixesimage: gcr.io/kaniko-project/executor:v1.24.0
# Image maintained by the fork, pinned by digestimage: ghcr.io/osscontainertools/kaniko:v1.28.6@sha256:2fa20b0a4b40c7db72b479d57e55c2cf79f2178533b2d3bcbd129d6bd3c087edThis 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.
- A build tool makes the image, an engine runs it:
docker builddelegates to BuildKit,podman buildto Buildah. - BuildKit is the default choice whenever Docker is available, on the workstation as in CI.
- Buildah builds without a daemon and rootless: it is the choice of Podman and of CIs without a daemon.
- Buildpacks builds without a Dockerfile and produces an SBOM; Bake drives several BuildKit builds.
- Kaniko is pulled from its maintained
osscontainertoolsfork: Google's repository has been archived since June 3, 2025 and thegcr.ioimage is frozen at v1.24.0. - A cache exported to a registry avoids rebuilding everything on every fresh CI runner.
- 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
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
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
A new full quiz with random questions
Work again only on the questions you missed
FAQ: frequently asked questions
buildkitd, without Docker, including as a rootless image.ghcr.io/osscontainertools/kaniko, pinned by digest: Google archived GoogleContainerTools/kaniko on June 3, 2025 and the gcr.io/kaniko-project/executor image is frozen at v1.24.0. On a runner with a Docker executor, Kaniko builds with the default security profiles, whereas Buildah and rootless BuildKit require loosening seccomp and AppArmor.buildah build (formerly buildah bud). The images it generates are 100% OCI-compatible and work with Docker, Podman and containerd. The podman build command, by the way, uses Buildah internally. Buildah also supports a script mode (without a Dockerfile) for more complex builds: buildah from, buildah run, buildah commit.docker buildx build \
--cache-from type=registry,ref=ghcr.io/user/cache \
--cache-to type=registry,ref=ghcr.io/user/cache,mode=max \
-t my-app:1.0.0 .
mode=max exports all the layers (not only the final result). GitHub Actions also supports --cache-from/to type=gha to use the GitHub cache.docker buildx build \
--platform linux/amd64,linux/arm64 \
-t my-app:1.0.0 --push .
On Linux, first register QEMU emulation:docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3-68@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 --install all
For better performance, use native ARM64 runners instead of emulating.docker buildx bake --file docker-compose.yml
Bake extracts the build: sections of each service and turns them into targets. You can also create a dedicated HCL file (docker-bake.hcl) for more complex builds with variables, inheritance and groups.ARG or ENV (visible in the final image). Use --mount=type=secret (BuildKit/Bake/Buildah):Dockerfile:RUN --mount=type=secret,id=github_token \
TOKEN=$(cat /run/secrets/github_token) && \
git clone https://$TOKEN@github.com/user/repo.git
Build:docker buildx build --secret id=github_token,src=$HOME/.github-token .
The secret is never persisted in a layer.docker run --rm -v "$PWD":/workspace \
ghcr.io/osscontainertools/kaniko:v1.28.6@sha256:2fa20b0a4b40c7db72b479d57e55c2cf79f2178533b2d3bcbd129d6bd3c087ed \
--context=/workspace --destination=my-app:1.0.0 --no-push
It is slower and less convenient than BuildKit or Buildah, which remain the workstation tools.buildkitd (tag with the -rootless suffix). Its documentation launches it without --privileged, by lifting the container filters: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
In Kubernetes, the project's examples use --oci-worker-no-process-sandbox, for lack of an equivalent to systempaths=unconfined. Rootless Docker (dockerd-rootless-setuptool.sh install) is another route. Buildah remains the rootless-by-default alternative.