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..
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Production DevOps experience
- ✓Deep understanding of the tool's internals
- ✓Experience debugging distributed systems
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.
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 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.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.
- 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
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.
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.type=registry,mode=max for production CI cache. It caches all build layers across builds and runners.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.
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.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.
Monorepo Makefile Grew to 400 Lines — One Bake File Replaced It All
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.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.- 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.
docker buildx ls. If using SSH, test the SSH connection first: ssh user@host docker info.docker run --privileged --rm tonistiigi/binfmt --install all.dependencies field. If one target implicitly depends on another, Bake serializes them. Use --print to see the execution plan.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.tags field must list ALL desired tags. Bake does not automatically add :latest. Use tags = ["myapp:${sha}", "myapp:latest"].| File | Command / Code | Purpose |
|---|---|---|
| docker-bake.hcl | variable "IMAGE_REGISTRY" { | Writing Your First docker-bake.hcl File |
| Dockerfile.cross-compile | FROM golang:1.22-alpine AS builder | Multi-Platform Builds with BUILDPLATFORM and TARGETPLATFORM |
| buildx-cache-config.sh | docker buildx build \ | Cache Configuration |
| docker-bake-matrix.hcl | variable "IMAGE_REGISTRY" { | Matrix Builds |
| docker-build-cloud.sh | docker buildx create --driver cloud myorg/mybuilder | Docker Build Cloud and Remote Builders |
Key takeaways
mode=max is the most effective cache backend for production CIfor_each eliminate duplication for microservice architectures.Common mistakes to avoid
4 patternsUsing 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 Questions on This Topic
Explain how Docker Bake improves on using a Makefile for building multiple Docker images.
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Docker. Mark it forged?
3 min read · try the examples if you haven't