Shrink Your Docker Images Fast: Using BuildKit Multi‑Stage Builds in CI
Learn how Docker BuildKit’s multi‑stage builds can cut image size by up to 70% and speed up CI pipelines. Follow a step‑by‑step example, understand trade‑offs, and get actionable next steps.
06 Mar 2026, 01:57 UTC

Problem
When a CI pipeline finishes a Docker build, the resulting image often carries a heavy footprint: build tools, temporary files, and source code that are only needed during compilation. A 500 MB image can slow down deployment, increase storage costs, and cause network bottlenecks. The key question is how to keep the image lean without sacrificing build speed or reproducibility.
BuildKit Basics
Docker BuildKit is a modern build engine that runs build steps in parallel, reuses cached layers more aggressively, and supports advanced features like multi‑stage builds. BuildKit is not enabled by default; you must either set the environment variable DOCKER_BUILDKIT=1 or enable it in the Dockerfile with # syntax=docker/dockerfile:1.4. BuildKit is available in Docker 20.10 and newer.
When BuildKit is active, the build output includes the line Using BuildKit and shows each stage’s progress in parallel. This parallelism can cut build times by 20–30% for large projects with many independent steps.
Multi‑Stage Build Strategy
A multi‑stage build separates the build phase from the runtime phase. The first stage pulls a full compiler image, installs dependencies, and compiles the application. The second stage starts from a minimal base image (e.g., scratch or alpine) and copies only the compiled binaries. Because the runtime stage never sees the build tools, the final image contains only what the application needs to run.
Key benefits:
- Image size reduction – up to 70% compared to single‑stage builds.
- Cache reuse – unchanged code only rebuilds the affected layers; BuildKit’s caching works across stages.
- Security – fewer packages mean a smaller attack surface.
Worked Example
Below is a simple Go application that demonstrates the process. Assume the source lives in the current directory.
# syntax=docker/dockerfile:1.4
# Stage 1: Build the binary
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /app/main ./cmd/app
# Stage 2: Runtime image
FROM scratch
COPY --from=builder /app/main /app/main
ENTRYPOINT ["/app/main"]
Run the build with BuildKit enabled:
export DOCKER_BUILDKIT=1
# Build the image
docker build --progress=plain -t myapp:latest .
# Inspect sizes
docker image ls myapp
# Verify layers
docker history myapp
In the output you should see the Using BuildKit header, parallel stage execution, and a final image size that is a fraction of the builder image. The docker history command will list only the runtime layers (no compiler or source files).
Trade‑offs & Limitations
- Complexity for beginners: Naming stages and correctly using
FROM ... AScan be confusing. A mistake like copying from the wrong stage can silently break the image. - Docker version constraints: BuildKit requires Docker 20.10+. On older hosts you must upgrade or fall back to the legacy builder.
- Cache invalidation: If you change the base image in the builder stage, all downstream layers will rebuild. Plan cache‑friendly stage ordering.
- Runtime base choice: Using
scratchlimits you to statically linked binaries. For dynamic languages (Python, Node) you’ll need a minimal runtime base (e.g.,node:20-alpine).
Actionable Next Steps
- Ensure your CI runner uses Docker 20.10+ and enable BuildKit with
export DOCKER_BUILDKIT=1or the# syntaxdirective. - Refactor existing Dockerfiles to separate build and runtime stages. Start with a minimal example like the one above.
- Measure the impact: compare
docker image lsbefore and after, and benchmarkdocker buildtimes with--progress=plain. - Automate the check in CI: add a step that fails if the final image exceeds a size threshold (e.g.,
docker image inspect --format='{{.Size}}'). - Document your stage names and copy patterns so teammates can maintain the Dockerfiles.
With these steps, you’ll achieve lean, fast‑building images that make your CI pipeline more efficient and your deployments more predictable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.