nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: compose-spec/compose-go
Pull request #711 (2024-11-12 10:02:30)

fix(variables): Add number suffix support to the variable extractor
Fixes https://github.com/docker/compose/issues/12285

Added support for suffix support in variable names, for example:
```
${FOO_1}
${BAR_1:-with-default}
```


Patch:
diff --git a/template/variables.go b/template/variables.go
index fb32f9c..84edee5 100644
--- a/template/variables.go
+++ b/template/variables.go
@@ -108,6 +108,9 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, boo
 			if r >= 'A' && r <= 'Z' {
 				return false
 			}
+			if r >= '0' && r <= '9' {
+				return false
+			}
 			if r == '_' {
 				return false
 			}


Test patch:
diff --git a/template/variables_test.go b/template/variables_test.go
index 00133d5..7239b01 100644
--- a/template/variables_test.go
+++ b/template/variables_test.go
@@ -50,6 +50,15 @@ func TestExtractVariables(t *testing.T) {
 				"bar": {Name: "bar"},
 			},
 		},
+		{
+			name: "variable-without-curly-braces-and-with-number-suffix",
+			dict: map[string]interface{}{
+				"foo": "$bar_1",
+			},
+			expected: map[string]Variable{
+				"bar_1": {Name: "bar_1"},
+			},
+		},
 		{
 			name: "variable",
 			dict: map[string]interface{}{
@@ -68,6 +77,15 @@ func TestExtractVariables(t *testing.T) {
 				"bar": {Name: "bar", DefaultValue: "", Required: true},
 			},
 		},
+		{
+			name: "required-variable-with-number-suffix",
+			dict: map[string]interface{}{
+				"foo": "${bar_1?:foo}",
+			},
+			expected: map[string]Variable{
+				"bar_1": {Name: "bar_1", DefaultValue: "", Required: true},
+			},
+		},
 		{
 			name: "required-variable2",
 			dict: map[string]interface{}{
@@ -95,6 +113,24 @@ func TestExtractVariables(t *testing.T) {
 				"bar": {Name: "bar", DefaultValue: "foo"},
 			},
 		},
+		{
+			name: "default-variable-with-number-suffix",
+			dict: map[string]interface{}{
+				"foo": "${bar_1:-foo}",
+			},
+			expected: map[string]Variable{
+				"bar_1": {Name: "bar_1", DefaultValue: "foo"},
+			},
+		},
+		{
+			name: "default-variable2-with-number-suffix",
+			dict: map[string]interface{}{
+				"foo": "${bar_1-foo}",
+			},
+			expected: map[string]Variable{
+				"bar_1": {Name: "bar_1", DefaultValue: "foo"},
+			},
+		},
 		{
 			name: "multiple-values",
 			dict: map[string]interface{}{
Repository: compose-spec/compose-go
Pull request #718 (2024-12-09 11:04:49)

extending a service, dependencies must be excluded
exclude `depends_on` and `volumes_from` when extending a service, as documented by https://docs.docker.com/compose/how-tos/multiple-compose-files/extends/#exceptions-and-limitations

closes https://github.com/docker/compose/issues/12360


Patch:
diff --git a/loader/extends.go b/loader/extends.go
index c48b1e4..2a127d2 100644
--- a/loader/extends.go
+++ b/loader/extends.go
@@ -27,6 +27,10 @@ import (
 	"github.com/compose-spec/compose-go/v2/types"
 )
 
+// as we use another service definition by `extends`, we must exclude attributes which creates dependency to another service
+// see https://github.com/compose-spec/compose-spec/blob/main/05-services.md#restrictions
+var exclusions = []string{"extends", "depends_on", "volumes_from"}
+
 func ApplyExtends(ctx context.Context, dict map[string]any, opts *Options, tracker *cycleTracker, post ...PostProcessor) error {
 	a, ok := dict["services"]
 	if !ok {
@@ -123,7 +127,9 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
 	if err != nil {
 		return nil, err
 	}
-	delete(merged, "extends")
+	for _, exclusion := range exclusions {
+		delete(merged, exclusion)
+	}
 	services[name] = merged
 	return merged, nil
 }


Test patch:
diff --git a/loader/extends_test.go b/loader/extends_test.go
index 8f25f35..3a14c54 100644
--- a/loader/extends_test.go
+++ b/loader/extends_test.go
@@ -322,7 +322,6 @@ services:
       file: ./testdata/extends/depends_on.yaml
       service: with_volumes_from
 `,
-			wantErr: `service "bar" depends on undefined service "zot"`,
 		},
 		{
 			name: "depends_on",
@@ -334,7 +333,6 @@ services:
       file: ./testdata/extends/depends_on.yaml
       service: with_depends_on
 `,
-			wantErr: `service "bar" depends on undefined service "zot"`,
 		},
 		{
 			name: "shared ipc",
@@ -369,8 +367,11 @@ services:
 					Content: []byte(tt.yaml),
 				}},
 			})
+			if tt.wantErr == "" {
+				assert.NilError(t, err)
+				return
+			}
 			assert.ErrorContains(t, err, tt.wantErr)
-
 			// Do the same but with a local `zot` service matching the imported reference
 			_, err = LoadWithContext(context.Background(), types.ConfigDetails{
 				ConfigFiles: []types.ConfigFile{{
Repository: compose-spec/compose-go
Pull request #721 (2024-12-19 10:09:29)

exlusions apply to source, not merged result
fixes https://github.com/docker/compose/issues/12402


Patch:
diff --git a/loader/extends.go b/loader/extends.go
index 2a127d2..6105173 100644
--- a/loader/extends.go
+++ b/loader/extends.go
@@ -29,7 +29,7 @@ import (
 
 // as we use another service definition by `extends`, we must exclude attributes which creates dependency to another service
 // see https://github.com/compose-spec/compose-spec/blob/main/05-services.md#restrictions
-var exclusions = []string{"extends", "depends_on", "volumes_from"}
+var exclusions = []string{"depends_on", "volumes_from"}
 
 func ApplyExtends(ctx context.Context, dict map[string]any, opts *Options, tracker *cycleTracker, post ...PostProcessor) error {
 	a, ok := dict["services"]
@@ -123,13 +123,15 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
 			},
 		})
 	}
+	for _, exclusion := range exclusions {
+		delete(source, exclusion)
+	}
 	merged, err := override.ExtendService(source, service)
 	if err != nil {
 		return nil, err
 	}
-	for _, exclusion := range exclusions {
-		delete(merged, exclusion)
-	}
+
+	delete(merged, "extends")
 	services[name] = merged
 	return merged, nil
 }


Test patch:
diff --git a/loader/extends_test.go b/loader/extends_test.go
index 3a14c54..22d9074 100644
--- a/loader/extends_test.go
+++ b/loader/extends_test.go
@@ -386,6 +386,29 @@ services:
 	}
 }
 
+func TestLoadExtendsDependsOn(t *testing.T) {
+	yaml := `
+name: extends-depends_on
+services:
+  service_a:
+    image: a
+    depends_on:
+      - service_c
+  
+  service_b:
+    extends: service_a
+    depends_on:
+      - service_a
+
+  service_c:
+    image: c`
+	p, err := loadYAML(yaml)
+	assert.NilError(t, err)
+	assert.DeepEqual(t, p.Services["service_b"].DependsOn, types.DependsOnConfig{
+		"service_a": types.ServiceDependency{Condition: types.ServiceConditionStarted, Required: true},
+	})
+}
+
 func TestLoadExtendsListener(t *testing.T) {
 	yaml := `
   name: listener-extends
Repository: compose-spec/compose-go
Pull request #723 (2024-12-22 05:13:35)

Apply `maybeUnixPath` to ssh path in build config
According to build ssh config's document:

```
ssh property syntax can be either:

default: Let the builder connect to the SSH-agent.
ID=path: A key/value definition of an ID and the associated path. It can be either a [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) file, or path to ssh-agent socket.

Using a custom id myproject with path to a local SSH key:

build:
  context: .
  ssh:
    - myproject=~/.ssh/myproject.pem
```

SSH config's value type is the path to the key, therefore should be resolved with `paths.ResolveRelativePaths`.


Patch:
diff --git a/paths/resolve.go b/paths/resolve.go
index 8bab0b4..c58cb41 100644
--- a/paths/resolve.go
+++ b/paths/resolve.go
@@ -36,6 +36,7 @@ func ResolveRelativePaths(project map[string]any, base string, remotes []RemoteR
 	r.resolvers = map[tree.Path]resolver{
 		"services.*.build.context":               r.absContextPath,
 		"services.*.build.additional_contexts.*": r.absContextPath,
+		"services.*.build.ssh.*":                 r.maybeUnixPath,
 		"services.*.env_file.*.path":             r.absPath,
 		"services.*.label_file.*":                r.absPath,
 		"services.*.extends.file":                r.absExtendsPath,
diff --git a/paths/unix.go b/paths/unix.go
index 5ab2616..d47f936 100644
--- a/paths/unix.go
+++ b/paths/unix.go
@@ -24,7 +24,10 @@ import (
 )
 
 func (r *relativePathsResolver) maybeUnixPath(a any) (any, error) {
-	p := a.(string)
+	p, ok := a.(string)
+	if !ok {
+		return a, nil
+	}
 	p = ExpandUser(p)
 	// Check if source is an absolute path (either Unix or Windows), to
 	// handle a Windows client with a Unix daemon or vice-versa.


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 125bf29..2020954 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -2477,7 +2477,7 @@ services:
 	assert.NilError(t, err)
 	sshValue, err := svc.Build.SSH.Get("key1")
 	assert.NilError(t, err)
-	assert.Equal(t, "value1", sshValue)
+	assert.Equal(t, filepath.Join(os.Getenv("PWD"), "value1"), sshValue)
 }
 
 func TestLoadSSHWithKeysValuesInBuildConfig(t *testing.T) {
@@ -2490,6 +2490,7 @@ services:
       ssh:
         - key1=value1
         - key2=value2
+        - default
 `)
 	assert.NilError(t, err)
 	svc, err := actual.GetService("test")
@@ -2497,11 +2498,15 @@ services:
 
 	sshValue, err := svc.Build.SSH.Get("key1")
 	assert.NilError(t, err)
-	assert.Equal(t, "value1", sshValue)
+	assert.Equal(t, filepath.Join(os.Getenv("PWD"), "value1"), sshValue)
 
 	sshValue, err = svc.Build.SSH.Get("key2")
 	assert.NilError(t, err)
-	assert.Equal(t, "value2", sshValue)
+	assert.Equal(t, filepath.Join(os.Getenv("PWD"), "value2"), sshValue)
+
+	sshValue, err = svc.Build.SSH.Get("default")
+	assert.NilError(t, err)
+	assert.Equal(t, "", sshValue)
 }
 
 func TestProjectNameInterpolation(t *testing.T) {
Repository: compose-spec/compose-go
Pull request #724 (2024-12-22 06:03:35)

Apply `transformStringOrList` to `services.*.label_file`
According to label_file's spec: https://github.com/compose-spec/compose-spec/pull/546, it supports string or a list:

```yaml label_file by ndeloof · Pull Request #546 · compose-spec/compose-spec
services:
  one:
    label_file: ./app.labels
  two:
    label_file: 
      - ./app.labels
      - ./additional.labels
```

But without applying `transform.Canonical` it only supports list format. This PR applies `transformStringOrList` to it to fix this.


Patch:
diff --git a/transform/canonical.go b/transform/canonical.go
index ff5bb37..2ace976 100644
--- a/transform/canonical.go
+++ b/transform/canonical.go
@@ -30,6 +30,7 @@ func init() {
 	transformers["services.*.build.additional_contexts"] = transformKeyValue
 	transformers["services.*.depends_on"] = transformDependsOn
 	transformers["services.*.env_file"] = transformEnvFile
+	transformers["services.*.label_file"] = transformStringOrList
 	transformers["services.*.extends"] = transformExtends
 	transformers["services.*.networks"] = transformServiceNetworks
 	transformers["services.*.volumes.*"] = transformVolumeMount


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 8f062c0..125bf29 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -915,6 +915,55 @@ networks:
 	assertEqual(t, expected, config)
 }
 
+func TestLoadWithLabelFile(t *testing.T) {
+	workingDir, err := os.Getwd()
+	assert.NilError(t, err)
+	config, err := loadYAML(`
+name: load-label-file
+services:
+  service_1:
+    label_file: ./example1.label
+  service_2:
+    label_file:
+      - ./example2.label
+`)
+	assert.NilError(t, err)
+
+	expected := &types.Project{
+		Name:        "load-label-file",
+		Environment: types.Mapping{"COMPOSE_PROJECT_NAME": "load-label-file"},
+		WorkingDir:  workingDir,
+		Services: types.Services{
+			"service_1": {
+				Name:        "service_1",
+				Environment: types.MappingWithEquals{},
+				Labels: types.Labels{
+					"BAR":                   "bar_from_label_file",
+					"BAZ":                   "baz_from_label_file",
+					"FOO":                   "foo_from_label_file",
+					"LABEL.WITH.DOT":        "ok",
+					"LABEL_WITH_UNDERSCORE": "ok",
+				},
+				LabelFiles: []string{
+					filepath.Join(workingDir, "example1.label"),
+				},
+			},
+			"service_2": {
+				Name:        "service_2",
+				Environment: types.MappingWithEquals{},
+				Labels: types.Labels{
+					"BAR": "bar_from_label_file_2",
+					"QUX": "quz_from_label_file_2",
+				},
+				LabelFiles: []string{
+					filepath.Join(workingDir, "example2.label"),
+				},
+			},
+		},
+	}
+	assertEqual(t, config, expected)
+}
+
 func TestUnsupportedProperties(t *testing.T) {
 	dict := `
 name: test
Repository: compose-spec/compose-go
Pull request #728 (2025-01-21 10:34:25)

drop extends restrictions
fix https://github.com/compose-spec/compose-spec/issues/554


Patch:
diff --git a/loader/extends.go b/loader/extends.go
index 6105173..4a04654 100644
--- a/loader/extends.go
+++ b/loader/extends.go
@@ -27,10 +27,6 @@ import (
 	"github.com/compose-spec/compose-go/v2/types"
 )
 
-// as we use another service definition by `extends`, we must exclude attributes which creates dependency to another service
-// see https://github.com/compose-spec/compose-spec/blob/main/05-services.md#restrictions
-var exclusions = []string{"depends_on", "volumes_from"}
-
 func ApplyExtends(ctx context.Context, dict map[string]any, opts *Options, tracker *cycleTracker, post ...PostProcessor) error {
 	a, ok := dict["services"]
 	if !ok {
@@ -123,9 +119,6 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
 			},
 		})
 	}
-	for _, exclusion := range exclusions {
-		delete(source, exclusion)
-	}
 	merged, err := override.ExtendService(source, service)
 	if err != nil {
 		return nil, err


Test patch:
diff --git a/loader/extends_test.go b/loader/extends_test.go
index 22d9074..9bf1861 100644
--- a/loader/extends_test.go
+++ b/loader/extends_test.go
@@ -322,6 +322,7 @@ services:
       file: ./testdata/extends/depends_on.yaml
       service: with_volumes_from
 `,
+			wantErr: `service "bar" depends on undefined service "zot"`,
 		},
 		{
 			name: "depends_on",
@@ -333,6 +334,7 @@ services:
       file: ./testdata/extends/depends_on.yaml
       service: with_depends_on
 `,
+			wantErr: `service "bar" depends on undefined service "zot"`,
 		},
 		{
 			name: "shared ipc",
@@ -397,7 +399,7 @@ services:
   
   service_b:
     extends: service_a
-    depends_on:
+    depends_on: !override
       - service_a
 
   service_c:
Repository: compose-spec/compose-go
Pull request #752 (2025-03-10 07:56:15)

report error using KEY=VALUE with whitespaces
fixes https://github.com/docker/compose/issues/12602


Patch:
diff --git a/types/mapping.go b/types/mapping.go
index 87f1631..1fc2aea 100644
--- a/types/mapping.go
+++ b/types/mapping.go
@@ -20,6 +20,7 @@ import (
 	"fmt"
 	"sort"
 	"strings"
+	"unicode"
 )
 
 // MappingWithEquals is a mapping type that can be converted from a list of
@@ -94,6 +95,9 @@ func (m *MappingWithEquals) DecodeMapstructure(value interface{}) error {
 		mapping := make(MappingWithEquals, len(v))
 		for _, s := range v {
 			k, e, ok := strings.Cut(fmt.Sprint(s), "=")
+			if unicode.IsSpace(rune(k[len(k)-1])) {
+				return fmt.Errorf("environment variable %s is declared with a trailing space", k)
+			}
 			if !ok {
 				mapping[k] = nil
 			} else {


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index c0c7565..b510aa8 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -3733,3 +3733,14 @@ services:
 	assert.Equal(t, policy, types.PullPolicyRefresh)
 	assert.Equal(t, duration, 2*24*time.Hour)
 }
+
+func TestEnvironmentWhitespace(t *testing.T) {
+	_, err := loadYAML(`
+name: environment_whitespace
+services:
+  test:
+    environment:
+      - DEBUG = true
+`)
+	assert.Check(t, strings.Contains(err.Error(), "'services[test].environment': environment variable DEBUG  is declared with a trailing space"))
+}
Repository: compose-spec/compose-go
Pull request #755 (2025-03-25 02:47:09)

feat: Add wildcard support for services selector
https://github.com/docker/compose/issues/12657

Added wildcard support for services selector


Patch:
diff --git a/types/project.go b/types/project.go
index 445bd91..973874d 100644
--- a/types/project.go
+++ b/types/project.go
@@ -190,16 +190,25 @@ func (p *Project) getServicesByNames(names ...string) (Services, []string) {
 	if len(names) == 0 {
 		return p.Services, nil
 	}
+
 	services := Services{}
 	var servicesNotFound []string
 	for _, name := range names {
-		service, ok := p.Services[name]
-		if !ok {
+		matched := false
+
+		for serviceName, service := range p.Services {
+			match, _ := filepath.Match(name, serviceName)
+			if match {
+				services[serviceName] = service
+				matched = true
+			}
+		}
+
+		if !matched {
 			servicesNotFound = append(servicesNotFound, name)
-			continue
 		}
-		services[name] = service
 	}
+
 	return services, servicesNotFound
 }
 


Test patch:
diff --git a/types/project_test.go b/types/project_test.go
index cf7dc13..3f8a48b 100644
--- a/types/project_test.go
+++ b/types/project_test.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"slices"
+	"sort"
 	"strings"
 	"testing"
 
@@ -281,6 +282,18 @@ func TestWithServices(t *testing.T) {
 	assert.DeepEqual(t, seen, []string{"service_1", "service_2", "service_4"})
 }
 
+func TestWithServicesWithWildcard(t *testing.T) {
+	p := makeProject()
+	var seen []string
+	err := p.ForEachService([]string{"service_*"}, func(name string, _ *ServiceConfig) error {
+		seen = append(seen, name)
+		return nil
+	}, IgnoreDependencies)
+	assert.NilError(t, err)
+	sort.Strings(seen)
+	assert.DeepEqual(t, seen, []string{"service_1", "service_2", "service_3", "service_4", "service_5", "service_6"})
+}
+
 func TestServicesWithBuild(t *testing.T) {
 	p := makeProject()
 	assert.Equal(t, len(p.ServicesWithBuild()), 0)
Repository: compose-spec/compose-go
Pull request #768 (2025-04-09 06:57:43)

Merge compose extensions.
Fixes https://github.com/docker/compose/issues/12724.

ndeloof: for legal reasons, contributor's commit must be signed-off. Please amend and force-push your branch

By the way, what are you using x- extension fields for?

Patch:
diff --git a/override/merge.go b/override/merge.go
index 872c5f8..8baade9 100644
--- a/override/merge.go
+++ b/override/merge.go
@@ -20,7 +20,6 @@ import (
 	"cmp"
 	"fmt"
 	"slices"
-	"strings"
 
 	"github.com/compose-spec/compose-go/v2/tree"
 )
@@ -104,7 +103,7 @@ func mergeYaml(e any, o any, p tree.Path) (any, error) {
 func mergeMappings(mapping map[string]any, other map[string]any, p tree.Path) (map[string]any, error) {
 	for k, v := range other {
 		e, ok := mapping[k]
-		if !ok || strings.HasPrefix(k, "x-") {
+		if !ok {
 			mapping[k] = v
 			continue
 		}


Test patch:
diff --git a/loader/loader_yaml_test.go b/loader/loader_yaml_test.go
index 1ea4566..a4318ac 100644
--- a/loader/loader_yaml_test.go
+++ b/loader/loader_yaml_test.go
@@ -30,6 +30,9 @@ func TestParseYAMLFiles(t *testing.T) {
 			{
 				Filename: "test.yaml",
 				Content: []byte(`
+x-extension:
+    test1: first
+
 services:
   test:
     image: foo
@@ -40,6 +43,9 @@ services:
 			{
 				Filename: "override.yaml",
 				Content: []byte(`
+x-extension:
+    test2: second
+
 services:
   test:
     image: bar
@@ -58,6 +64,10 @@ services:
 				"init":    false,
 			},
 		},
+		"x-extension": map[string]interface{}{
+			"test1": "first",
+			"test2": "second",
+		},
 	})
 }
Repository: compose-spec/compose-go
Pull request #771 (2025-04-22 14:43:14)

add npipe to the volume type enum values
it was forgotten during the introduction of the image type


Patch:
diff --git a/schema/compose-spec.json b/schema/compose-spec.json
index ee1137f..d6d4a0c 100644
--- a/schema/compose-spec.json
+++ b/schema/compose-spec.json
@@ -419,7 +419,7 @@
                 "required": ["type"],
                 "properties": {
                   "type": {"type": "string",
-                    "enum": ["bind", "volume", "tmpfs", "cluster", "image"]
+                    "enum": ["bind", "volume", "tmpfs", "cluster", "npipe", "image"]
                   },
                   "source": {"type": "string"},
                   "target": {"type": "string"},


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 6b07c37..fd21703 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -3799,3 +3799,24 @@ services:
 		Image:  &types.ServiceVolumeImage{SubPath: "/foo"},
 	})
 }
+
+func TestNpipeVolume(t *testing.T) {
+	p, err := loadYAML(`
+name: imageVolume
+services:
+  test:
+    volumes:
+      - type: npipe
+        source: \\.\pipe\docker_engine
+        target: \\.\pipe\docker_engine
+        image:
+          subpath: /foo
+`)
+	assert.NilError(t, err)
+	assert.DeepEqual(t, p.Services["test"].Volumes[0], types.ServiceVolumeConfig{
+		Type:   "npipe",
+		Source: "\\\\.\\pipe\\docker_engine",
+		Target: "\\\\.\\pipe\\docker_engine",
+		Image:  &types.ServiceVolumeImage{SubPath: "/foo"},
+	})
+}
Repository: compose-spec/compose-go
Pull request #775 (2025-04-25 07:31:42)

detect conflict with multiple mounts


Patch:
diff --git a/loader/validate.go b/loader/validate.go
index 0d95325..f6308b7 100644
--- a/loader/validate.go
+++ b/loader/validate.go
@@ -172,6 +172,24 @@ func checkConsistency(project *types.Project) error { //nolint:gocyclo
 				}
 			}
 		}
+
+		mounts := map[string]string{}
+		for i, tmpfs := range s.Tmpfs {
+			loc := fmt.Sprintf("services.%s.tmpfs[%d]", s.Name, i)
+			path, _, _ := strings.Cut(tmpfs, ":")
+			if p, ok := mounts[path]; ok {
+				return fmt.Errorf("%s: target %s already mounted as %s", loc, path, p)
+			}
+			mounts[path] = loc
+		}
+		for i, volume := range s.Volumes {
+			loc := fmt.Sprintf("services.%s.volumes[%d]", s.Name, i)
+			if p, ok := mounts[volume.Target]; ok {
+				return fmt.Errorf("%s: target %s already mounted as %s", loc, volume.Target, p)
+			}
+			mounts[volume.Target] = loc
+		}
+
 	}
 
 	for name, secret := range project.Secrets {


Test patch:
diff --git a/loader/validate_test.go b/loader/validate_test.go
index 5c1aa95..fdcd12e 100644
--- a/loader/validate_test.go
+++ b/loader/validate_test.go
@@ -407,3 +407,32 @@ func TestValidateWatch(t *testing.T) {
 		assert.ErrorContains(t, err, "depends on undefined service")
 	})
 }
+
+func TestValidateMountConflict(t *testing.T) {
+	project := &types.Project{
+		Services: types.Services{
+			"myservice": {
+				Name:  "myservice",
+				Image: "scratch",
+				Tmpfs: []string{
+					"/foo",
+					"/conflict:size=64m",
+				},
+				Volumes: []types.ServiceVolumeConfig{
+					{
+						Type:   "bind",
+						Target: "/bar",
+						Source: ".",
+					},
+					{
+						Type:   "bind",
+						Target: "/conflict",
+						Source: ".",
+					},
+				},
+			},
+		},
+	}
+	err := checkConsistency(project)
+	assert.Error(t, err, "services.myservice.volumes[1]: target /conflict already mounted as services.myservice.tmpfs[1]")
+}