Repository: docker/compose (go)

Issue:
docker compose does not use current buildkit builder
<!--
If you are reporting a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.

If you suspect your issue is a bug, please edit your issue description to
include the BUG REPORT INFORMATION shown below. If you fail to provide this
information within 7 days, we cannot debug your issue and will close it. We
will, however, reopen it if you later provide the information.

For more information about reporting issues, see
https://github.com/docker/compose-cli/blob/master/CONTRIBUTING.md#reporting-other-issues

---------------------------------------------------
GENERAL SUPPORT INFORMATION
---------------------------------------------------

The GitHub issue tracker is for bug reports and feature requests.
General support can be found at the following locations:

- Docker Support Forums - https://forums.docker.com
- Docker Community Slack - https://dockr.ly/community
- Post a question on StackOverflow, using the Docker tag

---------------------------------------------------
BUG REPORT INFORMATION
---------------------------------------------------
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
-->

**Description**

<!--
Briefly describe the problem you are having in a few paragraphs.
-->

`docker compose build` does not use the current buildkit builder. both `docker build` and `docker-compose build` use this builder, which I need because it contains cache mount data needed for a particularly slow `npm ci` command.

**Steps to reproduce the issue:**
1. `docker buildx create --name cacheable --use`
2. `docker buildx inspect --bootstrap`
3. `docker compose build <service_name>`

**Describe the results you received:**
The build runs and completes successfully, but does not use my currently selected builder. This can be identified by (a) the slow build and (b) if the builder is stopped beforehand with `docker stop buildx_buildkit_cacheable0` then it is still stopped after the build completes. 

**Describe the results you expected:**
I expect `docker compose build` to use my current buildkit builder, named `buildx_buildkit_cacheable0`

**Additional information you deem important (e.g. issue happens only occasionally):**
Both `docker build` and `docker-compose build` will start the currently selected builder container (if stopped) and use it to build, which leads me to believe this might be a bug in `docker compose`.

Environment contains these vars:
`COMPOSE_DOCKER_CLI_BUILD=1`
`DOCKER_BUILDKIT=1`

I have tried setting `BUILDKIT_HOST=docker-container://buildx_buildkit_cacheable0` without any change in the behaviour of `docker compose build`.

**Output of `docker version`:**

```
Client: Docker Engine - Community
 Cloud integration: 1.0.17
 Version:           20.10.6
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        370c289
 Built:             Fri Apr  9 22:46:38 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.6
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       8728dd2
  Built:            Fri Apr  9 22:44:49 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.4
  GitCommit:        05f951a3781f4f2c1911b05e61c160e9c30eaa8e
 runc:
  Version:          1.0.0-rc93
  GitCommit:        12644e614e25b05da6fd08a38ffa0cfe1903fdec
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

```

**Output of `docker context show`:**  
You can also run `docker context inspect context-name` to give us more details but don't forget to remove sensitive content.

```
default
```

**Output of `docker info`:**

```
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Build with BuildKit (Docker Inc., v0.5.1-docker)
  compose: Docker Compose (Docker Inc., 2.0.0-beta.3)
  scan: Docker Scan (Docker Inc., v0.7.0)

Server:
 Containers: 1
  Running: 0
  Paused: 0
  Stopped: 1
 Images: 2
 Server Version: 20.10.6
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc io.containerd.runc.v2 io.containerd.runtime.v1.linux
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e
 runc version: 12644e614e25b05da6fd08a38ffa0cfe1903fdec
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: default
 Kernel Version: 5.11.0-22-generic
 Operating System: Ubuntu 21.04
 OSType: linux
 Architecture: x86_64
 CPUs: 8
 Total Memory: 31.24GiB
 Name: W540
 ID: QNQR:7RST:WACE:AHDH:E6OR:II4B:QFD6:B7LR:K3I3:4VDE:3VPD:ZZI3
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Username: strophy
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

```

**Additional environment details (AWS ECS, Azure ACI, local, etc.):**
local

Pull request:
Add platforms build
**What I did**
Add support of the `platforms` attribut, [recently introduced](https://github.com/compose-spec/compose-spec/pull/267) in the Compose specification.

This feature will allow to build multi-arch images from the `build` command and push those imagines to a registry.

Huge thanks to @crazy-max for his help 🙏

Should fix #8531 

**(not mandatory) A picture of a cute animal, if possible in relation with what you did**
![image](https://user-images.githubusercontent.com/705411/183450323-d8231850-5458-4bbd-9147-4f6801139efa.png)


Patch:
diff --git a/cmd/compose/tracing.go b/cmd/compose/tracing.go
new file mode 100644
index 000000000..99ff58d82
--- /dev/null
+++ b/cmd/compose/tracing.go
@@ -0,0 +1,35 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package compose
+
+import (
+	"github.com/moby/buildkit/util/tracing/detect"
+	"go.opentelemetry.io/otel"
+
+	_ "github.com/moby/buildkit/util/tracing/detect/delegated" //nolint:blank-imports
+	_ "github.com/moby/buildkit/util/tracing/env"              //nolint:blank-imports
+)
+
+func init() {
+	detect.ServiceName = "compose"
+	// do not log tracing errors to stdio
+	otel.SetErrorHandler(skipErrors{})
+}
+
+type skipErrors struct{}
+
+func (skipErrors) Handle(err error) {}
diff --git a/go.mod b/go.mod
index 1711a404a..1e767eb35 100644
--- a/go.mod
+++ b/go.mod
@@ -101,7 +101,7 @@ require (
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
-	go.opentelemetry.io/otel v1.4.1 // indirect
+	go.opentelemetry.io/otel v1.4.1
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
 	go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
 	go.opentelemetry.io/otel/metric v0.27.0 // indirect
@@ -122,7 +122,7 @@ require (
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	k8s.io/apimachinery v0.24.1 // indirect; see replace for the actual version used
-	k8s.io/client-go v0.24.1 // indirect; see replace for the actual version used
+	k8s.io/client-go v0.24.1 // see replace for the actual version used
 	k8s.io/klog/v2 v2.60.1 // indirect
 	k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
@@ -130,9 +130,17 @@ require (
 )
 
 require (
+	github.com/cenkalti/backoff/v4 v4.1.2 // indirect
 	github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
+	github.com/googleapis/gnostic v0.5.5 // indirect
+	github.com/moby/spdystream v0.2.0 // indirect
+	github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // indirect
 	github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
 	github.com/zmap/zlint v1.1.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.4.1 // indirect
+	k8s.io/api v0.24.1 // indirect
 )
 
 replace (
diff --git a/go.sum b/go.sum
index 640374cd2..c052fe4d6 100644
--- a/go.sum
+++ b/go.sum
@@ -246,6 +246,7 @@ github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMS
 github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
 github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
 github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -498,6 +499,7 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 h1:pEtiCjIXx3RvGjlUJuCNxNOw0MNblyR9Wi+vJGBFh+8=
 github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
@@ -761,6 +763,7 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
 github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
 github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
+github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
 github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
 github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
 github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
@@ -1022,6 +1025,7 @@ github.com/moby/buildkit v0.10.4 h1:FvC+buO8isGpUFZ1abdSLdGHZVqg9sqI4BbFL8tlzP4=
 github.com/moby/buildkit v0.10.4/go.mod h1:Yajz9vt1Zw5q9Pp4pdb3TCSUXJBIroIQGQ3TTs/sLug=
 github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
 github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
+github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
 github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
 github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74=
 github.com/moby/sys/mount v0.1.1/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74=
@@ -1235,6 +1239,7 @@ github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+y
 github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 h1:ka9QPuQg2u4LGipiZGsgkg3rJCo4iIUCy75FddM0GRQ=
 github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
 github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
 github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
@@ -1465,13 +1470,16 @@ go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdT
 go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI=
 go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
 go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
+go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 h1:imIM3vRDMyZK1ypQlQlO+brE22I9lRhJsBDXpDWjlz8=
 go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 h1:WPpPsAAs8I2rA47v5u0558meKmmwm1Dj99ZbqCV8sZ8=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1/go.mod h1:o5RW5o2pKpJLD5dNTCmjF1DorYwMeFJmb/rKr5sLaa8=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 h1:AxqDiGk8CorEXStMDZF5Hz9vo9Z7ZZ+I5m8JRl/ko40=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1/go.mod h1:c6E4V3/U+miqjs/8l950wggHGL1qzlp0Ypj9xoGrPqo=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.4.1 h1:8qOago/OqoFclMUUj/184tZyRdDZFpcejSjbk5Jrl6Y=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.4.1/go.mod h1:VwYo0Hak6Efuy0TXsZs8o1hnV3dHDPNtDbycG0hI8+M=
 go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk=
 go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw=
@@ -1498,6 +1506,7 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
 go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
diff --git a/pkg/compose/build.go b/pkg/compose/build.go
index 497737731..6079d853d 100644
--- a/pkg/compose/build.go
+++ b/pkg/compose/build.go
@@ -81,6 +81,12 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 				Attrs: map[string]string{"ref": image},
 			})
 		}
+		if len(buildOptions.Platforms) > 1 {
+			buildOptions.Exports = []bclient.ExportEntry{{
+				Type:  "image",
+				Attrs: map[string]string{},
+			}}
+		}
 		opts[imageName] = buildOptions
 	}
 
@@ -162,6 +168,15 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
 			if err != nil {
 				return nil, err
 			}
+			opt.Exports = []bclient.ExportEntry{{
+				Type: "docker",
+				Attrs: map[string]string{
+					"load": "true",
+				},
+			}}
+			if opt.Platforms, err = useDockerDefaultPlatform(project, service.Build.Platforms); err != nil {
+				opt.Platforms = []specs.Platform{}
+			}
 			opts[imageName] = opt
 			continue
 		}
@@ -206,7 +221,7 @@ func (s *composeService) doBuild(ctx context.Context, project *types.Project, op
 	if buildkitEnabled, err := s.dockerCli.BuildKitEnabled(); err != nil || !buildkitEnabled {
 		return s.doBuildClassic(ctx, project, opts)
 	}
-	return s.doBuildBuildkit(ctx, project, opts, mode)
+	return s.doBuildBuildkit(ctx, opts, mode)
 }
 
 func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string, sshKeys []types.SSHKey) (build.Options, error) {
@@ -215,20 +230,9 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
 
 	buildArgs := flatten(service.Build.Args.Resolve(envResolver(project.Environment)))
 
-	var plats []specs.Platform
-	if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
-		p, err := platforms.Parse(platform)
-		if err != nil {
-			return build.Options{}, err
-		}
-		plats = append(plats, p)
-	}
-	if service.Platform != "" {
-		p, err := platforms.Parse(service.Platform)
-		if err != nil {
-			return build.Options{}, err
-		}
-		plats = append(plats, p)
+	plats, err := addPlatforms(project, service)
+	if err != nil {
+		return build.Options{}, err
 	}
 
 	cacheFrom, err := buildflags.ParseCacheEntry(service.Build.CacheFrom)
@@ -352,3 +356,40 @@ func addSecretsConfig(project *types.Project, service types.ServiceConfig) (sess
 	}
 	return secretsprovider.NewSecretProvider(store), nil
 }
+
+func addPlatforms(project *types.Project, service types.ServiceConfig) ([]specs.Platform, error) {
+	plats, err := useDockerDefaultPlatform(project, service.Build.Platforms)
+	if err != nil {
+		return nil, err
+	}
+
+	if service.Platform != "" && !utils.StringContains(service.Build.Platforms, service.Platform) {
+		return nil, fmt.Errorf("service.platform should be part of the service.build.platforms: %q", service.Platform)
+	}
+
+	for _, buildPlatform := range service.Build.Platforms {
+		p, err := platforms.Parse(buildPlatform)
+		if err != nil {
+			return nil, err
+		}
+		if !utils.Contains(plats, p) {
+			plats = append(plats, p)
+		}
+	}
+	return plats, nil
+}
+
+func useDockerDefaultPlatform(project *types.Project, platformList types.StringList) ([]specs.Platform, error) {
+	var plats []specs.Platform
+	if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
+		if !utils.StringContains(platformList, platform) {
+			return nil, fmt.Errorf("the DOCKER_DEFAULT_PLATFORM value should be part of the service.build.platforms: %q", platform)
+		}
+		p, err := platforms.Parse(platform)
+		if err != nil {
+			return nil, err
+		}
+		plats = append(plats, p)
+	}
+	return plats, nil
+}
diff --git a/pkg/compose/build_buildkit.go b/pkg/compose/build_buildkit.go
index d4120ced3..912c520c2 100644
--- a/pkg/compose/build_buildkit.go
+++ b/pkg/compose/build_buildkit.go
@@ -18,27 +18,36 @@ package compose
 
 import (
 	"context"
+	"fmt"
+	"net/url"
 	"os"
 	"path/filepath"
+	"strings"
+
+	ctxkube "github.com/docker/buildx/driver/kubernetes/context"
+	"github.com/docker/buildx/store"
+	"github.com/docker/buildx/store/storeutil"
+	"github.com/docker/cli/cli/command"
+	"github.com/docker/cli/cli/context/docker"
+	ctxstore "github.com/docker/cli/cli/context/store"
+	dockerclient "github.com/docker/docker/client"
+	"github.com/sirupsen/logrus"
+	"golang.org/x/sync/errgroup"
+	"k8s.io/client-go/tools/clientcmd"
 
-	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/buildx/build"
 	"github.com/docker/buildx/driver"
+	_ "github.com/docker/buildx/driver/docker"           //nolint:blank-imports
+	_ "github.com/docker/buildx/driver/docker-container" //nolint:blank-imports
+	_ "github.com/docker/buildx/driver/kubernetes"       //nolint:blank-imports
 	xprogress "github.com/docker/buildx/util/progress"
 )
 
-func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
-	const drivername = "default"
-	d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient(), s.configFile(), nil, nil, nil, nil, nil, project.WorkingDir)
+func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]build.Options, mode string) (map[string]string, error) {
+	dis, err := s.getDrivers(ctx)
 	if err != nil {
 		return nil, err
 	}
-	driverInfo := []build.DriverInfo{
-		{
-			Name:   drivername,
-			Driver: d,
-		},
-	}
 
 	// Progress needs its own context that lives longer than the
 	// build one otherwise it won't read all the messages from
@@ -47,8 +56,7 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
 	defer cancel()
 	w := xprogress.NewPrinter(progressCtx, s.stdout(), os.Stdout, mode)
 
-	// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
-	response, err := build.Build(ctx, driverInfo, opts, nil, filepath.Dir(s.configFile().Filename), w)
+	response, err := build.Build(ctx, dis, opts, &internalAPI{dockerCli: s.dockerCli}, filepath.Dir(s.configFile().Filename), w)
 	errW := w.Wait()
 	if err == nil {
 		err = errW
@@ -71,3 +79,187 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
 
 	return imagesBuilt, err
 }
+
+func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, error) { //nolint:gocyclo
+	txn, release, err := storeutil.GetStore(s.dockerCli)
+	if err != nil {
+		return nil, err
+	}
+	defer release()
+
+	ng, err := storeutil.GetCurrentInstance(txn, s.dockerCli)
+	if err != nil {
+		return nil, err
+	}
+
+	dis := make([]build.DriverInfo, len(ng.Nodes))
+	var f driver.Factory
+	if ng.Driver != "" {
+		factories := driver.GetFactories()
+		for _, fac := range factories {
+			if fac.Name() == ng.Driver {
+				f = fac
+				continue
+			}
+		}
+		if f == nil {
+			if f = driver.GetFactory(ng.Driver, true); f == nil {
+				return nil, fmt.Errorf("failed to find buildx driver %q", ng.Driver)
+			}
+		}
+	} else {
+		ep := ng.Nodes[0].Endpoint
+		dockerapi, err := clientForEndpoint(s.dockerCli, ep)
+		if err != nil {
+			return nil, err
+		}
+		f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
+		if err != nil {
+			return nil, err
+		}
+		ng.Driver = f.Name()
+	}
+
+	imageopt, err := storeutil.GetImageConfig(s.dockerCli, ng)
+	if err != nil {
+		return nil, err
+	}
+
+	eg, _ := errgroup.WithContext(ctx)
+	for i, n := range ng.Nodes {
+		func(i int, n store.Node) {
+			eg.Go(func() error {
+				di := build.DriverInfo{
+					Name:        n.Name,
+					Platform:    n.Platforms,
+					ProxyConfig: storeutil.GetProxyConfig(s.dockerCli),
+				}
+				defer func() {
+					dis[i] = di
+				}()
+
+				dockerapi, err := clientForEndpoint(s.dockerCli, n.Endpoint)
+				if err != nil {
+					di.Err = err
+					return nil
+				}
+				// TODO: replace the following line with dockerclient.WithAPIVersionNegotiation option in clientForEndpoint
+				dockerapi.NegotiateAPIVersion(ctx)
+
+				contextStore := s.dockerCli.ContextStore()
+
+				var kcc driver.KubeClientConfig
+				kcc, err = configFromContext(n.Endpoint, contextStore)
+				if err != nil {
+					// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
+					// try again with name="default".
+					// FIXME: n should retain real context name.
+					kcc, err = configFromContext("default", contextStore)
+					if err != nil {
+						logrus.Error(err)
+					}
+				}
+
+				tryToUseKubeConfigInCluster := false
+				if kcc == nil {
+					tryToUseKubeConfigInCluster = true
+				} else {
+					if _, err := kcc.ClientConfig(); err != nil {
+						tryToUseKubeConfigInCluster = true
+					}
+				}
+				if tryToUseKubeConfigInCluster {
+					kccInCluster := driver.KubeClientConfigInCluster{}
+					if _, err := kccInCluster.ClientConfig(); err == nil {
+						logrus.Debug("using kube config in cluster")
+						kcc = kccInCluster
+					}
+				}
+
+				d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, "")
+				if err != nil {
+					di.Err = err
+					return nil
+				}
+				di.Driver = d
+				di.ImageOpt = imageopt
+				return nil
+			})
+		}(i, n)
+	}
+
+	if err := eg.Wait(); err != nil {
+		return nil, err
+	}
+
+	return dis, nil
+}
+
+func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
+	list, err := dockerCli.ContextStore().List()
+	if err != nil {
+		return nil, err
+	}
+	for _, l := range list {
+		if l.Name != name {
+			continue
+		}
+		dep, ok := l.Endpoints["docker"]
+		if !ok {
+			return nil, fmt.Errorf("context %q does not have a Docker endpoint", name)
+		}
+		epm, ok := dep.(docker.EndpointMeta)
+		if !ok {
+			return nil, fmt.Errorf("endpoint %q is not of type EndpointMeta, %T", dep, dep)
+		}
+		ep, err := docker.WithTLSData(dockerCli.ContextStore(), name, epm)
+		if err != nil {
+			return nil, err
+		}
+		clientOpts, err := ep.ClientOpts()
+		if err != nil {
+			return nil, err
+		}
+		return dockerclient.NewClientWithOpts(clientOpts...)
+	}
+
+	ep := docker.Endpoint{
+		EndpointMeta: docker.EndpointMeta{
+			Host: name,
+		},
+	}
+
+	clientOpts, err := ep.ClientOpts()
+	if err != nil {
+		return nil, err
+	}
+
+	return dockerclient.NewClientWithOpts(clientOpts...)
+}
+
+func configFromContext(endpointName string, s ctxstore.Reader) (clientcmd.ClientConfig, error) {
+	if strings.HasPrefix(endpointName, "kubernetes://") {
+		u, _ := url.Parse(endpointName)
+		if kubeconfig := u.Query().Get("kubeconfig"); kubeconfig != "" {
+			_ = os.Setenv(clientcmd.RecommendedConfigPathEnvVar, kubeconfig)
+		}
+		rules := clientcmd.NewDefaultClientConfigLoadingRules()
+		apiConfig, err := rules.Load()
+		if err != nil {
+			return nil, err
+		}
+		return clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}), nil
+	}
+	return ctxkube.ConfigFromContext(endpointName, s)
+}
+
+type internalAPI struct {
+	dockerCli command.Cli
+}
+
+func (a *internalAPI) DockerAPI(name string) (dockerclient.APIClient, error) {
+	if name == "" {
+		name = a.dockerCli.CurrentContext()
+	}
+	return clientForEndpoint(a.dockerCli, name)
+}
diff --git a/pkg/compose/build_classic.go b/pkg/compose/build_classic.go
index e362ae247..3a41e618b 100644
--- a/pkg/compose/build_classic.go
+++ b/pkg/compose/build_classic.go
@@ -89,6 +89,10 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
 		}
 	}
 
+	if len(options.Platforms) > 1 {
+		return "", errors.Errorf("this builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use multi-arch builder")
+	}
+
 	switch {
 	case isLocalDir(specifiedContext):
 		contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, dockerfileName)
diff --git a/pkg/utils/slices.go b/pkg/utils/slices.go
new file mode 100644
index 000000000..3b635c25d
--- /dev/null
+++ b/pkg/utils/slices.go
@@ -0,0 +1,30 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package utils
+
+import "reflect"
+
+// Contains helps to detect if a non-comparable struct is part of an array
+// only use this method if you can't rely on existing golang Contains function of slices (https://pkg.go.dev/golang.org/x/exp/slices#Contains)
+func Contains[T any](origin []T, element T) bool {
+	for _, v := range origin {
+		if reflect.DeepEqual(v, element) {
+			return true
+		}
+	}
+	return false
+}


Test patch:
diff --git a/pkg/e2e/build_test.go b/pkg/e2e/build_test.go
index 178ed7f9b..7eeebf948 100644
--- a/pkg/e2e/build_test.go
+++ b/pkg/e2e/build_test.go
@@ -243,3 +243,105 @@ func TestBuildImageDependencies(t *testing.T) {
 		t.Skip("See https://github.com/docker/compose/issues/9232")
 	})
 }
+
+func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
+	c := NewParallelCLI(t)
+
+	// declare builder
+	result := c.RunDockerCmd(t, "buildx", "create", "--name", "build-platform", "--use", "--bootstrap")
+	assert.NilError(t, result.Error)
+
+	t.Cleanup(func() {
+		c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test/platforms", "down")
+		_ = c.RunDockerCmd(t, "buildx", "rm", "-f", "build-platform")
+	})
+
+	t.Run("platform not supported by builder", func(t *testing.T) {
+		res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms",
+			"-f", "fixtures/build-test/platforms/compose-unsupported-platform.yml", "build")
+		res.Assert(t, icmd.Expected{
+			ExitCode: 17,
+			Err:      "failed to solve: alpine: no match for platform in",
+		})
+	})
+
+	t.Run("multi-arch build ok", func(t *testing.T) {
+		res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms", "build")
+		assert.NilError(t, res.Error, res.Stderr())
+		res.Assert(t, icmd.Expected{Out: "I am building for linux/arm64"})
+		res.Assert(t, icmd.Expected{Out: "I am building for linux/amd64"})
+
+	})
+
+	t.Run("multi-arch multi service builds ok", func(t *testing.T) {
+		res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms",
+			"-f", "fixtures/build-test/platforms/compose-multiple-platform-builds.yaml", "build")
+		assert.NilError(t, res.Error, res.Stderr())
+		res.Assert(t, icmd.Expected{Out: "I'm Service A and I am building for linux/arm64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service A and I am building for linux/amd64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service B and I am building for linux/arm64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service B and I am building for linux/amd64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service C and I am building for linux/arm64"})
+		res.Assert(t, icmd.Expected{Out: "I'm Service C and I am building for linux/amd64"})
+	})
+
+	t.Run("multi-arch up --build", func(t *testing.T) {
+		res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms", "up", "--build")
+		assert.NilError(t, res.Error, res.Stderr())
+		res.Assert(t, icmd.Expected{Out: "platforms-platforms-1 exited with code 0"})
+	})
+
+	t.Run("use DOCKER_DEFAULT_PLATFORM value when up --build", func(t *testing.T) {
+		cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/platforms", "up", "--build")
+		res := icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
+			cmd.Env = append(cmd.Env, "DOCKER_DEFAULT_PLATFORM=linux/amd64")
+		})
+		assert.NilError(t, res.Error, res.Stderr())
+		res.Assert(t, icmd.Expected{Out: "I am building for linux/amd64"})
+		assert.Assert(t, !strings.Contains(res.Stdout(), "I am building for linux/arm64"))
+	})
+}
+
+func TestBuildPlatformsStandardErrors(t *testing.T) {
+	c := NewParallelCLI(t)
+
+	t.Run("no platform support with Classic Builder", func(t *testing.T) {
+		cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/platforms", "build")
+
+		res := icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
+			cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=0")
+		})
+		res.Assert(t, icmd.Expected{
+			ExitCode: 1,
+			Err:      "this builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use multi-arch builder",
+		})
+	})
+
+	t.Run("builder does not support multi-arch", func(t *testing.T) {
+		res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms", "build")
+		res.Assert(t, icmd.Expected{
+			ExitCode: 17,
+			Err:      `multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use")`,
+		})
+	})
+
+	t.Run("service platform not defined in platforms build section", func(t *testing.T) {
+		res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms",
+			"-f", "fixtures/build-test/platforms/compose-service-platform-not-in-build-platforms.yaml", "build")
+		res.Assert(t, icmd.Expected{
+			ExitCode: 1,
+			Err:      `service.platform should be part of the service.build.platforms: "linux/riscv64"`,
+		})
+	})
+
+	t.Run("DOCKER_DEFAULT_PLATFORM value not defined in platforms build section", func(t *testing.T) {
+		cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/platforms", "build")
+		res := icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
+			cmd.Env = append(cmd.Env, "DOCKER_DEFAULT_PLATFORM=windows/amd64")
+		})
+		res.Assert(t, icmd.Expected{
+			ExitCode: 1,
+			Err:      `DOCKER_DEFAULT_PLATFORM value should be part of the service.build.platforms: "windows/amd64"`,
+		})
+	})
+}
diff --git a/pkg/e2e/fixtures/build-test/platforms/Dockerfile b/pkg/e2e/fixtures/build-test/platforms/Dockerfile
new file mode 100644
index 000000000..ef22c17f6
--- /dev/null
+++ b/pkg/e2e/fixtures/build-test/platforms/Dockerfile
@@ -0,0 +1,22 @@
+#   Copyright 2020 Docker Compose CLI authors
+
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+
+#       http://www.apache.org/licenses/LICENSE-2.0
+
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+FROM --platform=$BUILDPLATFORM golang:alpine AS build
+
+ARG TARGETPLATFORM
+ARG BUILDPLATFORM
+RUN echo "I am building for $TARGETPLATFORM, running on $BUILDPLATFORM" > /log
+
+FROM alpine
+COPY --from=build /log /log
diff --git a/pkg/e2e/fixtures/build-test/platforms/compose-multiple-platform-builds.yaml b/pkg/e2e/fixtures/build-test/platforms/compose-multiple-platform-builds.yaml
new file mode 100644
index 000000000..aac3a3db9
--- /dev/null
+++ b/pkg/e2e/fixtures/build-test/platforms/compose-multiple-platform-builds.yaml
@@ -0,0 +1,23 @@
+services:
+  serviceA:
+    image: build-test-platform-a:test
+    build:
+      context: ./contextServiceA
+      platforms:
+        - linux/amd64
+        - linux/arm64
+  serviceB:
+    image: build-test-platform-b:test
+    build:
+      context: ./contextServiceB
+      platforms:
+        - linux/amd64
+        - linux/arm64
+  serviceC:
+    image: build-test-platform-c:test
+    build:
+      context: ./contextServiceC
+      platforms:
+        - linux/amd64
+        - linux/arm64
+
diff --git a/pkg/e2e/fixtures/build-test/platforms/compose-service-platform-not-in-build-platforms.yaml b/pkg/e2e/fixtures/build-test/platforms/compose-service-platform-not-in-build-platforms.yaml
new file mode 100644
index 000000000..bed88fa51
--- /dev/null
+++ b/pkg/e2e/fixtures/build-test/platforms/compose-service-platform-not-in-build-platforms.yaml
@@ -0,0 +1,9 @@
+services:
+  platforms:
+    image: build-test-platform:test
+    platform: linux/riscv64
+    build:
+      context: .
+      platforms:
+        - linux/amd64
+        - linux/arm64
diff --git a/pkg/e2e/fixtures/build-test/platforms/compose-unsupported-platform.yml b/pkg/e2e/fixtures/build-test/platforms/compose-unsupported-platform.yml
new file mode 100644
index 000000000..e33428291
--- /dev/null
+++ b/pkg/e2e/fixtures/build-test/platforms/compose-unsupported-platform.yml
@@ -0,0 +1,8 @@
+services:
+  platforms:
+    image: build-test-platform:test
+    build:
+      context: .
+      platforms:
+        - unsupported/unsupported
+        - linux/amd64
diff --git a/pkg/e2e/fixtures/build-test/platforms/compose.yaml b/pkg/e2e/fixtures/build-test/platforms/compose.yaml
new file mode 100644
index 000000000..73421f479
--- /dev/null
+++ b/pkg/e2e/fixtures/build-test/platforms/compose.yaml
@@ -0,0 +1,9 @@
+services:
+  platforms:
+    image: build-test-platform:test
+    build:
+      context: .
+      platforms:
+        - linux/amd64
+        - linux/arm64
+
diff --git a/pkg/e2e/fixtures/build-test/platforms/contextServiceA/Dockerfile b/pkg/e2e/fixtures/build-test/platforms/contextServiceA/Dockerfile
new file mode 100644
index 000000000..468b2b10d
--- /dev/null
+++ b/pkg/e2e/fixtures/build-test/platforms/contextServiceA/Dockerfile
@@ -0,0 +1,22 @@
+#   Copyright 2020 Docker Compose CLI authors
+
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+
+#       http://www.apache.org/licenses/LICENSE-2.0
+
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+FROM --platform=$BUILDPLATFORM golang:alpine AS build
+
+ARG TARGETPLATFORM
+ARG BUILDPLATFORM
+RUN echo "I'm Service A and I am building for $TARGETPLATFORM, running on $BUILDPLATFORM" > /log
+
+FROM alpine
+COPY --from=build /log /log
diff --git a/pkg/e2e/fixtures/build-test/platforms/contextServiceB/Dockerfile b/pkg/e2e/fixtures/build-test/platforms/contextServiceB/Dockerfile
new file mode 100644
index 000000000..cfa2ae34a
--- /dev/null
+++ b/pkg/e2e/fixtures/build-test/platforms/contextServiceB/Dockerfile
@@ -0,0 +1,22 @@
+#   Copyright 2020 Docker Compose CLI authors
+
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+
+#       http://www.apache.org/licenses/LICENSE-2.0
+
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+FROM --platform=$BUILDPLATFORM golang:alpine AS build
+
+ARG TARGETPLATFORM
+ARG BUILDPLATFORM
+RUN echo "I'm Service B and I am building for $TARGETPLATFORM, running on $BUILDPLATFORM" > /log
+
+FROM alpine
+COPY --from=build /log /log
diff --git a/pkg/e2e/fixtures/build-test/platforms/contextServiceC/Dockerfile b/pkg/e2e/fixtures/build-test/platforms/contextServiceC/Dockerfile
new file mode 100644
index 000000000..3216f6182
--- /dev/null
+++ b/pkg/e2e/fixtures/build-test/platforms/contextServiceC/Dockerfile
@@ -0,0 +1,22 @@
+#   Copyright 2020 Docker Compose CLI authors
+
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+
+#       http://www.apache.org/licenses/LICENSE-2.0
+
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+FROM --platform=$BUILDPLATFORM golang:alpine AS build
+
+ARG TARGETPLATFORM
+ARG BUILDPLATFORM
+RUN echo "I'm Service C and I am building for $TARGETPLATFORM, running on $BUILDPLATFORM" > /log
+
+FROM alpine
+COPY --from=build /log /log
diff --git a/pkg/utils/slices_test.go b/pkg/utils/slices_test.go
new file mode 100644
index 000000000..d9468afef
--- /dev/null
+++ b/pkg/utils/slices_test.go
@@ -0,0 +1,95 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package utils
+
+import (
+	"testing"
+
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+func TestContains(t *testing.T) {
+	source := []specs.Platform{
+		{
+			Architecture: "linux/amd64",
+			OS:           "darwin",
+			OSVersion:    "",
+			OSFeatures:   nil,
+			Variant:      "",
+		},
+		{
+			Architecture: "linux/arm64",
+			OS:           "linux",
+			OSVersion:    "12",
+			OSFeatures:   nil,
+			Variant:      "v8",
+		},
+		{
+			Architecture: "",
+			OS:           "",
+			OSVersion:    "",
+			OSFeatures:   nil,
+			Variant:      "",
+		},
+	}
+
+	type args struct {
+		origin  []specs.Platform
+		element specs.Platform
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "element found",
+			args: args{
+				origin: source,
+				element: specs.Platform{
+					Architecture: "linux/arm64",
+					OS:           "linux",
+					OSVersion:    "12",
+					OSFeatures:   nil,
+					Variant:      "v8",
+				},
+			},
+			want: true,
+		},
+		{
+			name: "element not found",
+			args: args{
+				origin: source,
+				element: specs.Platform{
+					Architecture: "linux/arm64",
+					OS:           "darwin",
+					OSVersion:    "12",
+					OSFeatures:   nil,
+					Variant:      "v8",
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := Contains(tt.args.origin, tt.args.element); got != tt.want {
+				t.Errorf("Contains() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}