Description
When COMPOSE_BAKE=true docker compose build is used to build a service, Docker Compose's internal compose→bake translator silently ignores the x-bake extension on the service's build:
block. Specifically, x-bake.output, x-bake.secrets, x-bake.platforms, x-bake.cache-from, x-bake.pull, x-bake.no-cache, and the other documented x-bake
fields never reach the bake target that Compose constructs.
This is surprising because:
docker compose config shows the x-bake extension is loaded and present in the merged compose config.
docker buildx bake -f compose.yml (invoking bake directly on the same compose file) does honor the extension — the parsed bake target includes exactly the fields from x-bake.
- The x-bake docs explicitly describe x-bake as "the special extension field… to evaluate extra fields" — implying it works with the bake
integration that COMPOSE_BAKE=true enables.
The effect is that any compose file that uses x-bake to pass bake-only options (e.g., setting output: type=image,push=true,compression=zstd for zstd layer compression) works correctly with
docker buildx bake but is silently degraded to Compose's default output behavior when COMPOSE_BAKE=true is used.
Steps To Reproduce
Setup
mkdir /tmp/xbake-repro && cd /tmp/xbake-repro
Dockerfile:
FROM alpine:3.20
RUN dd if=/dev/urandom of=/blob bs=1M count=100
compose.yml:
services:
demo:
build:
context: .
dockerfile: Dockerfile
x-bake:
output:
- type=image,name=xbake-repro:latest,compression=zstd,compression-level=3,force-compression=true,oci-mediatypes=true
Expected behavior (works correctly)
docker buildx bake reading the compose file directly does honor x-bake.output:
$ docker buildx bake --print -f compose.yml demo
#1 [internal] load local bake definitions
#1 reading compose.yml 232B / 232B done
#1 DONE 0.0s
{
"group": { "default": { "targets": ["demo"] } },
"target": {
"demo": {
"context": ".",
"dockerfile": "Dockerfile",
"output": [
{
"compression": "zstd",
"compression-level": "3",
"force-compression": "true",
"name": "xbake-repro:latest",
"oci-mediatypes": "true",
"type": "image"
}
]
}
}
}
Note the "compression": "zstd" etc. in the parsed output field — exactly as specified in the compose file's x-bake block.
Actual behavior (bug)
docker compose config confirms the extension is loaded:
$ docker compose -f compose.yml config
name: xbake-repro
services:
demo:
build:
context: /tmp/xbake-repro
dockerfile: Dockerfile
x-bake:
output:
- type=image,name=xbake-repro:latest,compression=zstd,compression-level=3,force-compression=true,oci-mediatypes=true
...
But when building via COMPOSE_BAKE=true docker compose build, the resulting bake target has none of the x-bake.output fields. The output value is hardcoded by Compose's translator to one of
type=docker, type=registry, or type=image,push=%t — regardless of what x-bake.output says.
Root cause
In pkg/compose/build_bake.go, the doBuildBake function constructs the bake target's Outputs field from exactly three
sources:
var outputs []string
var call string
push := options.Push && service.Image != ""
switch {
case options.Check:
call = "lint"
case len(service.Build.Platforms) > 1:
outputs = []string{fmt.Sprintf("type=image,push=%t", push)}
default:
if push {
outputs = []string{"type=registry"}
} else {
outputs = []string{"type=docker"}
}
}
grep -n "xbake\|x-bake\|Extensions" pkg/compose/build_bake.go on current main returns zero matches — the file doesn't read service.Build.Extensions["x-bake"] (or anywhere else) when
populating the bake target. The same holds for all the other x-bake fields (secrets, platforms, cache-from, etc.) documented at https://docs.docker.com/build/bake/compose-file/.
Prior art / related
- #12956 [BUG] Not generating a "cache-to" field when COMPOSE_BAKE=true from yaml (closed, fixed by
#12959) — same family of bug, fixed for cache_to by routing it through Compose's native cache_to field rather than via x-bake. The present
issue is about x-bake extensions specifically, which is still ignored.
- moby/buildkit#4898 — a different bug about YAML extension merging when a base compose file has an empty
x-bake: key. Not the same code path.
- docker/buildx#3072 — long-standing proposal for a top-level
docker buildx build --compression flag. Making x-bake.output work via
COMPOSE_BAKE would close a meaningful portion of that issue's use cases for compose users.
Proposed fix
Read service.Build.Extensions in doBuildBake and merge recognised x-bake fields into the bakeTarget struct. The set of fields to recognise is documented at
https://docs.docker.com/build/bake/compose-file/ — at minimum: output, secrets, platforms, tags, contexts, no-cache, no-cache-filter, pull, cache-from, cache-to, ssh.
The implementation shape is similar to #12959, which added a native cache_to code path — but generalised to deep-merge the x-bake extension onto
the bake target after the existing hardcoded logic runs, so user intent wins over defaults.
Happy to follow up with a PR if the approach is acceptable.
Real-world impact
The use case that led me to file this: I wanted to compress image layers with zstd (instead of gzip) on a large full Go development image (~3.5 GB uncompressed, including a 1.3 GB go build
cache baked into a layer). With gzip, the exporting layers step of the build takes ~70 seconds single-threaded. With zstd it drops to ~5 seconds (multi-threaded, ~4.5× better ratio on Go build
artefacts). The natural way to enable this is x-bake.output: type=image,compression=zstd,... in compose.yml and COMPOSE_BAKE=true on the build command. That's how the feature is documented
and that's how it works with docker buildx bake directly.
Because COMPOSE_BAKE=true docker compose build silently ignores the extension, we had to replace our docker-compose plugin invocation with a direct docker buildx build --output type=image,compression=zstd,... call, losing the plugin abstraction for that one step. Other users of the ecosystem (Buildkite users, GitHub Actions users, anyone wrapping compose) will hit the
same wall and won't have a good option.
Environment
- Docker version:
29.3.1 (API 1.54), client 29.3.1
- Docker Compose version:
v5.1.1
- docker buildx version:
v0.32.1-desktop.1 (commit 56d7a98f3ce2e9c260d9f75460a8308e4f157a47)
- Platform: macOS (Darwin 25.4.0)
Also reproduces against docker/compose main branch based on source-code inspection of pkg/compose/build_bake.go.
Compose Version
Docker Compose version v5.1.1
Docker Environment
docker info 10:49:46
Client:
Version: 29.3.1
Context: desktop-linux
Debug Mode: false
Plugins:
agent: Docker AI Agent Runner (Docker Inc.)
Version: v1.39.0
Path: /Users/elijahchancey/.docker/cli-plugins/docker-agent
ai: Docker AI Agent - Ask Gordon (Docker Inc.)
Version: v1.20.2
Path: /Users/elijahchancey/.docker/cli-plugins/docker-ai
buildx: Docker Buildx (Docker Inc.)
Version: v0.32.1-desktop.1
Path: /Users/elijahchancey/.docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v5.1.1
Path: /Users/elijahchancey/.docker/cli-plugins/docker-compose
debug: Get a shell into any image or container (Docker Inc.)
Version: 0.0.47
Path: /Users/elijahchancey/.docker/cli-plugins/docker-debug
desktop: Docker Desktop commands (Docker Inc.)
Version: v0.3.0
Path: /Users/elijahchancey/.docker/cli-plugins/docker-desktop
dhi: CLI for managing Docker Hardened Images (Docker Inc.)
Version: v0.0.2
Path: /Users/elijahchancey/.docker/cli-plugins/docker-dhi
extension: Manages Docker extensions (Docker Inc.)
Version: v0.2.31
Path: /Users/elijahchancey/.docker/cli-plugins/docker-extension
init: Creates Docker-related starter files for your project (Docker Inc.)
Version: v1.4.0
Path: /Users/elijahchancey/.docker/cli-plugins/docker-init
mcp: Docker MCP Plugin (Docker Inc.)
Version: v0.40.3
Path: /Users/elijahchancey/.docker/cli-plugins/docker-mcp
model: Docker Model Runner (Docker Inc.)
Version: v1.1.28
Path: /Users/elijahchancey/.docker/cli-plugins/docker-model
offload: Docker Offload (Docker Inc.)
Version: v0.5.81
Path: /Users/elijahchancey/.docker/cli-plugins/docker-offload
pass: Docker Pass Secrets Manager Plugin (beta) (Docker Inc.)
Version: v0.0.24
Path: /Users/elijahchancey/.docker/cli-plugins/docker-pass
sandbox: Docker Sandbox (Docker Inc.)
Version: v0.12.0
Path: /Users/elijahchancey/.docker/cli-plugins/docker-sandbox
sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc.)
Version: 0.6.0
Path: /Users/elijahchancey/.docker/cli-plugins/docker-sbom
scout: Docker Scout (Docker Inc.)
Version: v1.20.3
Path: /Users/elijahchancey/.docker/cli-plugins/docker-scout
Server:
Containers: 6
Running: 0
Paused: 0
Stopped: 6
Images: 6
Server Version: 29.3.1
Storage Driver: overlayfs
driver-type: io.containerd.snapshotter.v1
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 2
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
CDI spec directories:
/etc/cdi
/var/run/cdi
Swarm: inactive
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc
Init Binary: docker-init
containerd version: dea7da592f5d1d2b7755e3a161be07f43fad8f75
runc version: v1.3.4-0-gd6d73eb8
init version: de40ad0
Security Options:
seccomp
Profile: builtin
cgroupns
Kernel Version: 6.12.76-linuxkit
Operating System: Docker Desktop
OSType: linux
Architecture: aarch64
CPUs: 14
Total Memory: 7.652GiB
Name: docker-desktop
ID: 83d64803-9ddf-4d9d-ba78-518c4e490a1d
Docker Root Dir: /var/lib/docker
Debug Mode: false
HTTP Proxy: http.docker.internal:3128
HTTPS Proxy: http.docker.internal:3128
No Proxy: hubproxy.docker.internal
Labels:
com.docker.desktop.address=unix:///Users/elijahchancey/Library/Containers/com.docker.docker/Data/docker-cli.sock
Experimental: false
Insecure Registries:
hubproxy.docker.internal:5555
::1/128
127.0.0.0/8
Live Restore Enabled: false
Anything else?
No response
Description
When
COMPOSE_BAKE=true docker compose buildis used to build a service, Docker Compose's internal compose→bake translator silently ignores thex-bakeextension on the service'sbuild:block. Specifically,
x-bake.output,x-bake.secrets,x-bake.platforms,x-bake.cache-from,x-bake.pull,x-bake.no-cache, and the other documented x-bakefields never reach the bake target that Compose constructs.
This is surprising because:
docker compose configshows the x-bake extension is loaded and present in the merged compose config.docker buildx bake -f compose.yml(invoking bake directly on the same compose file) does honor the extension — the parsed bake target includes exactly the fields fromx-bake.integration that
COMPOSE_BAKE=trueenables.The effect is that any compose file that uses x-bake to pass bake-only options (e.g., setting
output: type=image,push=true,compression=zstdfor zstd layer compression) works correctly withdocker buildx bakebut is silently degraded to Compose's default output behavior whenCOMPOSE_BAKE=trueis used.Steps To Reproduce
Setup
Dockerfile:compose.yml:Expected behavior (works correctly)
docker buildx bakereading the compose file directly does honorx-bake.output:Note the
"compression": "zstd"etc. in the parsedoutputfield — exactly as specified in the compose file'sx-bakeblock.Actual behavior (bug)
docker compose configconfirms the extension is loaded:But when building via
COMPOSE_BAKE=true docker compose build, the resulting bake target has none of thex-bake.outputfields. Theoutputvalue is hardcoded by Compose's translator to one oftype=docker,type=registry, ortype=image,push=%t— regardless of whatx-bake.outputsays.Root cause
In
pkg/compose/build_bake.go, thedoBuildBakefunction constructs the bake target'sOutputsfield from exactly threesources:
grep -n "xbake\|x-bake\|Extensions" pkg/compose/build_bake.goon currentmainreturns zero matches — the file doesn't readservice.Build.Extensions["x-bake"](or anywhere else) whenpopulating the bake target. The same holds for all the other x-bake fields (
secrets,platforms,cache-from, etc.) documented at https://docs.docker.com/build/bake/compose-file/.Prior art / related
#12959) — same family of bug, fixed for
cache_toby routing it through Compose's nativecache_tofield rather than viax-bake. The presentissue is about x-bake extensions specifically, which is still ignored.
x-bake:key. Not the same code path.docker buildx build --compressionflag. Makingx-bake.outputwork viaCOMPOSE_BAKEwould close a meaningful portion of that issue's use cases for compose users.Proposed fix
Read
service.Build.ExtensionsindoBuildBakeand merge recognised x-bake fields into thebakeTargetstruct. The set of fields to recognise is documented athttps://docs.docker.com/build/bake/compose-file/ — at minimum:
output,secrets,platforms,tags,contexts,no-cache,no-cache-filter,pull,cache-from,cache-to,ssh.The implementation shape is similar to #12959, which added a native
cache_tocode path — but generalised to deep-merge the x-bake extension ontothe bake target after the existing hardcoded logic runs, so user intent wins over defaults.
Happy to follow up with a PR if the approach is acceptable.
Real-world impact
The use case that led me to file this: I wanted to compress image layers with zstd (instead of gzip) on a large
fullGo development image (~3.5 GB uncompressed, including a 1.3 GBgo buildcache baked into a layer). With gzip, the
exporting layersstep of the build takes ~70 seconds single-threaded. With zstd it drops to ~5 seconds (multi-threaded, ~4.5× better ratio on Go buildartefacts). The natural way to enable this is
x-bake.output: type=image,compression=zstd,...incompose.ymlandCOMPOSE_BAKE=trueon the build command. That's how the feature is documentedand that's how it works with
docker buildx bakedirectly.Because
COMPOSE_BAKE=true docker compose buildsilently ignores the extension, we had to replace ourdocker-composeplugin invocation with a directdocker buildx build --output type=image,compression=zstd,...call, losing the plugin abstraction for that one step. Other users of the ecosystem (Buildkite users, GitHub Actions users, anyone wrapping compose) will hit thesame wall and won't have a good option.
Environment
29.3.1(API 1.54), client 29.3.1v5.1.1v0.32.1-desktop.1(commit56d7a98f3ce2e9c260d9f75460a8308e4f157a47)Also reproduces against docker/compose
mainbranch based on source-code inspection ofpkg/compose/build_bake.go.Compose Version
Docker Environment
Anything else?
No response