Make Repeatable Docker Builds with docker bake
You will turn a small build definition into a repeatable Docker workflow, inspect what Docker resolved, and build only the target you intended. This guide uses Docker CE CLI 29.8.1 with Buildx 0.37.1, as installed on the machine used for these examples. Allow about 15 minutes if Dockerfiles already exist.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
You need the Docker CLI with the Buildx plugin, a working builder, and a directory containing the Dockerfile and files that it copies. Check the versions and builder before changing anything:
docker --version
docker buildx version
docker buildx ls
The local docker bake command is an alias for Buildx Bake. If the first command prints Docker 29.8.1 and the second prints Buildx 0.37.1, the installed behaviour matches the version used here. A builder marked as running is needed for an actual build; inspection commands can still help diagnose a setup that is not ready.
Checkpoint: create a Bake file
Make a file named docker-bake.hcl beside your Dockerfile. A target describes one build invocation. A group gives a name to several targets, and the group named default is selected when you run Bake without a target.
variable "TAG" {
default = "local"
}
group "default" {
targets = ["web"]
}
target "web" {
context = "."
dockerfile = "Dockerfile"
tags = ["example/web:${TAG}"]
}
Keep the tag as a local placeholder until you know where the image should go. The TAG variable is substituted in the tag, and an environment variable with the same name overrides its default. This is convenient for a release tag, but it also means an inherited shell environment can change the result. Set it deliberately when it matters.
Inspect before building
From the directory containing the file, print the resolved definition:
docker buildx bake --print
Look for the expected context, Dockerfile and tag in the JSON output. This command does not build or push an image. To inspect a file at another path, use --file:
docker buildx bake --file ./docker-bake.hcl --print
Bake searches the current directory for Compose and Bake files when no file is specified. Several matching definitions can be merged, so an unexpected docker-bake.override.hcl or Compose file can change the result. Use --file when you need an unambiguous input.
List and validate targets
List the targets defined by the resolved configuration:
docker buildx bake --list=targets
For a configuration with a web target, the output includes that name. If variables have descriptions, --list=variables lists them too. Before an expensive build, ask Buildx to evaluate the definition without executing it:
docker buildx bake --check
Fix syntax, missing values and Dockerfile checks reported by this command before proceeding. If the file has no usable default group, name the target explicitly instead of relying on the no-argument default.
Build one target
Build the example image with the tag chosen for this invocation:
TAG=dev docker buildx bake web
This runs the web target and substitutes dev for ${TAG}. Passing a target name is a useful safety habit when a file contains several images. You can pass more than one target, such as web api, or use a group name to select a defined collection.
For readable output in a terminal or log, select plain progress:
TAG=dev docker buildx bake --progress=plain web
By default, a successful BuildKit build may remain only in the builder's cache rather than appearing in the host's local image list. Add --load only when you need a single-platform result loaded into the local Docker image store:
TAG=dev docker buildx bake --load web
docker image inspect example/web:dev
If the inspect command prints image metadata, the loaded tag exists. The exact output depends on the builder and Dockerfile, so check the tag rather than relying on a progress line.
Push only after checking the destination
Warning
--push uploads the result to the registry named by the target tag. It can publish credentials or proprietary code indirectly through the build context, and an existing remote tag may be replaced according to registry policy. Do not add it to a first test run.
First inspect the final registry tag:
TAG=2026-09-23 docker buildx bake --print web
Then authenticate using your organisation's approved Docker method and push only when the tag and target are correct:
TAG=2026-09-23 docker buildx bake --push web
If this is the wrong tag, stop and correct the variable or target. A registry push is not undone by rerunning Bake. Recovery normally means publishing a corrected image under an approved tag and following the registry's retention or deletion process, which may require an administrator.
Common traps
- The wrong targets run: an omitted target selects
default. Use--list=targetsand name the target explicitly. - A wildcard matches local files: target patterns such as
web-*are interpreted by Bake, but an unquoted pattern can be expanded by the shell first. Quote it:docker buildx bake "web-*". - The tag is surprising: an environment variable overrides a Bake variable default. Run
env | grep '^TAG='if the resolved--printoutput is unexpected, then set or unset it deliberately. - The image is missing locally: a build can complete in the builder without loading into the host image store. Repeat with
--loadwhen that is supported by the target and builder. - The build context is too broad: Docker sends files from the context unless excluded by
.dockerignore. Review that file before building or pushing, especially in a repository containing credentials, build output or private source.
Done means
docker buildx bake --printshows the intended context, Dockerfile and tag.docker buildx bake --checkcompletes without configuration errors.- You built the named target, or named the approved group, rather than relying on an unfamiliar default.
- You used
--loadonly when a local image was required. - You used
--pushonly after checking the final registry destination.