
September 10, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Docker BuildKit and buildx are the modern replacement for Docker's legacy builder. If your CI pipeline still runs plain docker build, you are likely waiting on serial layer steps, weak cache reuse, and no clean path to ARM images. BuildKit is the backend engine. buildx is the CLI front end that exposes builders, platforms, and advanced cache. On real client projects I ship with Docker on Ubuntu and GitLab CI, switching to BuildKit often cuts build time before we touch application code. This guide covers enablement, daily commands, multi-platform workflows, and the failures I see after deploy.
What is Docker BuildKit and why should you use it?
BuildKit is Docker's next-generation image builder. It replaced the classic builder's linear, single-threaded model. BuildKit parses your Dockerfile into a directed acyclic graph (DAG). Independent stages run in parallel. Cache mounts and secrets stay out of final image layers.
The legacy builder still works on many hosts. It is slower, offers fewer cache options, and lacks first-class multi-platform support. BuildKit has been the default builder in Docker Engine since version 23.0. If you run a current Engine release on Ubuntu 22.04 or 24.04, BuildKit is probably already active.
buildx is a Docker CLI plugin built on BuildKit. It manages named builders, drives multi-platform builds, and supports advanced output modes. You do not choose BuildKit or buildx for most tasks. You enable BuildKit as the engine and use buildx when you need builders beyond the default one.
BuildKit matters for three practical reasons on production pipelines:
- Speed: Parallel stage execution and smarter layer caching shrink CI minutes. That saves money on GitLab runners and AWS build hosts.
- Security: BuildKit supports
RUN --mount=type=secretso API keys never land in image layers. - Portability: buildx can target
linux/amd64andlinux/arm64from one command. Useful for Apple Silicon dev laptops and Graviton EC2 nodes.
If you maintain Laravel or WordPress apps in containers, BuildKit pairs well with multi-stage Docker builds and image size reduction techniques. Smaller images deploy faster and reduce attack surface.
How do you enable Docker BuildKit and buildx on Ubuntu?
On Docker Engine 23.0 and later, BuildKit is the default builder. Verify your version first:
docker version --format '{{.Server.Version}}'
docker buildx version If buildx is missing, install the plugin package on Ubuntu:
sudo apt-get update
sudo apt-get install -y docker-buildx-plugin docker-compose-plugin Confirm BuildKit is active
Run a build with plain progress output. You should see BuildKit-style step IDs, not the legacy "Step 1/8" format:
DOCKER_BUILDKIT=1 docker build -t myapp:test . On current Engine releases you can skip the environment variable. BuildKit runs by default. To force the legacy builder during a one-off test:
DOCKER_BUILDKIT=0 docker build -t myapp:legacy . Create and select a buildx builder
The default builder works for single-platform local builds. For multi-platform work, create a dedicated builder with the docker-container driver:
docker buildx create --name multiarch --driver docker-container --use
docker buildx inspect --bootstrap The bootstrap step pulls the BuildKit image and starts the builder container. List builders anytime:
docker buildx ls I've set this up on shared EC2 hosts that run Deployer and GitLab CI for legal-tech portals. The builder lives alongside the app containers without replacing the production runtime. For server hardening around Docker itself, see Linux system administration practices that cover firewall rules, log rotation, and daemon updates.
How does Docker buildx differ from the classic docker build?
Both commands read a Dockerfile and produce an image. The difference is capability and defaults. Classic docker build talks to the local daemon builder. buildx routes work through a configurable BuildKit backend and adds export options the classic command lacks.
| Feature | Classic docker build | docker buildx build |
|---|---|---|
| Default since Engine 23+ | Uses BuildKit backend | Uses BuildKit with explicit builder control |
Multi-platform (--platform) | Single platform only | Multiple platforms in one command |
| Output destinations | Local daemon image store | Registry, local daemon, OCI dir, tar |
| Cache backends | Local layer cache | Local, registry, S3, GCS, Azure Blob |
| Build contexts | Local directory or Git URL | Local, Git, HTTP, Docker image contexts |
docker buildx bake | Not available | HCL/YAML/JSON multi-target builds |
| Load into local daemon | Automatic | Requires --load flag |
The --load requirement catches many developers. A multi-platform build cannot load all architectures into the local daemon at once. buildx pushes to a registry by default when multiple platforms are requested.
For day-to-day single-platform work on a dev laptop, docker build is fine. Switch to buildx when CI needs registry cache, multiple architectures, or bake files. The official Docker BuildKit documentation lists every front-end syntax extension. The multi-platform build guide covers platform flags and QEMU setup.
How do you use buildx for multi-platform image builds?
Multi-platform builds produce one manifest list tag that resolves to the correct architecture at pull time. This is how you ship the same tag to x86 EC2 and ARM Graviton without maintaining separate Dockerfiles.
Single-platform build loaded locally
docker buildx build \
--platform linux/amd64 \
--tag myorg/myapp:1.4.0 \
--load \
. Multi-platform build pushed to a registry
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag registry.example.com/myorg/myapp:1.4.0 \
--push \
. Use a registry you control. For a private setup, read the self-hosted Docker registry guide. For choosing between GitLab, Docker Hub, and ECR, see the container registry comparison.
Detailed platform workflows live in the dedicated buildx multi-platform builds article. The short version: create a docker-container driver builder, bootstrap it, then push with --push.
BuildKit Dockerfile features worth adopting
BuildKit unlocks syntax that the legacy builder ignores or rejects. These patterns appear in production Laravel and Node images I maintain:
# syntax=docker/dockerfile:1
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN --mount=type=cache,target=/tmp/cache \
composer install --no-dev --prefer-dist
FROM node:26-alpine AS assets
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
RUN npm run build
FROM php:8.3-fpm AS app
COPY --from=vendor /app/vendor ./vendor
COPY --from=assets /app/public/build ./public/build
COPY . .
The # syntax=docker/dockerfile:1 directive tells BuildKit to use the current Dockerfile frontend. Cache mounts persist Composer and npm caches across builds without bloating image layers. That pairs directly with Docker layer caching strategies.
GitLab CI example with registry cache
This pattern mirrors pipelines I run for containerised Laravel apps. It uses buildx with registry-backed cache and pushes on main branch merges:
build-image:
stage: build
image: docker:27-cli
services:
- docker:27-dind
variables:
DOCKER_BUILDKIT: "1"
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker buildx create --use --name ci-builder
- docker buildx inspect --bootstrap
script:
- |
docker buildx build \
--platform linux/amd64 \
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
--tag $CI_REGISTRY_IMAGE:latest \
--cache-from type=registry,ref=$CI_REGISTRY_IMAGE:buildcache \
--cache-to type=registry,ref=$CI_REGISTRY_IMAGE:buildcache,mode=max \
--push \
. The mode=max cache export stores intermediate layers remotely. Rebuilds after a small code change reuse Composer and npm layers. That is often the biggest CI win after enabling BuildKit itself.
How do you optimize BuildKit cache and bake files for CI?
Local layer cache disappears when CI runners are ephemeral. Registry cache backends solve that. buildx supports type=registry, type=local, and cloud object stores depending on your infrastructure.
Inline cache vs registry cache
- Inline cache (
--build-arg BUILDKIT_INLINE_CACHE=1): Embeds cache metadata in the image. Simple, but less efficient for large multi-stage files. - Registry cache (
--cache-to/--cache-from type=registry): Stores cache layers in a dedicated tag. Best for GitLab CI and GitHub Actions. - Cache mounts (
RUN --mount=type=cache): Speeds package manager steps inside a single build. Does not replace registry cache for cold runners.
docker buildx bake for multi-service repos
When a repo ships an app container, a queue worker, and an nginx sidecar, bake files beat shell loops. Create docker-bake.hcl:
group "default" {
targets = ["app", "worker"]
}
target "app" {
context = "."
dockerfile = "Dockerfile"
tags = ["myorg/myapp:latest"]
platforms = ["linux/amd64"]
}
target "worker" {
context = "."
dockerfile = "Dockerfile.worker"
tags = ["myorg/myapp-worker:latest"]
platforms = ["linux/amd64"]
} Run all targets in one command:
docker buildx bake --push For local Laravel development, compare bake-driven builds against Laravel Sail and Docker or a hand-rolled Docker Compose multi-container setup. Sail abstracts BuildKit for dev. Production CI still benefits from explicit buildx control.
On a booking platform like Adventure Third Pole Trek, separate app and worker images let the web tier scale independently from queue consumers. bake keeps both images versioned and cached consistently.
What are common BuildKit and buildx problems in production?
BuildKit fails differently from the legacy builder. Error messages reference graph nodes and mount types. These are the issues I troubleshoot most often.
Multi-platform load error
ERROR: multi-platform build is not supported for docker driver means you tried --load with multiple --platform values. Push to a registry instead, or build one platform at a time.
exec format error at runtime
The image architecture does not match the host CPU. An arm64 image on amd64 hardware produces this immediately. Inspect the manifest:
docker buildx imagetools inspect myorg/myapp:latest Tag correctly with Docker image tagging strategies so CI never deploys the wrong digest.
BuildKit disabled in CI
Older CI templates export DOCKER_BUILDKIT=0. Remove that line. Confirm the Dockerfile starts with the syntax directive if you use --mount or --secret flags.
Stale builder state
Builders accumulate over months on shared servers. Prune safely:
docker buildx prune -f
docker buildx rm old-builder-name If you evaluate alternatives, read the Podman vs Docker migration guide. Podman uses Buildah and a different build path. buildx skills still transfer because both ecosystems target OCI images.
For runtime routing after the image lands on a server, pair your pipeline with a reverse proxy like Traefik for Docker. BuildKit gets the image built. Traefik handles TLS and host rules at deploy time.
Key Takeaways
- BuildKit is the default builder in Docker Engine 23+; install the buildx plugin for multi-platform and registry cache workflows.
- Use
docker buildfor quick single-platform local work; usedocker buildx build --pushwhen CI needs multiple architectures or remote cache. - Add
# syntax=docker/dockerfile:1and cache mounts to speed Composer, npm, and apt steps without fattening image layers. - Registry cache with
--cache-to type=registry,mode=maxgives ephemeral CI runners warm builds on the second pipeline run. - Multi-platform images require
--pushto a registry — you cannot--loadmore than one platform into the local daemon. - Inspect manifest lists with
docker buildx imagetools inspectbefore blaming application code for exec format errors.
People Also Ask
Is BuildKit enabled by default in Docker?
Yes, on Docker Engine 23.0 and later. BuildKit runs as the default builder for docker build. You can confirm by watching for BuildKit-style progress output or by checking that DOCKER_BUILDKIT=1 is set in your environment. Older installations may still need that variable in /etc/docker/daemon.json or the shell profile.
Do I need buildx if I already have BuildKit?
For basic builds, no. You need buildx when you want named builders, multi-platform images, registry cache import and export, bake files, or output to OCI directories. buildx is the management layer; BuildKit is the engine underneath both docker build and docker buildx build.
Can buildx build images without pushing to a registry?
Yes, for single-platform builds. Pass --load to import the result into the local Docker image store. Multi-platform builds cannot load all architectures locally at once. Export to a tar with --output type=tar,dest=image.tar if you need offline artefacts without a registry.
Does buildx work with Docker Compose?
Compose v2 integrates BuildKit automatically when you run docker compose build. For advanced cache and platform flags, build images with buildx in CI and reference the tagged result in your Compose file. Mixing compose build and buildx push in the same pipeline is a common pattern on enterprise application projects.
Ship faster builds with BuildKit today
Docker BuildKit and buildx are not experimental extras in 2026. They are the baseline for any team building container images in CI. Enable the buildx plugin, create a builder, add registry cache to your pipeline, and adopt cache mounts in your Dockerfile. The first build may take the same time. The second build is where you win.
If your pipeline still runs on legacy patterns or your images keep failing with platform errors, I can audit the Dockerfile, CI config, and deploy path together. That is the same full-stack work I do across Laravel, Docker, and Linux hosting projects. For ongoing runner and daemon maintenance after the pipeline is fixed, see support and maintenance or contact us to discuss your build setup.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

