diff --git a/content/guides/compose-bake/index.md b/content/guides/compose-bake/index.md new file mode 100644 index 0000000000..b5ded0fce0 --- /dev/null +++ b/content/guides/compose-bake/index.md @@ -0,0 +1,406 @@ +--- +title: Building Compose projects with Bake +description: Learn how to build Docker Compose projects with Docker Buildx Bake +summary: | + This guide demonstrates how you can use Bake to build production-grade images for Docker Compose projects. +languages: [] +tags: [devops] +params: + time: 20 minutes +--- + +This guide explores how you can use Bake to build images for Docker Compose +projects with multiple services. + +[Docker Buildx Bake](/manuals/build/bake/_index.md) is a build orchestration +tool that enables declarative configuration for your builds, much like Docker +Compose does for defining runtime stacks. For projects where Docker Compose is +used to spin up services for local development, Bake offers a way of seamlessly +extending the project with a production-ready build configuration. + +## Prerequisites + +This guide assumes that you're familiar with + +- Docker Compose +- [Multi-stage builds](/manuals/build/building/multi-stage.md) +- [Multi-platform builds](/manuals/build/building/multi-platform.md) + +## Orientation + +This guide will use the +[dvdksn/example-voting-app](https://github.com/dvdksn/example-voting-app) +repository as an example of a monorepo using Docker Compose that can be +extended with Bake. + +```console +$ git clone https://github.com/dvdksn/example-voting-app.git +$ cd example-voting-app +``` + +This repository uses Docker Compose to define the runtime configurations for +running the application, in the `compose.yaml` file. This app consists of the +following services: + +| Service | Description | +| -------- | ---------------------------------------------------------------------- | +| `vote` | A front-end web app in Python which lets you vote between two options. | +| `result` | A Node.js web app which shows the results of the voting in real time. | +| `worker` | A .NET worker which consumes votes and stores them in the database. | +| `db` | A Postgres database backed by a Docker volume. | +| `redis` | A Redis instance which collects new votes. | +| `seed` | A utility container that seeds the database with mock data. | + +The `vote`, `result`, and `worker` services are built from code in this +repository, whereas `db` and `redis` use pre-existing Postgres and Redis images +from Docker Hub. The `seed` service is a utility that invokes requests against +the front-end service to populate the database, for testing purposes. + +## Build with Compose + +When you spin up a Docker Compose project, any services that define the `build` +property are automatically built before the service is started. Here's the +build configuration for the `vote` service in the example repository: + +```yaml {title="compose.yaml"} +services: + vote: + build: + context: ./vote # Build context + target: dev # Dockerfile stage +``` + +The `vote`, `result`, and `worker` services all have a build configuration +specified. Running `docker compose up` will trigger a build of these services. + +Did you know that you can also use Compose just to build the service images? +The `docker compose build` command lets you invoke a build using the build +configuration specified in the Compose file. For example, to build the `vote` +service with this configuration, run: + +```console +$ docker compose build vote +``` + +Omit the service name to build all services at once: + +```console +$ docker compose build +``` + +The `docker compose build` command is useful when you only need to build images +without running services. + +The Compose file format supports a number of properties for defining your +build's configuration. For example, to specify the tag name for the images, set +the `image` property on the service. + +```yaml +services: + vote: + image: username/vote + build: + context: ./vote + target: dev + #... + + result: + image: username/result + build: + context: ./result + #... + + worker: + image: username/worker + build: + context: ./worker + #... +``` + +Running `docker compose build` creates three service images with fully +qualified image names that you can push to Docker Hub. + +The `build` property supports a [wide range](/reference/compose-file/build.md) +of options for configuring builds. However, building production-grade images +are often different from images used in local development. To avoid cluttering +your Compose file with build configurations that might not be desirable for +local builds, consider separating the production builds from the local builds +by using Bake to build images for release. This approach separates concerns: +using Compose for local development and Bake for production-ready builds, while +still reusing service definitions and fundamental build configurations. + +## Build with Bake + +Like Compose, Bake parses the build definition for a project from a +configuration file. Bake supports HashiCorp Configuration Language (HCL), JSON, +and the Docker Compose YAML format. When you use Bake with multiple files, it +will find and merge all of the applicable configuration files into one unified +build configuration. The build options defined in your Compose file are +extended, or in some cases overridden, by options specified in the Bake file. + +The following section explores how you can use Bake to extend the build options +defined in your Compose file for production. + +### View the build configuration + +Bake automatically creates a build configuration from the `build` properties of +your services. Use the `--print` flag for Bake to view the build configuration +for a given Compose file. This flag evaluates the build configuration and +outputs the build definition in JSON format. + +```console +$ docker buildx bake --print +``` + +The JSON-formatted output shows the group that would be executed, and all the +targets of that group. A group is a collection of builds, and a target +represents a single build. + +```json +{ + "group": { + "default": { + "targets": [ + "vote", + "result", + "worker", + "seed" + ] + } + }, + "target": { + "result": { + "context": "result", + "dockerfile": "Dockerfile", + "network": "" + }, + "seed": { + "context": "seed-data", + "dockerfile": "Dockerfile", + "network": "" + }, + "vote": { + "context": "vote", + "dockerfile": "Dockerfile", + "target": "dev", + "network": "" + }, + "worker": { + "context": "worker", + "dockerfile": "Dockerfile", + "network": "" + } + } +} +``` + +As you can see, Bake has created a `default` group that includes four targets: + +- `seed` +- `vote` +- `result` +- `worker` + +This group is created automatically from your Compose file; it includes all of +your services containing a build configuration. To build this group of services +with Bake, run: + +```console +$ docker buildx bake +``` + +### Customize the build group + +Start by redefining the default build group that Bake executes. The current +default group includes a `seed` target — a Compose service used solely to +populate the database with mock data. Since this target doesn't produce a +production image, it doesn’t need to be included in the build group. + +To customize the build configuration that Bake uses, create a new file at the +root of the repository, alongside your `compose.yaml` file, named +`docker-bake.hcl`. + +```console +$ touch docker-bake.hcl +``` + +Open the Bake file and add the following configuration: + +```hcl {title=docker-bake.hcl} +group "default" { + targets = ["vote", "result", "worker"] +} +``` + +Save the file and print your Bake definition again. + +```console +$ docker buildx bake --print +``` + +The JSON output shows that the `default` group only includes the targets you +care about. + +```json +{ + "group": { + "default": { + "targets": ["vote", "result", "worker"] + } + }, + "target": { + "result": { + "context": "result", + "dockerfile": "Dockerfile", + "tags": ["username/result"] + }, + "vote": { + "context": "vote", + "dockerfile": "Dockerfile", + "tags": ["username/vote"], + "target": "dev" + }, + "worker": { + "context": "worker", + "dockerfile": "Dockerfile", + "tags": ["username/worker"] + } + } +} +``` + +Here, the build configuration for each target (context, tags, etc.) is picked +up from the `compose.yaml` file. The group is defined by the `docker-bake.hcl` +file. + +### Customize targets + +The Compose file currently defines the `dev` stage as the build target for the +`vote` service. That's appropriate for the image that you would run in local +development, because the `dev` stage includes additional development +dependencies and configurations. For the production image, however, you'll want +to target the `final` image instead. + +To modify the target stage used by the `vote` service, add the following +configuration to the Bake file: + +```hcl +target "vote" { + target = "final" +} +``` + +This overrides the `target` property specified in the Compose file with a +different value when you run the build with Bake. The other build options in +the Compose file (tag, context) remain unmodified. You can verify by inspecting +the build configuration for the `vote` target with `docker buildx bake --print +vote`: + +```json +{ + "group": { + "default": { + "targets": ["vote"] + } + }, + "target": { + "vote": { + "context": "vote", + "dockerfile": "Dockerfile", + "tags": ["username/vote"], + "target": "final" + } + } +} +``` + +### Additional build features + +Production-grade builds often have different characteristics from development +builds. Here are a few examples of things you might want to add for production +images. + +Multi-platform +: For local development, you only need to build images for your local platform, +since those images are just going to run on your machine. But for images that +are pushed to a registry, it's often a good idea to build for multiple +platforms, arm64 and amd64 in particular. + +Attestations +: [Attestations](/manuals/build/metadata/attestations/_index.md) are manifests +attached to the image that describe how the image was created and what +components it contains. Attaching attestations to your images helps ensure that +your images follow software supply chain best practices. + +Annotations +: [Annotations](/manuals/build/metadata/annotations.md) provide descriptive +metadata for images. Use annotations to record arbitrary information and attach +it to your image, which helps consumers and tools understand the origin, +contents, and how to use the image. + +> [!TIP] +> Why not just define these additional build options in the Compose file +> directly? +> +> The `build` property in the Compose file format does not support all build +> features. Additionally, some features, like multi-platform builds, can +> drastically increase the time it takes to build a service. For local +> development, you're better off keeping your build step simple and fast, +> saving the bells and whistles for release builds. + +To add these properties to the images you build with Bake, update the Bake file +as follows: + +```hcl +group "default" { + targets = ["vote", "result", "worker"] +} + +target "_common" { + annotations = ["org.opencontainers.image.authors=username"] + platforms = ["linux/amd64", "linux/arm64"] + attest = [ + "type=provenance,mode=max", + "type=sbom" + ] +} + +target "vote" { + inherits = ["_common"] + target = "final" +} + +target "result" { + inherits = ["_common"] +} + +target "worker" { + inherits = ["_common"] +} +``` + +This defines a new `_common` target that defines reusable build configuration +for adding multi-platform support, annotations, and attestations to your +images. The reusable target is inherited by the build targets. + +With these changes, building the project with Bake produces three sets of +multi-platform images for the `linux/amd64` and `linux/arm64` architectures. +Each image is decorated with an author annotation, and both SBOM and provenance +attestation records. + +## Conclusions + +The pattern demonstrated in this guide provides a useful approach for managing +production-ready Docker images in projects using Docker Compose. Using Bake +gives you access to all the powerful features of Buildx and BuildKit, and also +helps separate your development and build configuration in a reasonable way. + +### Further reading + +For more information about how to use Bake, check out these resources: + +- [Bake documentation](/manuals/build/bake/_index.md) +- [Building with Bake from a Compose file](/manuals/build/bake/compose-file.md) +- [Bake file reference](/manuals/build/bake/reference.md) +- [Mastering multi-platform builds, testing, and more with Docker Buildx Bake](/guides/bake/index.md) +- [Bake GitHub Action](https://github.com/docker/bake-action)