Back to Blog
Guide9 min read2026-07-08

What Is BuildKit? Modern Container Image Builds Explained

BuildKit is the modern engine behind fast, cacheable, parallel Docker image builds. Learn how its DAG-based architecture, advanced caching, and rootless mode improve build speed and security.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

If you've run docker build in the last few years, you've used BuildKit — often without knowing it. It's the build engine that replaced Docker's legacy builder, and it's a genuine architectural leap: parallel, aggressively cacheable, secret-aware, and capable of running rootless. This guide explains what BuildKit is and why it matters.

The problem with the legacy builder

The original Docker builder processed a Dockerfile sequentially, top to bottom, one instruction per layer, with no understanding of which steps actually depended on each other. Consequences:

  • No parallelism — independent steps still ran one after another.
  • Caching was naive and line-order-sensitive; touching one early line busted everything after it.
  • Secrets passed via build args leaked into image layers.
  • Builds required a privileged daemon with root.

For a small image, fine. For a large monorepo or a multi-stage build, painfully slow and a little dangerous.

What BuildKit changes

BuildKit reimagines the build as a directed acyclic graph (DAG) of operations rather than a linear script. It analyzes the Dockerfile, figures out the real dependencies between steps, and then:

  • Runs independent steps in parallel. Two unrelated stages build simultaneously.
  • Skips unneeded work. In a multi-stage build, only the stages contributing to the final target are executed.
  • Caches intelligently. It caches at the operation level and can import/export cache to external stores, so CI runs share cache.
  • Handles secrets safely. Secrets are mounted for a single step and never persisted in a layer.
  • Runs rootless. No privileged daemon required.

The DAG advantage, concretely

Consider a multi-stage build that compiles a frontend and a backend, then assembles them:

FROM node:20 AS frontend
WORKDIR /fe
COPY frontend/ .
RUN npm ci && npm run build

FROM golang:1.22 AS backend
WORKDIR /be
COPY backend/ .
RUN go build -o server .

FROM gcr.io/distroless/base
COPY --from=frontend /fe/dist /app/public
COPY --from=backend /be/server /app/server
CMD ["/app/server"]

The legacy builder builds frontend fully, then backend, then assembles. BuildKit sees that frontend and backend don't depend on each other and builds them in parallel — often roughly halving wall-clock time on this shape of build.

Caching that actually helps

BuildKit's caching is where most real-world speedups come from. Two features stand out:

Layer ordering still matters

The oldest trick still applies — copy dependency manifests before source so dependency installs cache across source changes:

COPY package*.json ./
RUN npm ci          # cached unless package*.json changes
COPY . .            # source changes don't bust the install

Cache mounts

BuildKit adds persistent cache mounts that survive across builds without bloating the image:

# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm \
    npm ci

The /root/.npm cache persists between builds, so even a cache-busted npm ci reuses downloaded packages. The same pattern works for Go's module cache, pip's wheel cache, apt, and more.

Secrets without leaks

The legacy builder forced an ugly choice: pass a token as a build arg (leaks into history) or do contortions. BuildKit mounts secrets for a single RUN only:

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci
docker build --secret id=npmrc,src=$HOME/.npmrc .

The secret is available during that step and never written to any layer.

Rootless mode and why it matters for security

Traditionally, building images meant a daemon running as root — a meaningful attack surface, especially in shared CI. BuildKit can run rootless, building images without root privileges and without a Docker socket. For a multi-tenant build platform, this is essential: you don't want one tenant's build able to escalate via a privileged daemon.

How PandaStack uses BuildKit

PandaStack runs rootless BuildKit inside ephemeral Kubernetes Job pods. Each build gets a fresh, isolated pod; BuildKit builds your image without a host Docker socket and pushes it to Google Artifact Registry; the image is then deployed via Helm. This gives you BuildKit's speed and caching, plus strong isolation between builds — no shared privileged daemon, no leakage between tenants. (We go deeper on this in our ephemeral build pods architecture post.)

BuildKit vs the legacy builder

FeatureLegacy builderBuildKit
ExecutionSequentialParallel (DAG)
Multi-stage pruningNoYes (only needed stages)
Cache mountsNoYes
External cache import/exportNoYes
Build secretsLeak-proneFirst-class, ephemeral
RootlessNoYes

Getting started locally

BuildKit is the default in modern Docker, but you can be explicit:

# Force BuildKit
DOCKER_BUILDKIT=1 docker build -t myapp .

# Or use buildx (BuildKit-powered)
docker buildx build -t myapp .

Add # syntax=docker/dockerfile:1 at the top of your Dockerfile to unlock the newer features like cache and secret mounts.

References

  • [BuildKit on GitHub](https://github.com/moby/buildkit)
  • [Docker: BuildKit overview](https://docs.docker.com/build/buildkit/)
  • [Dockerfile frontend syntax (mounts, secrets, cache)](https://docs.docker.com/reference/dockerfile/)
  • [BuildKit rootless mode](https://github.com/moby/buildkit/blob/master/docs/rootless.md)
  • [Google Artifact Registry](https://cloud.google.com/artifact-registry/docs)

---

You get BuildKit's speed and rootless security automatically on PandaStack — no Dockerfile syntax headers or buildx setup required. Push a repo and watch a parallel, cached build run at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also