mirror of https://github.com/docker/docs.git
build: add bake guide
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
This commit is contained in:
parent
939449bf22
commit
80aea3476b
Binary file not shown.
After Width: | Height: | Size: 235 KiB |
|
@ -0,0 +1,518 @@
|
|||
---
|
||||
title: Mastering multi-platform builds, testing, and more with Docker Buildx Bake
|
||||
linkTitle: Mastering Docker Buildx Bake
|
||||
description: >
|
||||
Learn how to manage simple and complex build configurations with Buildx Bake.
|
||||
summary: >
|
||||
Learn to automate Docker builds and testing with declarative configurations using Buildx Bake.
|
||||
languages: [go]
|
||||
subjects: [devops]
|
||||
levels: [advanced]
|
||||
params:
|
||||
time: 30 minutes
|
||||
featured: true
|
||||
image: /images/guides/bake.webp
|
||||
---
|
||||
|
||||
This guide demonstrates how to simplify and automate the process of building
|
||||
images, testing, and generating build artifacts using Docker Buildx Bake. By
|
||||
defining build configurations in a declarative `docker-bake.hcl` file, you can
|
||||
eliminate manual scripts and enable efficient workflows for complex builds,
|
||||
testing, and artifact generation.
|
||||
|
||||
## Assumptions
|
||||
|
||||
This guide assumes that you're familiar with:
|
||||
|
||||
- Docker
|
||||
- [Buildx](/manuals/build/concepts/overview.md#buildx)
|
||||
- [BuildKit](/manuals/build/concepts/overview.md#buildkit)
|
||||
- [Multi-stage builds](/manuals/build/building/multi-stage.md)
|
||||
- [Multi-platform builds](/manuals/build/building/multi-platform.md)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A recent version of Docker is installed on your machine.
|
||||
- Git is installed for cloning repositories.
|
||||
- You're using the [containerd](/manuals/desktop/containerd.md) image store.
|
||||
|
||||
## Introduction
|
||||
|
||||
This guide uses an example project to demonstrate how Docker Buildx Bake can
|
||||
streamline your build and test workflows. This repository includes both a
|
||||
Dockerfile and `docker-bake.hcl`, giving you a ready-to-use setup to try out
|
||||
Bake commands.
|
||||
|
||||
Start by cloning the example repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dvdksn/bakeme.git
|
||||
cd bakeme
|
||||
```
|
||||
|
||||
The Bake file, `docker-bake.hcl`, defines the build targets in a declarative
|
||||
syntax, using targets and groups, allowing you to manage complex builds
|
||||
efficiently.
|
||||
|
||||
Here's what the Bake file looks like out-of-the-box:
|
||||
|
||||
```hcl
|
||||
target "default" {
|
||||
target = "image"
|
||||
tags = [
|
||||
"bakeme:latest",
|
||||
]
|
||||
attest = [
|
||||
"type=provenance,mode=max",
|
||||
"type=sbom",
|
||||
]
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
"linux/riscv64",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `target` keyword defines a build target for Bake. The `default` target
|
||||
defines the target to build when no specific target is specified on the command
|
||||
line. Here's a quick summary of the options for the `default` target:
|
||||
|
||||
- `target`: The target build stage in the Dockerfile.
|
||||
- `tags`: Tags to assign to the image.
|
||||
- `attest`: [Attestations](/manuals/build/metadata/attestations/_index.md) to attach to the image.
|
||||
|
||||
> [!TIP]
|
||||
> The attestations provide metadata such as build provenance, which tracks
|
||||
> the source of the image's build, and an SBOM (Software Bill of Materials),
|
||||
> useful for security audits and compliance.
|
||||
|
||||
- `platforms`: Platform variants to build.
|
||||
|
||||
To execute this build, simply run the following command in the root of the
|
||||
repository:
|
||||
|
||||
```console
|
||||
$ docker buildx bake
|
||||
```
|
||||
|
||||
With Bake, you avoid long, hard-to-remember command-line incantations,
|
||||
simplifying build configuration management by replacing manual, error-prone
|
||||
scripts with a structured configuration file.
|
||||
|
||||
For contrast, here's what this build command would look like without Bake:
|
||||
|
||||
```console
|
||||
$ docker buildx build \
|
||||
--target=image \
|
||||
--tag=bakeme:latest \
|
||||
--provenance=true \
|
||||
--sbom=true \
|
||||
--platform=linux/amd64,linux/arm64,linux/riscv64 \
|
||||
.
|
||||
```
|
||||
|
||||
## Testing and linting
|
||||
|
||||
Bake isn't just for defining build configurations, and running builds. You can
|
||||
also use Bake to run your tests, effectively using BuildKit as a task runner.
|
||||
Running your tests in containers is great for ensuring reproducible results.
|
||||
This section shows how to add two types of tests:
|
||||
|
||||
- Unit testing with `go test`.
|
||||
- Linting for style violations with `golangci-lint`.
|
||||
|
||||
In Test-Driven Development (TDD) fashion, start by adding a new `test` target
|
||||
to the Bake file:
|
||||
|
||||
```hcl
|
||||
target "test" {
|
||||
target = "test"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Using the `type=cacheonly` ensures that the build output is effectively
|
||||
> discarded; the layers are saved to BuildKit's cache, but Buildx will not
|
||||
> attempt to load the result to the Docker Engine's image store.
|
||||
>
|
||||
> For test runs, you don't need to export the build output — only the test
|
||||
> execution matters.
|
||||
|
||||
To execute this Bake target, run `docker buildx bake test`. At this time,
|
||||
you'll receive an error indicating that the `test` stage does not exist in the
|
||||
Dockerfile.
|
||||
|
||||
```console
|
||||
$ docker buildx bake bake test
|
||||
[+] Building 1.2s (6/6) FINISHED
|
||||
=> [internal] load local bake definitions
|
||||
...
|
||||
ERROR: failed to solve: target stage "test" could not be found
|
||||
```
|
||||
|
||||
To satisfy this target, add the corresponding Dockerfile target. The `test`
|
||||
stage here is based on the same base stage as the build stage.
|
||||
|
||||
```dockerfile
|
||||
FROM base AS test
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go test .
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> The [`--mount=type=cache` directive](/manuals/build/cache/optimize.md#use-cache-mounts)
|
||||
> caches Go modules between builds, improving build performance by avoiding the
|
||||
> need to re-download dependencies. This shared cache ensures that the same
|
||||
> dependency set is available across build, test, and other stages.
|
||||
|
||||
Now, running the `test` target with Bake will evaluate the unit tests for this
|
||||
project. If you want to verify that it works, you can make an arbitrary change
|
||||
to `main_test.go` to cause the test to fail.
|
||||
|
||||
Next, to enable linting, add another target to the Bake file, named `lint`:
|
||||
|
||||
```hcl
|
||||
target "lint" {
|
||||
target = "lint"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
```
|
||||
|
||||
And in the Dockerfile, add the build stage. This stage will use the official
|
||||
`golangci-lint` image on Docker Hub.
|
||||
|
||||
> [!TIP]
|
||||
> Because this stage relies on executing an external dependency, it's generally
|
||||
> a good idea to define the version you want to use as a build argument. This
|
||||
> lets you more easily manage version upgrades in the future by collocating
|
||||
> dependency versions to the beginning of the Dockerfile.
|
||||
|
||||
```dockerfile {hl_lines=[2,"6-8"]}
|
||||
ARG GO_VERSION="1.23"
|
||||
ARG GOLANGCI_LINT_VERSION="1.61"
|
||||
|
||||
#...
|
||||
|
||||
FROM golangci/golangci-lint:v${GOLANGCI_LINT_VERSION}-alpine AS lint
|
||||
RUN --mount=target=.,rw \
|
||||
golangci-lint run
|
||||
```
|
||||
|
||||
Lastly, to enable running both tests simultaneously, you can use the `groups`
|
||||
construct in the Bake file. A group can specify multiple targets to run with a
|
||||
single invocation.
|
||||
|
||||
```hcl
|
||||
group "validate" {
|
||||
targets = ["test", "lint"]
|
||||
}
|
||||
```
|
||||
|
||||
Now, running both tests is as simple as:
|
||||
|
||||
```console
|
||||
$ docker buildx bake validate
|
||||
```
|
||||
|
||||
## Building variants
|
||||
|
||||
Sometimes you need to build more than one version of a program. The following
|
||||
example uses Bake to build separate "release" and "debug" variants of the
|
||||
program, using [matrices](/manuals/build/bake/matrices.md). Using matrices lets
|
||||
you run parallel builds with different configurations, saving time and ensuring
|
||||
consistency.
|
||||
|
||||
A matrix expands a single build into multiple builds, each representing a
|
||||
unique combination of matrix parameters. This means you can orchestrate Bake
|
||||
into building both the production and development build of your program in
|
||||
parallel, with minimal configuration changes.
|
||||
|
||||
The example project for this guide is set up to use a build-time option to
|
||||
conditionally enable debug logging and tracing capabilities.
|
||||
|
||||
- If you compile the program with `go build -tags="debug"`, the additional
|
||||
logging and tracing capabilities are enabled (development mode).
|
||||
- If you build without the `debug` tag, the program is compiled with a default
|
||||
logger (production mode).
|
||||
|
||||
Update the Bake file by adding a matrix attribute which defines the variable
|
||||
combinations to build:
|
||||
|
||||
```diff {title="docker-bake.hcl"}
|
||||
target "default" {
|
||||
+ matrix = {
|
||||
+ mode = ["release", "debug"]
|
||||
+ }
|
||||
+ name = "image-${mode}"
|
||||
target = "image"
|
||||
```
|
||||
|
||||
The `matrix` attribute defines the variants to build ("release" and "debug").
|
||||
The `name` attribute defines how the matrix gets expanded into multiple
|
||||
distinct build targets. In this case, it's relatively simple. The matrix
|
||||
attribute expands the build into two workflows: `image-release` and
|
||||
`image-debug`, each using different configuration parameters.
|
||||
|
||||
Next, when building the development variant, we'll pass in a `BUILD_TAGS`
|
||||
argument with the value of the matrix variable, which we'll later consume in
|
||||
the Dockerfile.
|
||||
|
||||
```diff {title="docker-bake.hcl"}
|
||||
target = "image"
|
||||
+ args = {
|
||||
+ BUILD_TAGS = mode
|
||||
+ }
|
||||
tags = [
|
||||
```
|
||||
|
||||
You'll also want to change how the image tags are assigned to these builds.
|
||||
Currently, both matrix paths would generate the same image tag names, and
|
||||
overwrite each other. Update the `tags` attribute use a conditional operator to
|
||||
set the tag depending on the matrix variable value.
|
||||
|
||||
```diff {title="docker-bake.hcl"}
|
||||
tags = [
|
||||
- "bakeme:latest",
|
||||
+ mode == "release" ? "bakeme:latest" : "bakeme:dev"
|
||||
]
|
||||
```
|
||||
|
||||
- If `mode` is `release`, the tag name is `bakeme:latest`
|
||||
- If `mode` is `debug`, the tag name is `bakeme:dev`
|
||||
|
||||
Finally, update the Dockerfile to consume the `BUILD_TAGS` argument during the
|
||||
compilation stage. When the `-tags="${BUILD_TAGS}"` option evaluates to
|
||||
`-tags="debug"`, the compiler uses the `configureLogging` function in the
|
||||
[`debug.go`](https://github.com/dvdksn/bakeme/blob/75c8a41e613829293c4bd3fc3b4f0c573f458f42/debug.go#L1)
|
||||
file.
|
||||
|
||||
```diff {title=Dockerfile}
|
||||
# build compiles the program
|
||||
FROM base AS build
|
||||
-ARG TARGETOS TARGETARCH
|
||||
+ARG TARGETOS TARGETARCH BUILD_TAGS
|
||||
ENV GOOS=$TARGETOS
|
||||
ENV GOARCH=$TARGETARCH
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
- go build -o "/usr/bin/bakeme" .
|
||||
+ go build -tags="${BUILD_TAGS}" -o "/usr/bin/bakeme" .
|
||||
```
|
||||
|
||||
That's all. With these changes, your `docker buildx bake` command now builds
|
||||
two multi-platform image variants. You can introspect the canonical build
|
||||
configuration that Bake generates using the `docker buildx bake --print`
|
||||
command. Running this command shows that Bake will run a `default` group with
|
||||
two targets with different build arguments and image tags.
|
||||
|
||||
```json {collapse=true}
|
||||
{
|
||||
"group": {
|
||||
"default": {
|
||||
"targets": ["image-release", "image-debug"]
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"image-debug": {
|
||||
"attest": ["type=provenance,mode=max", "type=sbom"],
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"BUILD_TAGS": "debug"
|
||||
},
|
||||
"tags": ["bakeme:dev"],
|
||||
"target": "image",
|
||||
"platforms": ["linux/amd64", "linux/arm64", "linux/riscv64"]
|
||||
},
|
||||
"image-release": {
|
||||
"attest": ["type=provenance,mode=max", "type=sbom"],
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"BUILD_TAGS": "release"
|
||||
},
|
||||
"tags": ["bakeme:latest"],
|
||||
"target": "image",
|
||||
"platforms": ["linux/amd64", "linux/arm64", "linux/riscv64"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Factoring in all of the platform variants as well, this means that the build
|
||||
configuration generates 6 different images.
|
||||
|
||||
```console
|
||||
$ docker buildx bake
|
||||
$ docker image ls --tree
|
||||
|
||||
IMAGE ID DISK USAGE CONTENT SIZE USED
|
||||
bakeme:dev f7cb5c08beac 49.3MB 28.9MB
|
||||
├─ linux/riscv64 0eae8ba0367a 9.18MB 9.18MB
|
||||
├─ linux/arm64 56561051c49a 30MB 9.89MB
|
||||
└─ linux/amd64 e8ca65079c1f 9.8MB 9.8MB
|
||||
|
||||
bakeme:latest 20065d2c4d22 44.4MB 25.9MB
|
||||
├─ linux/riscv64 7cc82872695f 8.21MB 8.21MB
|
||||
├─ linux/arm64 e42220c2b7a3 27.1MB 8.93MB
|
||||
└─ linux/amd64 af5b2dd64fde 8.78MB 8.78MB
|
||||
```
|
||||
|
||||
## Exporting build artifacts
|
||||
|
||||
Exporting build artifacts like binaries can be useful for deploying to
|
||||
environments without Docker or Kubernetes. For example, if your programs are
|
||||
meant to be run on user's local machine.
|
||||
|
||||
> [!TIP]
|
||||
> The techniques discussed in this section can be applied not only to build
|
||||
> output like binaries, but to any type of artifacts, such as test reports.
|
||||
|
||||
With programming languages like Go and Rust where the compiled binaries are
|
||||
usually portable, creating alternate build targets for exporting only the
|
||||
binary is trivial. All you need to do is add an empty stage in the Dockerfile
|
||||
containing nothing but the binary that you want to export.
|
||||
|
||||
First, let's add a quick way to build a binary for your local platform and
|
||||
export it to `./build/local` on the local filesystem.
|
||||
|
||||
In the `docker-bake.hcl` file, create a new `bin` target. In this stage, set
|
||||
the `output` attribute to a local filesystem path. Buildx automatically detects
|
||||
that the output looks like a filepath, and exports the results to the specified
|
||||
path using the [local exporter](/manuals/build/exporters/local-tar.md).
|
||||
|
||||
```hcl
|
||||
target "bin" {
|
||||
target = "bin"
|
||||
output = ["build/bin"]
|
||||
platforms = ["local"]
|
||||
}
|
||||
```
|
||||
|
||||
Notice that this stage specifies a `local` platform. By default, if `platforms`
|
||||
is unspecified, builds target the OS and architecture of the BuildKit host. If
|
||||
you're using Docker Desktop, this often means builds target `linux/amd64` or
|
||||
`linux/arm64`, even if your local machine is macOS or Windows, because Docker
|
||||
runs in a Linux VM. Using the `local` platform forces the target platform to
|
||||
match your local environment.
|
||||
|
||||
Next, add the `bin` stage to the Dockerfile which copies the compiled binary
|
||||
from the build stage.
|
||||
|
||||
```dockerfile
|
||||
FROM scratch AS bin
|
||||
COPY --from=build "/usr/bin/bakeme" /
|
||||
```
|
||||
|
||||
Now you can export your local platform version of the binary with `docker
|
||||
buildx bake bin`. For example, on macOS, this build target generates an
|
||||
executable in the [Mach-O format](https://en.wikipedia.org/wiki/Mach-O) — the
|
||||
standard executable format for macOS.
|
||||
|
||||
```console
|
||||
$ docker buildx bake bin
|
||||
$ file ./build/bin/bakeme
|
||||
./build/bin/bakeme: Mach-O 64-bit executable arm64
|
||||
```
|
||||
|
||||
Next, let's add a target to build all of the platform variants of the program.
|
||||
To do this, you can [inherit](/manuals/build/bake/inheritance.md) the `bin`
|
||||
target that you just created, and extend it by adding the desired platforms.
|
||||
|
||||
```hcl
|
||||
target "bin-cross" {
|
||||
inherits = ["bin"]
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
"linux/riscv64",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Now, building the `bin-cross` target creates binaries for all platforms.
|
||||
Subdirectories are automatically created for each variant.
|
||||
|
||||
```console
|
||||
$ docker buildx bake bin-cross
|
||||
$ tree build/
|
||||
build/
|
||||
└── bin
|
||||
├── bakeme
|
||||
├── linux_amd64
|
||||
│ └── bakeme
|
||||
├── linux_arm64
|
||||
│ └── bakeme
|
||||
└── linux_riscv64
|
||||
└── bakeme
|
||||
|
||||
5 directories, 4 files
|
||||
```
|
||||
|
||||
To also generate "release" and "debug" variants, you can use a matrix just like
|
||||
you did with the default target. When using a matrix, you also need to
|
||||
differentiate the output directory based on the matrix value, otherwise the
|
||||
binary gets written to the same location for each matrix run.
|
||||
|
||||
```hcl
|
||||
target "bin-all" {
|
||||
inherits = ["bin-cross"]
|
||||
matrix = {
|
||||
mode = ["release", "debug"]
|
||||
}
|
||||
name = "bin-${mode}"
|
||||
args = {
|
||||
BUILD_TAGS = mode
|
||||
}
|
||||
output = ["build/bin/${mode}"]
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ rm -r ./build/
|
||||
$ docker buildx bake bin-all
|
||||
$ tree build/
|
||||
build/
|
||||
└── bin
|
||||
├── debug
|
||||
│ ├── linux_amd64
|
||||
│ │ └── bakeme
|
||||
│ ├── linux_arm64
|
||||
│ │ └── bakeme
|
||||
│ └── linux_riscv64
|
||||
│ └── bakeme
|
||||
└── release
|
||||
├── linux_amd64
|
||||
│ └── bakeme
|
||||
├── linux_arm64
|
||||
│ └── bakeme
|
||||
└── linux_riscv64
|
||||
└── bakeme
|
||||
|
||||
10 directories, 6 files
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Docker Buildx Bake streamlines complex build workflows, enabling efficient
|
||||
multi-platform builds, testing, and artifact export. By integrating Buildx Bake
|
||||
into your projects, you can simplify your Docker builds, make your build
|
||||
configuration portable, and wrangle complex configurations more easily.
|
||||
|
||||
Experiment with different configurations and extend your Bake files to match
|
||||
your project's needs. You might consider integrating Bake into your CI/CD
|
||||
pipelines to automate builds, testing, and artifact deployment. The flexibility
|
||||
and power of Buildx Bake can significantly improve your development and
|
||||
deployment processes.
|
||||
|
||||
### Further reading
|
||||
|
||||
For more information about how to use Bake, check out these resources:
|
||||
|
||||
- [Bake documentation](/manuals/build/bake/_index.md)
|
||||
- [Matrix targets](/manuals/build/bake/matrices.md)
|
||||
- [Bake file reference](/manuals/build/bake/reference.md)
|
||||
- [Bake GitHub Action](https://github.com/docker/bake-action)
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
title: DevOps
|
||||
---
|
Loading…
Reference in New Issue