Home DevOps Docker Bake and Buildx: Declarative Multi-Platform Builds Without the Pain
Advanced 3 min · July 11, 2026

Docker Bake and Buildx: Declarative Multi-Platform Builds Without the Pain

Docker Bake (bake.hcl / docker-bake.hcl / docker-compose.yml) enables declarative multi-platform builds with matrix targets, parallel execution, registry/GHA/inline cache, Docker Build Cloud, native ARM, cross-compilation with BUILDPLATFORM/TARGETPLATFORM, and remote builds..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 11, 2026
last updated
1,750
articles · all by Naren
Before you start⏱ 30 min
  • Production DevOps experience
  • Deep understanding of the tool's internals
  • Experience debugging distributed systems
 ● Production Incident 🔎 Debug Guide
Quick Answer

Docker Bake (docker buildx bake) is a declarative build orchestrator that defines build targets in a file and executes them in parallel. It replaces shell scripts for multi-image, multi-platform builds. Define targets in docker-bake.hcl, run docker buildx bake, and it handles dependency resolution, parallel execution, cache configuration, and multi-platform builds. Pair with Docker Build Cloud for managed remote builders.

✦ Definition~90s read
What is Docker Bake and Buildx Multi-Platform Builds?

Docker Bake is a declarative build system that replaces complex shell scripts and Makefiles for orchestrating Docker builds. You define build targets (images, platforms, tags, cache configuration) in a JSON/HCL/YAML file (docker-bake.hcl, docker-compose.yml, or docker-bake.json), and docker buildx bake executes them in parallel with proper dependency ordering.

Imagine you're running a bakery.

It's like a Makefile for Docker — with built-in multi-platform support, cache configuration, and matrix builds.

The core concept is the 'target'. Each target specifies an image name, context, Dockerfile, platforms (e.g., linux/amd64, linux/arm64), tags, cache-to/cache-from configuration, and build arguments. Targets can inherit from other targets (via inherits) and depend on other targets (via dependencies). The bake command resolves the dependency graph and executes targets in parallel where possible.

Bake is part of docker buildx, which is Docker's next-generation build client. Buildx provides: multi-platform builds (build for amd64, arm64, and arm/v7 simultaneously), cloud-native builders (Docker Build Cloud for managed build infrastructure), remote builds (SSH to a remote builder), advanced cache configuration (registry, GHA, inline, local), and cross-compilation with BUILDPLATFORM/TARGETPLATFORM ARGs.

Plain-English First

Imagine you're running a bakery. Normally you'd write individual recipes for each type of bread, convert temperatures, and manage ovens manually. Docker Bake is like a master baker who knows every recipe, schedules them in parallel across multiple ovens, and adjusts for different altitudes (platforms). You just say 'bake everything' and the master baker figures out the rest.

Every team that manages more than a handful of Docker images confronts the same wall: a sprawling Makefile that tries to coordinate building, tagging, and pushing multiple images across multiple platforms. The script grows from 50 lines to 500. Cache configuration is copy-pasted across CI pipelines. At this point, the build script has become the bottleneck — not the build itself.

Docker Bake solves this by moving build configuration from imperative scripts into a declarative file. Instead of nested for-loops over platforms and images, you define targets with their context, Dockerfile, platforms, and cache settings. Bake handles the DAG of dependencies — building base images before services that depend on them — and parallelizes independent targets automatically.

Bake is one part of the docker buildx ecosystem. Buildx itself brings multi-platform builds, managed cloud builders (Docker Build Cloud for native ARM and AMD runners), remote builds (SSH to a more powerful machine), and advanced cache configuration.

Writing Your First docker-bake.hcl File

Docker Bake supports three file formats: HCL (HashiCorp Configuration Language, recommended for complex configs), JSON (for programmatic generation), and docker-compose YAML (for simple setups). The HCL format is the most powerful — it supports variables, functions, inheritance, and dynamic target generation.

A bake file defines one or more 'target' blocks. Each target specifies: a name, context, dockerfile, platforms, tags, cache-from/cache-to, and args.

Variables and functions in HCL make bake files DRY. Define common values (like image_registry, platforms, cache_ref) at the top and reference them across targets.

docker-bake.hclHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# docker-bake.hcl — Declarative build configuration
variable "IMAGE_REGISTRY" {
  default = "registry.example.com"
}
variable "SHA" {
  default = "latest"
}
variable "PLATFORMS" {
  default = ["linux/amd64", "linux/arm64"]
}

function "cache_config" {
  params = ["ref"]
  result = {
    cache-from = ["type=registry,ref=${ref}:cache"]
    cache-to   = ["type=registry,ref=${ref}:cache,mode=max"]
  }
}

target "api" {
  context    = "./services/api"
  dockerfile = "Dockerfile"
  tags = [
    "${var.IMAGE_REGISTRY}/api:${var.SHA}",
    "${var.IMAGE_REGISTRY}/api:latest"
  ]
  platforms = var.PLATFORMS
  cache-from = cache_config("${var.IMAGE_REGISTRY}/api").cache-from
  cache-to   = cache_config("${var.IMAGE_REGISTRY}/api").cache-to
}

target "worker" {
  context    = "./services/worker"
  dockerfile = "Dockerfile"
  tags = [
    "${var.IMAGE_REGISTRY}/worker:${var.SHA}",
    "${var.IMAGE_REGISTRY}/worker:latest"
  ]
  platforms    = var.PLATFORMS
  cache-from   = cache_config("${var.IMAGE_REGISTRY}/worker").cache-from
  cache-to     = cache_config("${var.IMAGE_REGISTRY}/worker").cache-to
}

group "default" {
  targets = ["api", "worker"]
}
Run All Targets or a Single Target
docker buildx bake builds the default group. Use docker buildx bake api worker to build specific targets. Use docker buildx bake --print to preview the execution plan without building.
Production Insight
A team managing 12 microservices in a monorepo replaced a 400-line Makefile with a 60-line bake.hcl. Adding a new microservice now takes 3 lines instead of 30 lines of Makefile boilerplate.
Key Takeaway
Docker Bake replaces imperative build scripts with declarative target definitions. Variables, functions, and inheritance keep the bake file DRY.
docker-bake-buildx THECODEFORGE.IO Docker Buildx Layered Architecture From HCL declaration to remote builder execution Declarative Layer docker-bake.hcl | Target Groups | Variable Blocks Build Driver docker-container | kubernetes | remote Platform Resolution BUILDPLATFORM | TARGETPLATFORM | QEMU Emulation Cache Layer Registry Cache | GHA Cache | Inline Cache Output Layer Multi-Arch Manifest | Image Push | Local Export THECODEFORGE.IO
thecodeforge.io
Docker Bake Buildx

Multi-Platform Builds with BUILDPLATFORM and TARGETPLATFORM

Building Docker images for multiple CPU architectures (amd64, arm64, arm/v7) is required for mixed-architecture Kubernetes clusters. Buildx supports two approaches: emulation (QEMU) and cross-compilation. QEMU emulates the target architecture — it works for any Dockerfile but is slow for compiled languages. Cross-compilation runs the build natively and produces a binary for the target architecture — it's fast but requires platform-aware Dockerfiles.

The key is BUILDPLATFORM and TARGETPLATFORM ARGs, automatically provided by BuildKit when using docker buildx build --platform. These ARGs are only available with Buildx, not legacy docker build.

In the Dockerfile, use ARG TARGETPLATFORM and ARG BUILDPLATFORM to set platform-specific build variables.

Dockerfile.cross-compileDOCKERFILE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# syntax=docker/dockerfile:1
FROM golang:1.22-alpine AS builder

ARG TARGETPLATFORM
ARG BUILDPLATFORM

RUN echo "Building for $TARGETPLATFORM on $BUILDPLATFORM"

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .

RUN GOOS=$(echo ${TARGETPLATFORM} | cut -d/ -f1) && \
    GOARCH=$(echo ${TARGETPLATFORM} | cut -d/ -f2) && \
    CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/server ./cmd/server

FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
Multi-Platform Build Strategies
  • BUILDPLATFORM = the machine running Docker (e.g., linux/amd64)
  • TARGETPLATFORM = the architecture the image is for (e.g., linux/arm64)
  • Use these ARGs to install platform-specific packages or set compiler flags
  • Test each platform individually before building multi-arch manifests
Production Insight
A team using QEMU emulation for Go arm64 builds added 8 minutes per build. Switching to cross-compilation with TARGETPLATFORM ARGs reduced that to 30 seconds.
Key Takeaway
Multi-platform builds require platform-aware Dockerfiles. Use BUILDPLATFORM/TARGETPLATFORM ARGs for cross-compilation in compiled languages.

Cache Configuration: Registry, GHA, and Inline Cache

Cache configuration is the single biggest lever for CI build speed. Buildx supports four cache backends: registry (store cache alongside the image in the registry), gha (GitHub Actions cache), inline (embed cache into the image), and local (local filesystem cache).

type=registry,mode=max is the recommended default for production CI. It pushes build cache layers to the registry as a separate cache image. The mode=max option caches all layers, not just those exported to the final image.

type=gha uses the built-in GHA cache service — faster than registry cache but limited to 10 GB per repository. Ideal for open-source projects.

type=inline embeds cache into the image manifest. Only caches final image layers — use only when registry or GHA cache is unavailable.

buildx-cache-config.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Registry cache (recommended for production)
docker buildx build \
  --cache-from type=registry,ref=myapp:cache \
  --cache-to type=registry,ref=myapp:cache,mode=max \
  -t myapp:latest --push .

# GitHub Actions cache (for GHA runners)
docker buildx build \
  --cache-from type=gha \
  --cache-to type=gha,mode=max \
  -t myapp:latest --push .

# Inline cache (fallback, no separate storage)
docker buildx build \
  --cache-from type=registry,ref=myapp:latest \
  --cache-to type=inline \
  -t myapp:latest --push .

# In bake.hcl
target "myapp" {
  cache-from = ["type=registry,ref=myapp:cache"]
  cache-to   = ["type=registry,ref=myapp:cache,mode=max"]
}
Cache Mode: max vs min Matters
mode=max caches ALL layers including intermediate build stages. mode=min only caches layers in the final image. Use mode=max for CI pipelines and mode=min if concerned about cache storage costs.
Production Insight
Switching from inline cache to registry cache with mode=max reduced CI build time from 8 minutes to 2 minutes for a multi-stage Node.js application. The inline cache couldn't cache the intermediate build stage where npm install runs.
Key Takeaway
Use type=registry,mode=max for production CI cache. It caches all build layers across builds and runners.
Cache Backend Trade-offs Registry vs GHA vs Inline cache for multi-platform builds Registry Cache GHA Cache Setup Complexity Simple: just set cache-to/from Requires GHA cache action and token Cache Sharing Global across CI and local Scoped to GitHub Actions workflow Cache Size Limit Depends on registry storage 10 GB per repository (GHA limit) Multi-Platform Support Full support per platform Shared cache, may mix platforms Cost Registry storage costs apply Free within GHA quota THECODEFORGE.IO
thecodeforge.io
Docker Bake Buildx

Matrix Builds: Building Multiple Targets Across Multiple Platforms

Docker Bake supports dynamic target generation using for_each (HCL). This is useful when you have multiple services that follow the same build pattern — define the pattern once, and Bake generates a target for each service × platform combination.

In HCL, use a locals block to define the services, then a target block with for_each to generate targets dynamically. Each target gets its context, tags, and platforms from the local variables.

This pattern eliminates duplication entirely: adding a new service is a one-line addition to the services list.

docker-bake-matrix.hclHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# docker-bake-matrix.hcl
# Matrix build: generate targets for multiple services

variable "IMAGE_REGISTRY" {
  default = "registry.example.com"
}
variable "SHA" {
  default = "latest"
}

locals {
  services = {
    api   = { path = "./services/api",   port = 8080 }
    web   = { path = "./services/web",   port = 3000 }
    queue = { path = "./services/queue", port = 5000 }
  }
  platforms = ["linux/amd64", "linux/arm64"]
}

target "default" {
  for_each = local.services
  name     = each.key
  context  = each.value.path
  tags = [
    "${var.IMAGE_REGISTRY}/${each.key}:${var.SHA}",
    "${var.IMAGE_REGISTRY}/${each.key}:latest"
  ]
  platforms   = local.platforms
  cache-from  = ["type=registry,ref=${var.IMAGE_REGISTRY}/${each.key}:cache"]
  cache-to    = ["type=registry,ref=${var.IMAGE_REGISTRY}/${each.key}:cache,mode=max"]
}
Name Targets Explicitly in for_each
The name field determines the target name in the output and the Docker tag. Use meaningful service names that match your application. The target name appears in build logs.
Production Insight
A team with 15 Go microservices used a matrix bake file. The matrix bake file was 25 lines. Without matrix generation, the same configuration would require 150+ lines of repeated target blocks.
Key Takeaway
Matrix builds with HCL for_each eliminate build configuration duplication. Define the service list once, and Bake generates targets automatically.

Docker Build Cloud and Remote Builders

Docker Build Cloud is Docker's managed build service that provides native AMD64 and ARM64 build infrastructure. It solves two problems: (1) local machines that can't build for all target platforms, and (2) CI runners with limited resources.

Build Cloud provides cloud-hosted builders with native architecture support. When you run docker buildx build --platform linux/amd64,linux/arm64 --builder cloud-<name>, Build Cloud splits the build across native AMD64 and ARM64 machines. No emulation needed.

Beyond Build Cloud, Buildx supports connecting to any Docker host as a remote builder via SSH: docker buildx create --name remote --driver docker-container ssh://user@host.

docker-build-cloud.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Set up Docker Build Cloud
docker buildx create --driver cloud myorg/mybuilder

# Use the cloud builder for multi-platform build
docker buildx build \
  --builder cloud-myorg-mybuilder \
  --platform linux/amd64,linux/arm64 \
  -t myapp:latest --push .

# Set cloud builder as default
export BUILDX_BUILDER=cloud-myorg-mybuilder

# Or use in bake (add builder field)
target "myapp" {
  builder = "cloud-myorg-mybuilder"
}

# Remote builder via SSH
docker buildx create \
  --name remote-builder \
  --driver docker-container \
  ssh://user@build-server.example.com

# Use remote builder
docker buildx build --builder remote-builder -t myapp:latest --push .
Build Cloud vs Local QEMU: Speed
For a Go application on an Intel Mac: local QEMU emulation for arm64 takes 5-8 minutes. Docker Build Cloud native arm64 build: 45 seconds.
Production Insight
A startup with a single Intel Mac Mini CI runner was spending 12 minutes per build on arm64 emulation. Switching to Docker Build Cloud reduced multi-platform build time to 3 minutes.
Key Takeaway
Docker Build Cloud provides native AMD64 and ARM64 build infrastructure, eliminating slow QEMU emulation for multi-platform builds.
● Production incidentPOST-MORTEMseverity: high

Monorepo Makefile Grew to 400 Lines — One Bake File Replaced It All

Symptom
CI pipeline for the monorepo took 45 minutes. Adding a new microservice required copying 30 lines of Makefile. Build order was hardcoded, so fast builds waited for slow builds even when independent.
Assumption
The team assumed the Makefile was fine — it had grown organically and everyone knew how to add to it. Nobody measured how much time was lost to sequential builds.
Root cause
The Makefile had hardcoded targets with explicit docker build calls. Each target ran docker build for a single platform (amd64). There was no parallelism. Adding a new service meant copying the pattern and changing variables — any mistake broke the entire build.
Fix
1. Replaced the 400-line Makefile with a 50-line docker-bake.hcl file. 2. Defined each microservice as a bake target with proper dependencies. 3. Added multi-platform support (amd64 + arm64) with a single platforms array. 4. Configured registry cache so rebuilds reuse layers across builds. 5. Ran docker buildx bake --push in CI — targets ran in parallel automatically. 6. CI time dropped from 45 minutes to 12 minutes.
Key lesson
  • Imperative build scripts don't scale beyond a few images. Bake's declarative model eliminates duplication.
  • Bake's automatic parallelization is its biggest time-saving feature.
  • Adding a new service to a bake file is adding 3-5 lines — no variable collision risk.
  • Cache configuration should be centralized in the bake file, not repeated across CI workflows.
Production debug guideSystematic debugging for failed bake targets, multi-platform build failures, cache problems, and remote builder issues.5 entries
Symptom · 01
docker buildx bake fails with 'no such host' when using remote builder.
Fix
Verify the builder is running: docker buildx ls. If using SSH, test the SSH connection first: ssh user@host docker info.
Symptom · 02
Multi-platform build fails for arm64 with 'exec format error'.
Fix
The arm64 build used QEMU emulation instead of native cross-compilation. Use BUILDPLATFORM and TARGETPLATFORM ARGs. Install QEMU binfmt: docker run --privileged --rm tonistiigi/binfmt --install all.
Symptom · 03
Bake targets that should run in parallel execute sequentially.
Fix
Check the dependencies field. If one target implicitly depends on another, Bake serializes them. Use --print to see the execution plan.
Symptom · 04
Cache from registry doesn't speed up subsequent builds.
Fix
Use mode=max in cache-to config. The mode=max option caches all layers, not just the final image. Verify the cache image is pushed to the same registry.
Symptom · 05
docker buildx bake --push pushes the wrong tags or misses tags.
Fix
The tags field must list ALL desired tags. Bake does not automatically add :latest. Use tags = ["myapp:${sha}", "myapp:latest"].
FeatureMakefile + docker buildDocker Bake (HCL)docker-compose build
Parallel targetsManual (background jobs)Automatic (DAG resolution)Limited (--parallel flag)
Multi-platformManual loopBuilt-in (platforms array)No
Cache configManual --cache-from flagsDeclarative cache-from/toLimited
Matrix generationManual for loopsBuilt-in (for_each)No
Dependency orderingManual (target prerequisites)Automatic (depends_on)No
Multi-arch manifestManual manifest-toolAutomatic (buildx)No
Remote buildersManual (DOCKER_HOST)Built-in (builder field)Via compose context
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
docker-bake.hclvariable "IMAGE_REGISTRY" {Writing Your First docker-bake.hcl File
Dockerfile.cross-compileFROM golang:1.22-alpine AS builderMulti-Platform Builds with BUILDPLATFORM and TARGETPLATFORM
buildx-cache-config.shdocker buildx build \Cache Configuration
docker-bake-matrix.hclvariable "IMAGE_REGISTRY" {Matrix Builds
docker-build-cloud.shdocker buildx create --driver cloud myorg/mybuilderDocker Build Cloud and Remote Builders

Key takeaways

1
Docker Bake replaces Makefiles for multi-image builds. Declare targets in bake.hcl, and Bake handles parallelism, dependencies, and multi-platform execution.
2
Multi-platform builds require platform-aware Dockerfiles using BUILDPLATFORM/TARGETPLATFORM ARGs for cross-compilation.
3
Registry cache with mode=max is the most effective cache backend for production CI
it caches all intermediate build stages.
4
Matrix builds with HCL for_each eliminate duplication for microservice architectures.
5
Docker Build Cloud and remote SSH builders extend Buildx beyond local machine constraints.

Common mistakes to avoid

4 patterns
×

Using inline cache in CI when registry cache would be more effective.

×

Not using BUILDPLATFORM/TARGETPLATFORM in the Dockerfile, defaulting to slow QEMU emulation.

×

Defining duplicate target blocks for each service instead of using for_each.

×

Building without --push when using multi-platform builds.

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Explain how Docker Bake improves on using a Makefile for building multip...
Q02JUNIOR
How would you configure a multi-platform build for a Go application on a...
Q03JUNIOR
Compare registry cache, GHA cache, and inline cache for Buildx.
Q04JUNIOR
What is Docker Build Cloud and when would you use it?
Q01 of 04JUNIOR

Explain how Docker Bake improves on using a Makefile for building multiple Docker images.

ANSWER
Docker Bake provides declarative target definitions instead of imperative shell commands. Key advantages: automatic dependency resolution (parallel execution of independent targets), matrix support (for_each generates targets dynamically), multi-platform support (single platforms array), centralized cache configuration, and Buildx integration. Makefiles require manual parallelization and fragile variable passing.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between docker buildx bake and docker-compose build?
02
Can I use Docker Bake without docker buildx?
03
How do I pass build arguments (--build-arg) in a bake file?
04
What happens when I use `docker buildx bake --push` with multiple platforms?
05
How do I debug a bake file without running the full build?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 11, 2026
last updated
1,750
articles · all by Naren
🔥

That's Docker. Mark it forged?

3 min read · try the examples if you haven't

Previous
Docker CI/CD for Kubernetes
40 / 41 · Docker
Next
Kaniko vs Buildah vs Docker-in-Docker