Docker Bake in Practice — Part 1: From Bash Scripts to Declarative Builds

Docker Bake in Practice — Part 1: From Bash Scripts to Declarative Builds

Docker Bake 实战 — 第一部分:从 Bash 脚本到声明式构建

The first of a two-part series. Part 1 covers the fundamentals: what Bake is, why it exists, and the features that make it worth adopting. Part 2 will cover CI/CD integrations — Cloud Build, GitHub Actions, GitLab CI, and Dagger. 这是两篇系列文章的第一篇。第一部分涵盖了基础知识:什么是 Bake,它为何存在,以及哪些特性使其值得采用。第二部分将涵盖 CI/CD 集成——包括 Cloud Build、GitHub Actions、GitLab CI 和 Dagger。

Why this article exists For the past two years I’ve been giving a talk about Docker Bake at conferences across France and Morocco — DevLille, DevFest Toulouse, DevFest Lyon, Devoxx Morocco, and Cloud Native Days France. Every time I deliver it, the same thing happens: people come up afterwards and say “I had no idea Bake could do that” or “I’ve been writing 200-line bash scripts to do exactly this.” 为什么会有这篇文章 在过去的两年里,我一直在法国和摩洛哥的各大技术会议上分享关于 Docker Bake 的演讲,包括 DevLille、DevFest Toulouse、DevFest Lyon、Devoxx Morocco 和 Cloud Native Days France。每次演讲后,总会有人走过来对我说:“我完全不知道 Bake 还能做这些”或者“我为了实现同样的功能,写了 200 行的 bash 脚本”。

If you prefer to read at your own pace, with copy-pasteable snippets, this article is for you. All the code in this article comes from my companion repository — each section links to the relevant file so you can run the examples yourself. 如果你喜欢按照自己的节奏阅读,并希望获取可直接复制粘贴的代码片段,那么这篇文章非常适合你。本文中的所有代码均来自我的配套仓库,每个部分都链接到了相关文件,以便你可以亲自运行这些示例。

The foundation: BuildKit, Buildx, and Bake

基础:BuildKit、Buildx 和 Bake

Before we touch a single HCL file, let’s clear up the layering. These three names get thrown around as if they were interchangeable, but they sit on top of each other. 在接触任何 HCL 文件之前,让我们先理清它们的层级关系。这三个名称经常被混用,仿佛它们可以互换,但实际上它们是层层叠加的。

+----------------------------------------------------+
| Bake (declarative orchestration)                   |
| docker buildx bake -f file.hcl                     |
+----------------------------------------------------+
| Buildx (CLI plugin / frontend)                     |
| docker buildx build ...                            |
+----------------------------------------------------+
| BuildKit (build engine / backend)                  |
| parallel stages, cache mounts, multi-platform      |
+----------------------------------------------------+

BuildKit is the modern Docker build engine. It replaced the legacy builder a few years ago and is now the default in Docker Engine 23+. BuildKit is the piece doing the actual work: it parses your Dockerfile, builds stages in parallel where it can, manages the cache, handles multi-platform builds, mount caches, secrets, SSH forwarding, and so on. BuildKit 是现代 Docker 构建引擎。它在几年前取代了旧版构建器,现在已成为 Docker Engine 23+ 的默认引擎。BuildKit 是实际执行工作的组件:它解析 Dockerfile,尽可能并行构建阶段,管理缓存,处理多平台构建,以及挂载缓存、密钥、SSH 转发等。

Buildx is a Docker CLI plugin that exposes BuildKit’s features through a friendlier interface. When you type docker buildx build ..., you’re using Buildx as the frontend and BuildKit as the engine. Buildx also manages builders — named BuildKit instances you can swap between (local, remote, container-driven). Buildx 是一个 Docker CLI 插件,通过更友好的界面展示了 BuildKit 的功能。当你输入 docker buildx build ... 时,你是在使用 Buildx 作为前端,而 BuildKit 作为引擎。Buildx 还负责管理构建器(builders)——即你可以在其间切换的命名 BuildKit 实例(本地、远程或容器驱动)。

Bake is a subcommand of Buildx: docker buildx bake. It takes one or more declarative files (HCL, JSON, or a Compose file) and orchestrates many builds at once. Think of it as docker-compose but for building instead of running. You define your images once, in one place, and Bake builds them all — in parallel, with shared variables, inheritance, matrices, and groups. Bake 是 Buildx 的一个子命令:docker buildx bake。它接收一个或多个声明式文件(HCL、JSON 或 Compose 文件),并同时编排多个构建任务。你可以把它想象成构建版的 docker-compose。你只需在一个地方定义一次镜像,Bake 就能将它们全部构建出来——支持并行、共享变量、继承、矩阵和分组。

The traditional approach: bash scripts

传统方法:Bash 脚本

To appreciate why Bake exists, let’s start with the pain. Imagine you have two images to build and push: an app and an infra. Multi-platform (amd64 + arm64), with provenance and SBOM attestations. Here’s the straightforward bash version: 为了理解 Bake 存在的意义,让我们先看看痛点。想象一下,你需要构建并推送两个镜像:一个应用(app)和一个基础设施(infra)。它们需要支持多平台(amd64 + arm64),并包含来源证明(provenance)和 SBOM 证明。以下是直接的 bash 实现:

#!/usr/bin/env bash
set -euo pipefail

docker build \
  --platform linux/amd64,linux/arm64 \
  --file app/Dockerfile \
  --tag "${REPO_URL}/app_bake:${IMAGE_TAG_VERSION_APP}" \
  --provenance=true \
  --sbom=true \
  --push \
  .

docker build \
  --platform linux/amd64,linux/arm64 \
  --file infra/Dockerfile \
  --tag "${REPO_URL}/infra_bake:${IMAGE_TAG_VERSION_INFRA}" \
  --provenance=true \
  --sbom=true \
  --push \
  .

This works. But notice four things:

  1. It’s sequential. The infra build doesn’t start until the app build finishes — even though they’re completely independent.
  2. It duplicates everything. The platform list, the attestations… 这确实能用。但请注意以下四点:
  3. 它是串行的。即使 app 和 infra 构建完全独立,infra 的构建也必须等到 app 构建完成后才能开始。
  4. 它存在大量重复。平台列表、证明配置等都在重复……