Repository: docker/compose
Pull request #11207 (2023-11-20 11:04:27)

adopt compose-go/v2
**What I did**
adopt compose-go/v2

**Related issue**

**(not mandatory) A picture of a cute animal, if possible in relation to what you did**
![image](https://github.com/docker/compose/assets/132757/024acead-4a7e-491b-a29d-de915c03861f)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) Report
Attention: `34 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`f5e3ff0`)](https://app.codecov.io/gh/docker/compose/commit/f5e3ff03f085e430264d1b237481780d92ef3911?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) 56.81% compared to head [(`89e6e9e`)](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) 56.33%.

| [Files](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) | Patch % | Lines |
|---|---|---|
| [pkg/compose/publish.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-cGtnL2NvbXBvc2UvcHVibGlzaC5nbw==) | 0.00% | [6 Missing :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [cmd/compose/build.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-Y21kL2NvbXBvc2UvYnVpbGQuZ28=) | 62.50% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [cmd/compose/create.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-Y21kL2NvbXBvc2UvY3JlYXRlLmdv) | 62.50% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [pkg/api/api.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-cGtnL2FwaS9hcGkuZ28=) | 40.00% | [3 Missing :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [pkg/compose/compose.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-cGtnL2NvbXBvc2UvY29tcG9zZS5nbw==) | 82.35% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [pkg/compose/create.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-cGtnL2NvbXBvc2UvY3JlYXRlLmdv) | 57.14% | [0 Missing and 3 partials :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [pkg/compose/pull.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-cGtnL2NvbXBvc2UvcHVsbC5nbw==) | 75.00% | [3 Missing :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [cmd/compose/scale.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-Y21kL2NvbXBvc2Uvc2NhbGUuZ28=) | 60.00% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [cmd/compose/up.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-Y21kL2NvbXBvc2UvdXAuZ28=) | 71.42% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| [internal/sync/docker\_cp.go](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker#diff-aW50ZXJuYWwvc3luYy9kb2NrZXJfY3AuZ28=) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) |
| ... and [4 more](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker) | |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main   #11207      +/-   ##
==========================================
- Coverage   56.81%   56.33%   -0.49%     
==========================================
  Files         133      133              
  Lines       11500    11504       +4     
==========================================
- Hits         6534     6481      -53     
- Misses       4329     4384      +55     
- Partials      637      639       +2     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/docker/compose/pull/11207?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=docker).


Patch:
diff --git a/cmd/compose/build.go b/cmd/compose/build.go
index 8e4de1df4..76c7fc814 100644
--- a/cmd/compose/build.go
+++ b/cmd/compose/build.go
@@ -22,9 +22,8 @@ import (
 	"os"
 	"strings"
 
-	"github.com/compose-spec/compose-go/cli"
-	"github.com/compose-spec/compose-go/loader"
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/cli"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	cliopts "github.com/docker/cli/opts"
 	ui "github.com/docker/compose/v2/pkg/progress"
@@ -50,7 +49,14 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
 	var SSHKeys []types.SSHKey
 	var err error
 	if opts.ssh != "" {
-		SSHKeys, err = loader.ParseShortSSHSyntax(opts.ssh)
+		id, path, found := strings.Cut(opts.ssh, "=")
+		if !found && id != "default" {
+			return api.BuildOptions{}, fmt.Errorf("invalid ssh key %q", opts.ssh)
+		}
+		SSHKeys = append(SSHKeys, types.SSHKey{
+			ID:   id,
+			Path: path,
+		})
 		if err != nil {
 			return api.BuildOptions{}, err
 		}
diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go
index 55d48e883..d2f8e98e9 100644
--- a/cmd/compose/compose.go
+++ b/cmd/compose/compose.go
@@ -27,10 +27,10 @@ import (
 	"strings"
 	"syscall"
 
-	"github.com/compose-spec/compose-go/cli"
-	"github.com/compose-spec/compose-go/dotenv"
-	"github.com/compose-spec/compose-go/types"
-	composegoutils "github.com/compose-spec/compose-go/utils"
+	"github.com/compose-spec/compose-go/v2/cli"
+	"github.com/compose-spec/compose-go/v2/dotenv"
+	"github.com/compose-spec/compose-go/v2/types"
+	composegoutils "github.com/compose-spec/compose-go/v2/utils"
 	"github.com/docker/buildx/util/logutil"
 	dockercli "github.com/docker/cli/cli"
 	"github.com/docker/cli/cli-plugins/manager"
@@ -230,10 +230,10 @@ func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po
 		return nil, err
 	}
 
-	for i, s := range project.Services {
+	for name, s := range project.Services {
 		s.CustomLabels = map[string]string{
 			api.ProjectLabel:     project.Name,
-			api.ServiceLabel:     s.Name,
+			api.ServiceLabel:     name,
 			api.VersionLabel:     api.ComposeVersion,
 			api.WorkingDirLabel:  project.WorkingDir,
 			api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
@@ -242,7 +242,7 @@ func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po
 		if len(o.EnvFiles) != 0 {
 			s.CustomLabels[api.EnvironmentFileLabel] = strings.Join(o.EnvFiles, ",")
 		}
-		project.Services[i] = s
+		project.Services[name] = s
 	}
 
 	project.WithoutUnnecessaryResources()
diff --git a/cmd/compose/config.go b/cmd/compose/config.go
index b4a9baedf..2128c6cea 100644
--- a/cmd/compose/config.go
+++ b/cmd/compose/config.go
@@ -24,8 +24,8 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/compose-spec/compose-go/cli"
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/cli"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
@@ -210,7 +210,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err
 		if err != nil {
 			return err
 		}
-		fmt.Fprintf(dockerCli.Out(), "%s %s\n", s.Name, hash)
+		fmt.Fprintf(dockerCli.Out(), "%s %s\n", name, hash)
 	}
 	return nil
 }
diff --git a/cmd/compose/create.go b/cmd/compose/create.go
index 2e652cf47..a7731bae5 100644
--- a/cmd/compose/create.go
+++ b/cmd/compose/create.go
@@ -24,7 +24,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
@@ -164,7 +164,15 @@ func (opts createOptions) Apply(project *types.Project) error {
 		return err
 	}
 
-	for _, scale := range opts.scale {
+	err := applyScaleOpts(project, opts.scale)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func applyScaleOpts(project *types.Project, opts []string) error {
+	for _, scale := range opts {
 		split := strings.Split(scale, "=")
 		if len(split) != 2 {
 			return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale)
@@ -174,7 +182,7 @@ func (opts createOptions) Apply(project *types.Project) error {
 		if err != nil {
 			return err
 		}
-		err = setServiceScale(project, name, uint64(replicas))
+		err = setServiceScale(project, name, replicas)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go
index 0df79ec3b..12ebc7005 100644
--- a/cmd/compose/exec.go
+++ b/cmd/compose/exec.go
@@ -19,7 +19,7 @@ package compose
 import (
 	"context"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
diff --git a/cmd/compose/options.go b/cmd/compose/options.go
index 88135df20..948b66400 100644
--- a/cmd/compose/options.go
+++ b/cmd/compose/options.go
@@ -19,16 +19,13 @@ package compose
 import (
 	"fmt"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/utils"
 )
 
 func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
 	defaultPlatform := project.Environment["DOCKER_DEFAULT_PLATFORM"]
-	for i := range project.Services {
-		// mutable reference so platform fields can be updated
-		service := &project.Services[i]
-
+	for name, service := range project.Services {
 		if service.Build == nil {
 			continue
 		}
@@ -36,7 +33,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
 		// default platform only applies if the service doesn't specify
 		if defaultPlatform != "" && service.Platform == "" {
 			if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, defaultPlatform) {
-				return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", service.Name, defaultPlatform)
+				return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, defaultPlatform)
 			}
 			service.Platform = defaultPlatform
 		}
@@ -44,7 +41,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
 		if service.Platform != "" {
 			if len(service.Build.Platforms) > 0 {
 				if !utils.StringContains(service.Build.Platforms, service.Platform) {
-					return fmt.Errorf("service %q build configuration does not support platform: %s", service.Name, service.Platform)
+					return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
 				}
 			}
 
@@ -71,6 +68,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
 			// empty indicates that the builder gets to decide
 			service.Build.Platforms = nil
 		}
+		project.Services[name] = service
 	}
 	return nil
 }
diff --git a/cmd/compose/publish.go b/cmd/compose/publish.go
index 71f43ac03..2ef14aabf 100644
--- a/cmd/compose/publish.go
+++ b/cmd/compose/publish.go
@@ -28,6 +28,7 @@ import (
 type publishOptions struct {
 	*ProjectOptions
 	resolveImageDigests bool
+	ociVersion          string
 }
 
 func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
@@ -44,6 +45,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
 	}
 	flags := cmd.Flags()
 	flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
+	flags.StringVar(&opts.ociVersion, "oci-version", "", "OCI Image/Artifact specification version (automatically determined by default)")
 	return cmd
 }
 
@@ -55,5 +57,6 @@ func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service,
 
 	return backend.Publish(ctx, project, repository, api.PublishOptions{
 		ResolveImageDigests: opts.resolveImageDigests,
+		OCIVersion:          api.OCIVersion(opts.ociVersion),
 	})
 }
diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go
index 6eb6774d6..657f5bbba 100644
--- a/cmd/compose/pull.go
+++ b/cmd/compose/pull.go
@@ -21,7 +21,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/morikuni/aec"
 	"github.com/spf13/cobra"
diff --git a/cmd/compose/push.go b/cmd/compose/push.go
index 712b22cea..ddb5904c6 100644
--- a/cmd/compose/push.go
+++ b/cmd/compose/push.go
@@ -19,7 +19,7 @@ package compose
 import (
 	"context"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
diff --git a/cmd/compose/run.go b/cmd/compose/run.go
index 02b2dd744..de996778f 100644
--- a/cmd/compose/run.go
+++ b/cmd/compose/run.go
@@ -21,12 +21,12 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/compose-spec/compose-go/v2/format"
 	xprogress "github.com/moby/buildkit/util/progress/progressui"
 	"github.com/sirupsen/logrus"
 
-	cgo "github.com/compose-spec/compose-go/cli"
-	"github.com/compose-spec/compose-go/loader"
-	"github.com/compose-spec/compose-go/types"
+	cgo "github.com/compose-spec/compose-go/v2/cli"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/opts"
 	"github.com/mattn/go-shellwords"
@@ -98,16 +98,16 @@ func (options runOptions) apply(project *types.Project) error {
 	}
 
 	for _, v := range options.volumes {
-		volume, err := loader.ParseVolume(v)
+		volume, err := format.ParseVolume(v)
 		if err != nil {
 			return err
 		}
 		target.Volumes = append(target.Volumes, volume)
 	}
 
-	for i, s := range project.Services {
-		if s.Name == options.Service {
-			project.Services[i] = target
+	for name := range project.Services {
+		if name == options.Service {
+			project.Services[name] = target
 			break
 		}
 	}
@@ -279,10 +279,10 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 		QuietPull:         options.quietPull,
 	}
 
-	for i, service := range project.Services {
-		if service.Name == options.Service {
+	for name, service := range project.Services {
+		if name == options.Service {
 			service.StdinOpen = options.interactive
-			project.Services[i] = service
+			project.Services[name] = service
 		}
 	}
 
@@ -300,16 +300,16 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, requestedServiceName string, ignoreOrphans bool) error {
 	dependencies := types.Services{}
 	var requestedService types.ServiceConfig
-	for _, service := range project.Services {
-		if service.Name != requestedServiceName {
-			dependencies = append(dependencies, service)
+	for name, service := range project.Services {
+		if name != requestedServiceName {
+			dependencies[name] = service
 		} else {
 			requestedService = service
 		}
 	}
 
 	project.Services = dependencies
-	project.DisabledServices = append(project.DisabledServices, requestedService)
+	project.DisabledServices[requestedServiceName] = requestedService
 	err := backend.Create(ctx, &project, api.CreateOptions{
 		Build:         buildOpts,
 		IgnoreOrphans: ignoreOrphans,
diff --git a/cmd/compose/scale.go b/cmd/compose/scale.go
index 63ab1414a..78b23ae6f 100644
--- a/cmd/compose/scale.go
+++ b/cmd/compose/scale.go
@@ -24,7 +24,7 @@ import (
 
 	"github.com/docker/cli/cli/command"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"golang.org/x/exp/maps"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -73,18 +73,12 @@ func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, o
 	}
 
 	for key, value := range serviceReplicaTuples {
-		for i, service := range project.Services {
-			if service.Name != key {
-				continue
-			}
-			if service.Deploy == nil {
-				service.Deploy = &types.DeployConfig{}
-			}
-			scale := uint64(value)
-			service.Deploy.Replicas = &scale
-			project.Services[i] = service
-			break
+		service, err := project.GetService(key)
+		if err != nil {
+			return err
 		}
+		service.SetScale(value)
+		project.Services[key] = service
 	}
 
 	return backend.Scale(ctx, project, api.ScaleOptions{Services: services})
diff --git a/cmd/compose/up.go b/cmd/compose/up.go
index a86d1964c..f68c79403 100644
--- a/cmd/compose/up.go
+++ b/cmd/compose/up.go
@@ -25,7 +25,7 @@ import (
 
 	xprogress "github.com/moby/buildkit/util/progress/progressui"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/spf13/cobra"
@@ -259,22 +259,12 @@ func runUp(
 	})
 }
 
-func setServiceScale(project *types.Project, name string, replicas uint64) error {
-	for i, s := range project.Services {
-		if s.Name != name {
-			continue
-		}
-
-		service, err := project.GetService(name)
-		if err != nil {
-			return err
-		}
-		if service.Deploy == nil {
-			service.Deploy = &types.DeployConfig{}
-		}
-		service.Deploy.Replicas = &replicas
-		project.Services[i] = service
-		return nil
+func setServiceScale(project *types.Project, name string, replicas int) error {
+	service, err := project.GetService(name)
+	if err != nil {
+		return err
 	}
-	return fmt.Errorf("unknown service %q", name)
+	service.SetScale(replicas)
+	project.Services[name] = service
+	return nil
 }
diff --git a/cmd/compose/watch.go b/cmd/compose/watch.go
index 7311d9bca..24d704570 100644
--- a/cmd/compose/watch.go
+++ b/cmd/compose/watch.go
@@ -20,7 +20,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/internal/locker"
diff --git a/docs/reference/compose_alpha_publish.md b/docs/reference/compose_alpha_publish.md
index ba58d73c8..8424d7fbc 100644
--- a/docs/reference/compose_alpha_publish.md
+++ b/docs/reference/compose_alpha_publish.md
@@ -5,10 +5,11 @@ Publish compose application
 
 ### Options
 
-| Name                      | Type | Default | Description                     |
-|:--------------------------|:-----|:--------|:--------------------------------|
-| `--dry-run`               |      |         | Execute command in dry run mode |
-| `--resolve-image-digests` |      |         | Pin image tags to digests.      |
+| Name                      | Type     | Default | Description                                                                    |
+|:--------------------------|:---------|:--------|:-------------------------------------------------------------------------------|
+| `--dry-run`               |          |         | Execute command in dry run mode                                                |
+| `--oci-version`           | `string` |         | OCI Image/Artifact specification version (automatically determined by default) |
+| `--resolve-image-digests` |          |         | Pin image tags to digests.                                                     |
 
 
 <!---MARKER_GEN_END-->
diff --git a/docs/reference/docker_compose_alpha_publish.yaml b/docs/reference/docker_compose_alpha_publish.yaml
index 266e894c4..f2237f2ae 100644
--- a/docs/reference/docker_compose_alpha_publish.yaml
+++ b/docs/reference/docker_compose_alpha_publish.yaml
@@ -5,6 +5,16 @@ usage: docker compose alpha publish [OPTIONS] [REPOSITORY]
 pname: docker compose alpha
 plink: docker_compose_alpha.yaml
 options:
+    - option: oci-version
+      value_type: string
+      description: |
+        OCI Image/Artifact specification version (automatically determined by default)
+      deprecated: false
+      hidden: false
+      experimental: false
+      experimentalcli: false
+      kubernetes: false
+      swarm: false
     - option: resolve-image-digests
       value_type: bool
       default_value: "false"
diff --git a/go.mod b/go.mod
index 2a65d2051..b6c7ba0e3 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/AlecAivazis/survey/v2 v2.3.7
 	github.com/Microsoft/go-winio v0.6.1
 	github.com/buger/goterm v1.0.4
-	github.com/compose-spec/compose-go v1.20.2
+	github.com/compose-spec/compose-go/v2 v2.0.0-beta.1
 	github.com/containerd/console v1.0.3
 	github.com/containerd/containerd v1.7.7
 	github.com/davecgh/go-spew v1.1.1
diff --git a/go.sum b/go.sum
index 9c5aff180..94b2e9197 100644
--- a/go.sum
+++ b/go.sum
@@ -132,8 +132,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g
 github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
 github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
-github.com/compose-spec/compose-go v1.20.2 h1:u/yfZHn4EaHGdidrZycWpxXgFffjYULlTbRfJ51ykjQ=
-github.com/compose-spec/compose-go v1.20.2/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM=
+github.com/compose-spec/compose-go/v2 v2.0.0-beta.1 h1:/A+2QMQVSsAmr9Gn5fm6YwaufjRZmWBnHYjr0oCyGiw=
+github.com/compose-spec/compose-go/v2 v2.0.0-beta.1/go.mod h1:PWCgeD8cxiI/DmdpBM407CuLDrZ2W4xuS6/Z9jAi0YQ=
 github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
 github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
 github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
diff --git a/internal/ocipush/push.go b/internal/ocipush/push.go
new file mode 100644
index 000000000..8f632a878
--- /dev/null
+++ b/internal/ocipush/push.go
@@ -0,0 +1,212 @@
+/*
+   Copyright 2023 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 ocipush
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"path/filepath"
+	"time"
+
+	pusherrors "github.com/containerd/containerd/remotes/errors"
+	"github.com/distribution/reference"
+	"github.com/docker/buildx/util/imagetools"
+	"github.com/docker/compose/v2/pkg/api"
+	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
+	v1 "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+const (
+	// ComposeProjectArtifactType is the OCI 1.1-compliant artifact type value
+	// for the generated image manifest.
+	ComposeProjectArtifactType = "application/vnd.docker.compose.project"
+	// ComposeYAMLMediaType is the media type for each layer (Compose file)
+	// in the image manifest.
+	ComposeYAMLMediaType = "application/vnd.docker.compose.file+yaml"
+	// ComposeEmptyConfigMediaType is a media type used for the config descriptor
+	// when doing OCI 1.0-style pushes.
+	//
+	// The content is always `{}`, the same as a normal empty descriptor, but
+	// the specific media type allows clients to fall back to the config media
+	// type to recognize the manifest as a Compose project since the artifact
+	// type field is not available in OCI 1.0.
+	//
+	// This is based on guidance from the OCI 1.1 spec:
+	//	> Implementers note: artifacts have historically been created without
+	// 	> an artifactType field, and tooling to work with artifacts should
+	//	> fallback to the config.mediaType value.
+	ComposeEmptyConfigMediaType = "application/vnd.docker.compose.config.empty.v1+json"
+)
+
+// clientAuthStatusCodes are client (4xx) errors that are authentication
+// related.
+var clientAuthStatusCodes = []int{
+	http.StatusUnauthorized,
+	http.StatusForbidden,
+	http.StatusProxyAuthRequired,
+}
+
+type Pushable struct {
+	Descriptor v1.Descriptor
+	Data       []byte
+}
+
+func DescriptorForComposeFile(path string, content []byte) v1.Descriptor {
+	return v1.Descriptor{
+		MediaType: ComposeYAMLMediaType,
+		Digest:    digest.FromString(string(content)),
+		Size:      int64(len(content)),
+		Annotations: map[string]string{
+			"com.docker.compose.version": api.ComposeVersion,
+			"com.docker.compose.file":    filepath.Base(path),
+		},
+	}
+}
+
+func PushManifest(
+	ctx context.Context,
+	resolver *imagetools.Resolver,
+	named reference.Named,
+	layers []Pushable,
+	ociVersion api.OCIVersion,
+) error {
+	// prepare to push the manifest by pushing the layers
+	layerDescriptors := make([]v1.Descriptor, len(layers))
+	for i := range layers {
+		layerDescriptors[i] = layers[i].Descriptor
+		if err := resolver.Push(ctx, named, layers[i].Descriptor, layers[i].Data); err != nil {
+			return err
+		}
+	}
+
+	if ociVersion != "" {
+		// if a version was explicitly specified, use it
+		return createAndPushManifest(ctx, resolver, named, layerDescriptors, ociVersion)
+	}
+
+	// try to push in the OCI 1.1 format but fallback to OCI 1.0 on 4xx errors
+	// (other than auth) since it's most likely the result of the registry not
+	// having support
+	err := createAndPushManifest(ctx, resolver, named, layerDescriptors, api.OCIVersion1_1)
+	var pushErr pusherrors.ErrUnexpectedStatus
+	if errors.As(err, &pushErr) && isNonAuthClientError(pushErr.StatusCode) {
+		// TODO(milas): show a warning here (won't work with logrus)
+		return createAndPushManifest(ctx, resolver, named, layerDescriptors, api.OCIVersion1_0)
+	}
+	return err
+}
+
+func createAndPushManifest(
+	ctx context.Context,
+	resolver *imagetools.Resolver,
+	named reference.Named,
+	layers []v1.Descriptor,
+	ociVersion api.OCIVersion,
+) error {
+	toPush, err := generateManifest(layers, ociVersion)
+	if err != nil {
+		return err
+	}
+	for _, p := range toPush {
+		err = resolver.Push(ctx, named, p.Descriptor, p.Data)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func isNonAuthClientError(statusCode int) bool {
+	if statusCode < 400 || statusCode >= 500 {
+		// not a client error
+		return false
+	}
+	for _, v := range clientAuthStatusCodes {
+		if statusCode == v {
+			// client auth error
+			return false
+		}
+	}
+	// any other 4xx client error
+	return true
+}
+
+func generateManifest(layers []v1.Descriptor, ociCompat api.OCIVersion) ([]Pushable, error) {
+	var toPush []Pushable
+	var config v1.Descriptor
+	var artifactType string
+	switch ociCompat {
+	case api.OCIVersion1_0:
+		// "Content other than OCI container images MAY be packaged using the image manifest.
+		// When this is done, the config.mediaType value MUST be set to a value specific to
+		// the artifact type or the empty value."
+		// Source: https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidelines-for-artifact-usage
+		//
+		// The `ComposeEmptyConfigMediaType` is used specifically for this purpose:
+		// there is no config, and an empty descriptor is used for OCI 1.1 in
+		// conjunction with the `ArtifactType`, but for OCI 1.0 compatibility,
+		// tooling falls back to the config media type, so this is used to
+		// indicate that it's not a container image but custom content.
+		configData := []byte("{}")
+		config = v1.Descriptor{
+			MediaType: ComposeEmptyConfigMediaType,
+			Digest:    digest.FromBytes(configData),
+			Size:      int64(len(configData)),
+		}
+		// N.B. OCI 1.0 does NOT support specifying the artifact type, so it's
+		//		left as an empty string to omit it from the marshaled JSON
+		artifactType = ""
+		toPush = append(toPush, Pushable{Descriptor: config, Data: configData})
+	case api.OCIVersion1_1:
+		config = v1.DescriptorEmptyJSON
+		artifactType = ComposeProjectArtifactType
+		// N.B. the descriptor has the data embedded in it
+		toPush = append(toPush, Pushable{Descriptor: config, Data: nil})
+	default:
+		return nil, fmt.Errorf("unsupported OCI version: %s", ociCompat)
+	}
+
+	manifest, err := json.Marshal(v1.Manifest{
+		Versioned:    specs.Versioned{SchemaVersion: 2},
+		MediaType:    v1.MediaTypeImageManifest,
+		ArtifactType: artifactType,
+		Config:       config,
+		Layers:       layers,
+		Annotations: map[string]string{
+			"org.opencontainers.image.created": time.Now().Format(time.RFC3339),
+		},
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	manifestDescriptor := v1.Descriptor{
+		MediaType: v1.MediaTypeImageManifest,
+		Digest:    digest.FromString(string(manifest)),
+		Size:      int64(len(manifest)),
+		Annotations: map[string]string{
+			"com.docker.compose.version": api.ComposeVersion,
+		},
+		ArtifactType: artifactType,
+	}
+	toPush = append(toPush, Pushable{Descriptor: manifestDescriptor, Data: manifest})
+	return toPush, nil
+}
diff --git a/internal/sync/docker_cp.go b/internal/sync/docker_cp.go
index ae5a77ad6..47077b404 100644
--- a/internal/sync/docker_cp.go
+++ b/internal/sync/docker_cp.go
@@ -22,7 +22,7 @@ import (
 	"io/fs"
 	"os"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/sirupsen/logrus"
 )
@@ -62,10 +62,7 @@ func (d *DockerCopy) Sync(ctx context.Context, service types.ServiceConfig, path
 }
 
 func (d *DockerCopy) sync(ctx context.Context, service types.ServiceConfig, pathMapping PathMapping) error {
-	scale := 1
-	if service.Deploy != nil && service.Deploy.Replicas != nil {
-		scale = int(*service.Deploy.Replicas)
-	}
+	scale := service.GetScale()
 
 	if fi, statErr := os.Stat(pathMapping.HostPath); statErr == nil {
 		if fi.IsDir() {
diff --git a/internal/sync/shared.go b/internal/sync/shared.go
index 2ff9b434c..6e2b117cb 100644
--- a/internal/sync/shared.go
+++ b/internal/sync/shared.go
@@ -17,7 +17,7 @@ package sync
 import (
 	"context"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 )
 
 // PathMapping contains the Compose service and modified host system path.
diff --git a/internal/sync/tar.go b/internal/sync/tar.go
index bb6a0acf0..ae7d8517e 100644
--- a/internal/sync/tar.go
+++ b/internal/sync/tar.go
@@ -32,7 +32,7 @@ import (
 
 	"github.com/hashicorp/go-multierror"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/pkg/archive"
 )
diff --git a/internal/tracing/attributes.go b/internal/tracing/attributes.go
index f0ee29f89..1fd2dd7f9 100644
--- a/internal/tracing/attributes.go
+++ b/internal/tracing/attributes.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/docker/compose/v2/pkg/utils"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	moby "github.com/docker/docker/api/types"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/trace"
@@ -58,17 +58,12 @@ func ProjectOptions(proj *types.Project) SpanOptions {
 		return nil
 	}
 
-	disabledServiceNames := make([]string, len(proj.DisabledServices))
-	for i := range proj.DisabledServices {
-		disabledServiceNames[i] = proj.DisabledServices[i].Name
-	}
-
 	attrs := []attribute.KeyValue{
 		attribute.String("project.name", proj.Name),
 		attribute.String("project.dir", proj.WorkingDir),
 		attribute.StringSlice("project.compose_files", proj.ComposeFiles),
 		attribute.StringSlice("project.services.active", proj.ServiceNames()),
-		attribute.StringSlice("project.services.disabled", disabledServiceNames),
+		attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()),
 		attribute.StringSlice("project.profiles", proj.Profiles),
 		attribute.StringSlice("project.volumes", proj.VolumeNames()),
 		attribute.StringSlice("project.networks", proj.NetworkNames()),
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 926462efe..83176381a 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -22,7 +22,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/utils"
 )
 
@@ -146,9 +146,9 @@ type BuildOptions struct {
 // Apply mutates project according to build options
 func (o BuildOptions) Apply(project *types.Project) error {
 	platform := project.Environment["DOCKER_DEFAULT_PLATFORM"]
-	for i, service := range project.Services {
+	for name, service := range project.Services {
 		if service.Image == "" && service.Build == nil {
-			return fmt.Errorf("invalid service %q. Must specify either image or build", service.Name)
+			return fmt.Errorf("invalid service %q. Must specify either image or build", name)
 		}
 
 		if service.Build == nil {
@@ -156,20 +156,20 @@ func (o BuildOptions) Apply(project *types.Project) error {
 		}
 		if platform != "" {
 			if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, platform) {
-				return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", service.Name, platform)
+				return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, platform)
 			}
 			service.Platform = platform
 		}
 		if service.Platform != "" {
 			if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, service.Platform) {
-				return fmt.Errorf("service %q build configuration does not support platform: %s", service.Name, service.Platform)
+				return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
 			}
 		}
 
 		service.Build.Pull = service.Build.Pull || o.Pull
 		service.Build.NoCache = service.Build.NoCache || o.NoCache
 
-		project.Services[i] = service
+		project.Services[name] = service
 	}
 	return nil
 }
@@ -361,9 +361,28 @@ type PortOptions struct {
 	Index    int
 }
 
+// OCIVersion controls manifest generation to ensure compatibility
+// with different registries.
+//
+// Currently, this is not exposed as an option to the user – Compose uses
+// OCI 1.0 mode automatically for ECR registries based on domain and OCI 1.1
+// for all other registries.
+//
+// There are likely other popular registries that do not support the OCI 1.1
+// format, so it might make sense to expose this as a CLI flag or see if
+// there's a way to generically probe the registry for support level.
+type OCIVersion string
+
+const (
+	OCIVersion1_0 OCIVersion = "1.0"
+	OCIVersion1_1 OCIVersion = "1.1"
+)
+
 // PublishOptions group options of the Publish API
 type PublishOptions struct {
 	ResolveImageDigests bool
+
+	OCIVersion OCIVersion
 }
 
 func (e Event) String() string {
diff --git a/pkg/api/proxy.go b/pkg/api/proxy.go
index b08059a32..446ce8dc4 100644
--- a/pkg/api/proxy.go
+++ b/pkg/api/proxy.go
@@ -19,7 +19,7 @@ package api
 import (
 	"context"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 )
 
 var _ Service = &ServiceProxy{}
diff --git a/pkg/compose/attach.go b/pkg/compose/attach.go
index f920b4ea5..499f002fd 100644
--- a/pkg/compose/attach.go
+++ b/pkg/compose/attach.go
@@ -23,7 +23,7 @@ import (
 	"io"
 	"strings"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/streams"
 	moby "github.com/docker/docker/api/types"
 	containerType "github.com/docker/docker/api/types/container"
diff --git a/pkg/compose/build.go b/pkg/compose/build.go
index 3b8ae0505..d24a8fcbf 100644
--- a/pkg/compose/build.go
+++ b/pkg/compose/build.go
@@ -26,7 +26,7 @@ import (
 
 	"github.com/moby/buildkit/util/progress/progressui"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/containerd/containerd/platforms"
 	"github.com/docker/buildx/build"
 	"github.com/docker/buildx/builder"
@@ -67,7 +67,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
 }
 
 type serviceToBuild struct {
-	idx     int
+	name    string
 	service types.ServiceConfig
 }
 
@@ -85,7 +85,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 		if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
 			return nil
 		}
-		service, idx := getServiceIndex(project, name)
+		service := project.Services[name]
 
 		if service.Build == nil {
 			return nil
@@ -97,7 +97,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 			return nil
 		}
 		mapServiceMutx.Lock()
-		serviceToBeBuild[name] = serviceToBuild{idx: idx, service: service}
+		serviceToBeBuild[name] = serviceToBuild{name: name, service: service}
 		mapServiceMutx.Unlock()
 		return nil
 	}, func(traversal *graphTraversal) {
@@ -146,7 +146,17 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 		}
 	}
 
+	// we use a pre-allocated []string to collect build digest by service index while running concurrent goroutines
 	builtDigests := make([]string, len(project.Services))
+	names := project.ServiceNames()
+	getServiceIndex := func(name string) int {
+		for idx, n := range names {
+			if n == name {
+				return idx
+			}
+		}
+		return -1
+	}
 	err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
 		if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
 			return nil
@@ -156,14 +166,13 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 			return nil
 		}
 		service := serviceToBuild.service
-		idx := serviceToBuild.idx
 
 		if !buildkitEnabled {
 			id, err := s.doBuildClassic(ctx, project, service, options)
 			if err != nil {
 				return err
 			}
-			builtDigests[idx] = id
+			builtDigests[getServiceIndex(name)] = id
 
 			if options.Push {
 				return s.push(ctx, project, api.PushOptions{})
@@ -180,11 +189,11 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 			return err
 		}
 
-		digest, err := s.doBuildBuildkit(ctx, service.Name, buildOptions, w, nodes)
+		digest, err := s.doBuildBuildkit(ctx, name, buildOptions, w, nodes)
 		if err != nil {
 			return err
 		}
-		builtDigests[idx] = digest
+		builtDigests[getServiceIndex(name)] = digest
 
 		return nil
 	}, func(traversal *graphTraversal) {
@@ -204,29 +213,17 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 
 	for i, imageDigest := range builtDigests {
 		if imageDigest != "" {
-			imageRef := api.GetImageNameOrDefault(project.Services[i], project.Name)
+			imageRef := api.GetImageNameOrDefault(project.Services[names[i]], project.Name)
 			imageIDs[imageRef] = imageDigest
 		}
 	}
 	return imageIDs, err
 }
 
-func getServiceIndex(project *types.Project, name string) (types.ServiceConfig, int) {
-	var service types.ServiceConfig
-	var idx int
-	for i, s := range project.Services {
-		if s.Name == name {
-			idx, service = i, s
-			break
-		}
-	}
-	return service, idx
-}
-
 func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, buildOpts *api.BuildOptions, quietPull bool) error {
-	for _, service := range project.Services {
+	for name, service := range project.Services {
 		if service.Image == "" && service.Build == nil {
-			return fmt.Errorf("invalid service %q. Must specify either image or build", service.Name)
+			return fmt.Errorf("invalid service %q. Must specify either image or build", name)
 		}
 	}
 
@@ -264,15 +261,16 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
 	}
 
 	// set digest as com.docker.compose.image label so we can detect outdated containers
-	for i, service := range project.Services {
+	for name, service := range project.Services {
 		image := api.GetImageNameOrDefault(service, project.Name)
 		digest, ok := images[image]
 		if ok {
-			if project.Services[i].Labels == nil {
-				project.Services[i].Labels = types.Labels{}
+			if service.Labels == nil {
+				service.Labels = types.Labels{}
 			}
-			project.Services[i].CustomLabels.Add(api.ImageDigestLabel, digest)
+			service.CustomLabels.Add(api.ImageDigestLabel, digest)
 		}
+		project.Services[name] = service
 	}
 	return nil
 }
@@ -440,7 +438,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
 		Platforms:   plats,
 		Labels:      imageLabels,
 		NetworkMode: service.Build.Network,
-		ExtraHosts:  service.Build.ExtraHosts.AsList(),
+		ExtraHosts:  service.Build.ExtraHosts.AsList(":"),
 		Ulimits:     toUlimitOpt(service.Build.Ulimits),
 		Session:     sessionConfig,
 		Allow:       allow,
diff --git a/pkg/compose/build_classic.go b/pkg/compose/build_classic.go
index e49a15cc2..150383e65 100644
--- a/pkg/compose/build_classic.go
+++ b/pkg/compose/build_classic.go
@@ -31,7 +31,7 @@ import (
 
 	"github.com/docker/docker/api/types/registry"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli"
 	"github.com/docker/cli/cli/command/image/build"
 	dockertypes "github.com/docker/docker/api/types"
@@ -229,7 +229,7 @@ func imageBuildOptions(dockerCli command.Cli, project *types.Project, service ty
 		BuildArgs:   resolveAndMergeBuildArgs(dockerCli, project, service, options),
 		Labels:      config.Labels,
 		NetworkMode: config.Network,
-		ExtraHosts:  config.ExtraHosts.AsList(),
+		ExtraHosts:  config.ExtraHosts.AsList(":"),
 		Target:      config.Target,
 		Isolation:   container.Isolation(config.Isolation),
 	}
diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go
index d0fc1988c..18dc65a1c 100644
--- a/pkg/compose/compose.go
+++ b/pkg/compose/compose.go
@@ -29,7 +29,7 @@ import (
 
 	"github.com/docker/docker/api/types/volume"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/distribution/reference"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/config/configfile"
@@ -177,25 +177,28 @@ func (s *composeService) Config(ctx context.Context, project *types.Project, opt
 // projectFromName builds a types.Project based on actual resources with compose labels set
 func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
 	project := &types.Project{
-		Name: projectName,
+		Name:     projectName,
+		Services: types.Services{},
 	}
 	if len(containers) == 0 {
 		return project, fmt.Errorf("no container found for project %q: %w", projectName, api.ErrNotFound)
 	}
-	set := map[string]*types.ServiceConfig{}
+	set := types.Services{}
 	for _, c := range containers {
 		serviceLabel := c.Labels[api.ServiceLabel]
-		_, ok := set[serviceLabel]
+		service, ok := set[serviceLabel]
 		if !ok {
-			set[serviceLabel] = &types.ServiceConfig{
+			service = types.ServiceConfig{
 				Name:   serviceLabel,
 				Image:  c.Image,
 				Labels: c.Labels,
 			}
+
 		}
-		set[serviceLabel].Scale++
+		service.Scale = increment(service.Scale)
+		set[serviceLabel] = service
 	}
-	for _, service := range set {
+	for name, service := range set {
 		dependencies := service.Labels[api.DependenciesLabel]
 		if len(dependencies) > 0 {
 			service.DependsOn = types.DependsOnConfig{}
@@ -216,9 +219,11 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
 				}
 				service.DependsOn[dependency] = types.ServiceDependency{Condition: condition, Restart: restart, Required: required}
 			}
+			set[name] = service
 		}
-		project.Services = append(project.Services, *service)
 	}
+	project.Services = set
+
 SERVICES:
 	for _, qs := range services {
 		for _, es := range project.Services {
@@ -236,6 +241,14 @@ SERVICES:
 	return project, nil
 }
 
+func increment(scale *int) *int {
+	i := 1
+	if scale != nil {
+		i = *scale + 1
+	}
+	return &i
+}
+
 func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
 	opts := volume.ListOptions{
 		Filters: filters.NewArgs(projectFilter(projectName)),
diff --git a/pkg/compose/containers.go b/pkg/compose/containers.go
index 075b831c2..ce1722892 100644
--- a/pkg/compose/containers.go
+++ b/pkg/compose/containers.go
@@ -22,7 +22,7 @@ import (
 	"sort"
 	"strconv"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/utils"
 	moby "github.com/docker/docker/api/types"
diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go
index 45a9257ed..4ba31ff09 100644
--- a/pkg/compose/convergence.go
+++ b/pkg/compose/convergence.go
@@ -29,7 +29,7 @@ import (
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/trace"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/containerd/containerd/platforms"
 	"github.com/docker/compose/v2/internal/tracing"
 	moby "github.com/docker/docker/api/types"
@@ -205,6 +205,16 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
 	return err
 }
 
+func getScale(config types.ServiceConfig) (int, error) {
+	scale := config.GetScale()
+	if scale > 1 && config.ContainerName != "" {
+		return 0, fmt.Errorf(doubledContainerNameWarning,
+			config.Name,
+			config.ContainerName)
+	}
+	return scale, nil
+}
+
 // resolveServiceReferences replaces reference to another service with reference to an actual container
 func (c *convergence) resolveServiceReferences(service *types.ServiceConfig) error {
 	err := c.resolveVolumeFrom(service)
@@ -428,7 +438,7 @@ func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceD
 			}
 		}
 		return false, err
-	} else if service.Scale == 0 {
+	} else if service.GetScale() == 0 {
 		// don't wait for the dependency which configured to have 0 containers running
 		return false, nil
 	}
@@ -455,19 +465,6 @@ func nextContainerNumber(containers []moby.Container) int {
 
 }
 
-func getScale(config types.ServiceConfig) (int, error) {
-	scale := 1
-	if config.Deploy != nil && config.Deploy.Replicas != nil {
-		scale = int(*config.Deploy.Replicas)
-	}
-	if scale > 1 && config.ContainerName != "" {
-		return 0, fmt.Errorf(doubledContainerNameWarning,
-			config.Name,
-			config.ContainerName)
-	}
-	return scale, nil
-}
-
 func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
 	name string, number int, opts createOptions) (container moby.Container, err error) {
 	w := progress.ContextWriter(ctx)
@@ -751,7 +748,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
 	}
 
 	if len(containers) == 0 {
-		if scale, err := getScale(service); err != nil && scale == 0 {
+		if service.GetScale() == 0 {
 			return nil
 		}
 		return fmt.Errorf("service %q has no container to start", service.Name)
diff --git a/pkg/compose/convert.go b/pkg/compose/convert.go
index 6f092ee2d..4ce4c6acf 100644
--- a/pkg/compose/convert.go
+++ b/pkg/compose/convert.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"time"
 
-	compose "github.com/compose-spec/compose-go/types"
+	compose "github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/docker/api/types/container"
 )
 
diff --git a/pkg/compose/create.go b/pkg/compose/create.go
index c0a36a8a0..ddeff661d 100644
--- a/pkg/compose/create.go
+++ b/pkg/compose/create.go
@@ -41,7 +41,7 @@ import (
 	"github.com/docker/go-units"
 	"github.com/sirupsen/logrus"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/progress"
@@ -94,11 +94,7 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
 		return err
 	}
 
-	allServices := project.AllServices()
-	allServiceNames := []string{}
-	for _, service := range allServices {
-		allServiceNames = append(allServiceNames, service.Name)
-	}
+	allServiceNames := append(project.ServiceNames(), project.DisabledServiceNames()...)
 	orphans := observedState.filter(isNotService(allServiceNames...))
 	if len(orphans) > 0 && !options.IgnoreOrphans {
 		if options.RemoveOrphans {
@@ -263,7 +259,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
 		DNS:            service.DNS,
 		DNSSearch:      service.DNSSearch,
 		DNSOptions:     service.DNSOpts,
-		ExtraHosts:     service.ExtraHosts.AsList(),
+		ExtraHosts:     service.ExtraHosts.AsList(":"),
 		SecurityOpt:    securityOpts,
 		UsernsMode:     container.UsernsMode(service.UserNSMode),
 		UTSMode:        container.UTSMode(service.Uts),
@@ -810,7 +806,7 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
 		}
 
 		definedConfig := p.Configs[config.Source]
-		if definedConfig.External.External {
+		if definedConfig.External {
 			return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
 		}
 
@@ -860,7 +856,7 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
 		}
 
 		definedSecret := p.Secrets[secret.Source]
-		if definedSecret.External.External {
+		if definedSecret.External {
 			return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
 		}
 
@@ -1019,7 +1015,7 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
 }
 
 func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
-	if n.External.External {
+	if n.External {
 		return s.resolveExternalNetwork(ctx, n)
 	}
 
@@ -1206,14 +1202,14 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
 		if !errdefs.IsNotFound(err) {
 			return err
 		}
-		if volume.External.External {
+		if volume.External {
 			return fmt.Errorf("external volume %q not found", volume.Name)
 		}
 		err := s.createVolume(ctx, volume)
 		return err
 	}
 
-	if volume.External.External {
+	if volume.External {
 		return nil
 	}
 
diff --git a/pkg/compose/dependencies.go b/pkg/compose/dependencies.go
index 94b8d2d21..4a3fa4bd2 100644
--- a/pkg/compose/dependencies.go
+++ b/pkg/compose/dependencies.go
@@ -22,7 +22,7 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"golang.org/x/sync/errgroup"
 
diff --git a/pkg/compose/down.go b/pkg/compose/down.go
index b4a88eb64..009dcf8f5 100644
--- a/pkg/compose/down.go
+++ b/pkg/compose/down.go
@@ -24,7 +24,7 @@ import (
 
 	"github.com/docker/compose/v2/pkg/utils"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	moby "github.com/docker/docker/api/types"
 	containerType "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
@@ -136,7 +136,7 @@ func checkSelectedServices(options api.DownOptions, project *types.Project) ([]s
 func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
 	var ops []downOp
 	for _, vol := range project.Volumes {
-		if vol.External.External {
+		if vol.External {
 			continue
 		}
 		volumeName := vol.Name
@@ -171,7 +171,7 @@ func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Pr
 func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
 	var ops []downOp
 	for key, n := range project.Networks {
-		if n.External.External {
+		if n.External {
 			continue
 		}
 		// loop capture variable for op closure
diff --git a/pkg/compose/errors.go b/pkg/compose/errors.go
index 0dd06c6db..58eea0147 100644
--- a/pkg/compose/errors.go
+++ b/pkg/compose/errors.go
@@ -20,7 +20,7 @@ import (
 	"errors"
 	"io/fs"
 
-	"github.com/compose-spec/compose-go/errdefs"
+	"github.com/compose-spec/compose-go/v2/errdefs"
 )
 
 // Error error to categorize failures and extract metrics info
diff --git a/pkg/compose/hash.go b/pkg/compose/hash.go
index c4a4c91a4..284ccaa76 100644
--- a/pkg/compose/hash.go
+++ b/pkg/compose/hash.go
@@ -19,7 +19,7 @@ package compose
 import (
 	"encoding/json"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/opencontainers/go-digest"
 )
 
@@ -28,12 +28,11 @@ func ServiceHash(o types.ServiceConfig) (string, error) {
 	// remove the Build config when generating the service hash
 	o.Build = nil
 	o.PullPolicy = ""
-	if o.Deploy == nil {
-		o.Deploy = &types.DeployConfig{}
+	o.Scale = nil
+	if o.Deploy != nil {
+		o.Deploy.Replicas = nil
 	}
-	o.Scale = 1
-	var one uint64 = 1
-	o.Deploy.Replicas = &one
+
 	bytes, err := json.Marshal(o)
 	if err != nil {
 		return "", err
diff --git a/pkg/compose/image_pruner.go b/pkg/compose/image_pruner.go
index 26c1bcb10..e29406795 100644
--- a/pkg/compose/image_pruner.go
+++ b/pkg/compose/image_pruner.go
@@ -22,7 +22,7 @@ import (
 	"sort"
 	"sync"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/distribution/reference"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go
index 02fd62604..1608ff42a 100644
--- a/pkg/compose/publish.go
+++ b/pkg/compose/publish.go
@@ -18,19 +18,15 @@ package compose
 
 import (
 	"context"
-	"encoding/json"
 	"os"
-	"path/filepath"
-	"time"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/distribution/reference"
 	"github.com/docker/buildx/util/imagetools"
+	"github.com/docker/compose/v2/internal/ocipush"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/opencontainers/go-digest"
-	"github.com/opencontainers/image-spec/specs-go"
-	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error {
@@ -45,8 +41,6 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
 		return err
 	}
 
-	w := progress.ContextWriter(ctx)
-
 	named, err := reference.ParseDockerRef(repository)
 	if err != nil {
 		return err
@@ -56,18 +50,18 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
 		Auth: s.configFile(),
 	})
 
-	var layers []v1.Descriptor
+	var layers []ocipush.Pushable
 	for _, file := range project.ComposeFiles {
 		f, err := os.ReadFile(file)
 		if err != nil {
 			return err
 		}
 
-		layer, err := s.pushComposeFile(ctx, file, f, resolver, named)
-		if err != nil {
-			return err
-		}
-		layers = append(layers, layer)
+		layerDescriptor := ocipush.DescriptorForComposeFile(file, f)
+		layers = append(layers, ocipush.Pushable{
+			Descriptor: layerDescriptor,
+			Data:       f,
+		})
 	}
 
 	if options.ResolveImageDigests {
@@ -76,58 +70,25 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
 			return err
 		}
 
-		layer, err := s.pushComposeFile(ctx, "image-digests.yaml", yaml, resolver, named)
-		if err != nil {
-			return err
-		}
-		layers = append(layers, layer)
-	}
-
-	emptyConfig, err := json.Marshal(v1.ImageConfig{})
-	if err != nil {
-		return err
-	}
-	configDescriptor := v1.Descriptor{
-		MediaType: "application/vnd.oci.empty.v1+json",
-		Digest:    digest.FromBytes(emptyConfig),
-		Size:      int64(len(emptyConfig)),
-	}
-	var imageManifest []byte
-	if !s.dryRun {
-		err = resolver.Push(ctx, named, configDescriptor, emptyConfig)
-		if err != nil {
-			return err
-		}
-		imageManifest, err = json.Marshal(v1.Manifest{
-			Versioned:    specs.Versioned{SchemaVersion: 2},
-			MediaType:    v1.MediaTypeImageManifest,
-			ArtifactType: "application/vnd.docker.compose.project",
-			Config:       configDescriptor,
-			Layers:       layers,
-			Annotations: map[string]string{
-				"org.opencontainers.image.created": time.Now().Format(time.RFC3339),
-			},
+		layerDescriptor := ocipush.DescriptorForComposeFile("image-digests.yaml", yaml)
+		layers = append(layers, ocipush.Pushable{
+			Descriptor: layerDescriptor,
+			Data:       yaml,
 		})
-		if err != nil {
-			return err
-		}
 	}
 
+	w := progress.ContextWriter(ctx)
 	w.Event(progress.Event{
 		ID:     repository,
 		Text:   "publishing",
 		Status: progress.Working,
 	})
 	if !s.dryRun {
-		err = resolver.Push(ctx, named, v1.Descriptor{
-			MediaType: v1.MediaTypeImageManifest,
-			Digest:    digest.FromString(string(imageManifest)),
-			Size:      int64(len(imageManifest)),
-			Annotations: map[string]string{
-				"com.docker.compose.version": api.ComposeVersion,
-			},
-			ArtifactType: "application/vnd.docker.compose.project",
-		}, imageManifest)
+		err = ocipush.PushManifest(ctx, resolver, named, layers, options.OCIVersion)
+		if err != nil {
+			return err
+		}
+
 		if err != nil {
 			w.Event(progress.Event{
 				ID:     repository,
@@ -161,44 +122,13 @@ func (s *composeService) generateImageDigestsOverride(ctx context.Context, proje
 	if err != nil {
 		return nil, err
 	}
-	override := types.Project{}
-	for _, service := range project.Services {
-		override.Services = append(override.Services, types.ServiceConfig{
-			Name:  service.Name,
+	override := types.Project{
+		Services: types.Services{},
+	}
+	for name, service := range project.Services {
+		override.Services[name] = types.ServiceConfig{
 			Image: service.Image,
-		})
+		}
 	}
 	return override.MarshalYAML()
 }
-
-func (s *composeService) pushComposeFile(ctx context.Context, file string, content []byte, resolver *imagetools.Resolver, named reference.Named) (v1.Descriptor, error) {
-	w := progress.ContextWriter(ctx)
-	w.Event(progress.Event{
-		ID:     file,
-		Text:   "publishing",
-		Status: progress.Working,
-	})
-	layer := v1.Descriptor{
-		MediaType: "application/vnd.docker.compose.file+yaml",
-		Digest:    digest.FromString(string(content)),
-		Size:      int64(len(content)),
-		Annotations: map[string]string{
-			"com.docker.compose.version": api.ComposeVersion,
-			"com.docker.compose.file":    filepath.Base(file),
-		},
-	}
-	err := resolver.Push(ctx, named, layer, content)
-	w.Event(progress.Event{
-		ID:     file,
-		Text:   "published",
-		Status: statusFor(err),
-	})
-	return layer, err
-}
-
-func statusFor(err error) progress.EventStatus {
-	if err != nil {
-		return progress.Error
-	}
-	return progress.Done
-}
diff --git a/pkg/compose/pull.go b/pkg/compose/pull.go
index 75fd3375b..977397bd0 100644
--- a/pkg/compose/pull.go
+++ b/pkg/compose/pull.go
@@ -25,7 +25,7 @@ import (
 	"io"
 	"strings"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/distribution/reference"
 	"github.com/docker/buildx/driver"
 	moby "github.com/docker/docker/api/types"
@@ -63,11 +63,11 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 		imagesBeingPulled = map[string]string{}
 	)
 
-	for i, service := range project.Services {
-		i, service := i, service
+	i := 0
+	for name, service := range project.Services {
 		if service.Image == "" {
 			w.Event(progress.Event{
-				ID:     service.Name,
+				ID:     name,
 				Status: progress.Done,
 				Text:   "Skipped - No image to be pulled",
 			})
@@ -77,7 +77,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 		switch service.PullPolicy {
 		case types.PullPolicyNever, types.PullPolicyBuild:
 			w.Event(progress.Event{
-				ID:     service.Name,
+				ID:     name,
 				Status: progress.Done,
 				Text:   "Skipped",
 			})
@@ -85,7 +85,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 		case types.PullPolicyMissing, types.PullPolicyIfNotPresent:
 			if imageAlreadyPresent(service.Image, images) {
 				w.Event(progress.Event{
-					ID:     service.Name,
+					ID:     name,
 					Status: progress.Done,
 					Text:   "Skipped - Image is already present locally",
 				})
@@ -95,7 +95,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 
 		if service.Build != nil && opts.IgnoreBuildable {
 			w.Event(progress.Event{
-				ID:     service.Name,
+				ID:     name,
 				Status: progress.Done,
 				Text:   "Skipped - Image can be built",
 			})
@@ -104,7 +104,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 
 		if s, ok := imagesBeingPulled[service.Image]; ok {
 			w.Event(progress.Event{
-				ID:     service.Name,
+				ID:     name,
 				Status: progress.Done,
 				Text:   fmt.Sprintf("Skipped - Image is already being pulled by %v", s),
 			})
@@ -113,17 +113,18 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 
 		imagesBeingPulled[service.Image] = service.Name
 
+		idx, name, service := i, name, service
 		eg.Go(func() error {
 			_, err := s.pullServiceImage(ctx, service, s.configFile(), w, false, project.Environment["DOCKER_DEFAULT_PLATFORM"])
 			if err != nil {
-				pullErrors[i] = err
+				pullErrors[idx] = err
 				if service.Build != nil {
 					mustBuild = append(mustBuild, service.Name)
 				}
 				if !opts.IgnoreFailures && service.Build == nil {
 					if s.dryRun {
 						w.Event(progress.Event{
-							ID:     service.Name,
+							ID:     name,
 							Status: progress.Error,
 							Text:   fmt.Sprintf(" - Pull error for image: %s", service.Image),
 						})
@@ -134,6 +135,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 			}
 			return nil
 		})
+		i++
 	}
 
 	err = eg.Wait()
@@ -260,7 +262,7 @@ func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
 }
 
 func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error {
-	var needPull types.Services
+	var needPull []types.ServiceConfig
 	for _, service := range project.Services {
 		if service.Image == "" {
 			continue
diff --git a/pkg/compose/push.go b/pkg/compose/push.go
index c9f0ebf8d..934eecde6 100644
--- a/pkg/compose/push.go
+++ b/pkg/compose/push.go
@@ -25,7 +25,7 @@ import (
 	"io"
 	"strings"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/distribution/reference"
 	"github.com/docker/buildx/driver"
 	moby "github.com/docker/docker/api/types"
diff --git a/pkg/compose/restart.go b/pkg/compose/restart.go
index db27b1fba..0a93813ed 100644
--- a/pkg/compose/restart.go
+++ b/pkg/compose/restart.go
@@ -20,7 +20,7 @@ import (
 	"context"
 	"strings"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/utils"
diff --git a/pkg/compose/run.go b/pkg/compose/run.go
index 05c5c2ba1..aca15f380 100644
--- a/pkg/compose/run.go
+++ b/pkg/compose/run.go
@@ -23,7 +23,7 @@ import (
 	"os"
 	"os/signal"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli"
 	cmd "github.com/docker/cli/cli/command/container"
 	"github.com/docker/compose/v2/pkg/api"
@@ -73,7 +73,8 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
 	if service.ContainerName == "" {
 		service.ContainerName = fmt.Sprintf("%[1]s%[4]s%[2]s%[4]srun%[4]s%[3]s", project.Name, service.Name, stringid.TruncateID(slug), api.Separator)
 	}
-	service.Scale = 1
+	one := 1
+	service.Scale = &one
 	service.Restart = ""
 	if service.Deploy != nil {
 		service.Deploy.RestartPolicy = nil
diff --git a/pkg/compose/scale.go b/pkg/compose/scale.go
index c143b56d8..1e869ea72 100644
--- a/pkg/compose/scale.go
+++ b/pkg/compose/scale.go
@@ -18,7 +18,7 @@ package compose
 import (
 	"context"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/progress"
diff --git a/pkg/compose/secrets.go b/pkg/compose/secrets.go
index e5570d320..5619d92f5 100644
--- a/pkg/compose/secrets.go
+++ b/pkg/compose/secrets.go
@@ -24,7 +24,7 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	moby "github.com/docker/docker/api/types"
 )
 
diff --git a/pkg/compose/start.go b/pkg/compose/start.go
index 1333b5cec..dd35a2e94 100644
--- a/pkg/compose/start.go
+++ b/pkg/compose/start.go
@@ -30,7 +30,7 @@ import (
 	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/utils"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"golang.org/x/sync/errgroup"
diff --git a/pkg/compose/up.go b/pkg/compose/up.go
index 6b168e780..fb54a5965 100644
--- a/pkg/compose/up.go
+++ b/pkg/compose/up.go
@@ -23,7 +23,7 @@ import (
 	"os/signal"
 	"syscall"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
diff --git a/pkg/compose/viz.go b/pkg/compose/viz.go
index 25ea7a2be..3fd5a2386 100644
--- a/pkg/compose/viz.go
+++ b/pkg/compose/viz.go
@@ -21,7 +21,7 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/api"
 )
 
@@ -30,13 +30,13 @@ type vizGraph map[*types.ServiceConfig][]*types.ServiceConfig
 
 func (s *composeService) Viz(_ context.Context, project *types.Project, opts api.VizOptions) (string, error) {
 	graph := make(vizGraph)
-	for i, serviceConfig := range project.Services {
-		serviceConfigPtr := &project.Services[i]
-		graph[serviceConfigPtr] = make([]*types.ServiceConfig, 0, len(serviceConfig.DependsOn))
-		for dependencyName := range serviceConfig.DependsOn {
+	for _, service := range project.Services {
+		service := service
+		graph[&service] = make([]*types.ServiceConfig, 0, len(service.DependsOn))
+		for dependencyName := range service.DependsOn {
 			// no error should be returned since dependencyName should exist
 			dependency, _ := project.GetService(dependencyName)
-			graph[serviceConfigPtr] = append(graph[serviceConfigPtr], &dependency)
+			graph[&service] = append(graph[&service], &dependency)
 		}
 	}
 
diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go
index a3961b793..288bc2f85 100644
--- a/pkg/compose/watch.go
+++ b/pkg/compose/watch.go
@@ -27,7 +27,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/internal/sync"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/watch"
diff --git a/pkg/mocks/mock_docker_compose_api.go b/pkg/mocks/mock_docker_compose_api.go
index 7eb0f7d4b..20e23cf7b 100644
--- a/pkg/mocks/mock_docker_compose_api.go
+++ b/pkg/mocks/mock_docker_compose_api.go
@@ -8,7 +8,7 @@ import (
 	context "context"
 	reflect "reflect"
 
-	types "github.com/compose-spec/compose-go/types"
+	types "github.com/compose-spec/compose-go/v2/types"
 	api "github.com/docker/compose/v2/pkg/api"
 	gomock "github.com/golang/mock/gomock"
 )
diff --git a/pkg/remote/git.go b/pkg/remote/git.go
index 4c3388630..4d42007cc 100644
--- a/pkg/remote/git.go
+++ b/pkg/remote/git.go
@@ -25,9 +25,9 @@ import (
 	"regexp"
 	"strconv"
 
-	"github.com/compose-spec/compose-go/cli"
-	"github.com/compose-spec/compose-go/loader"
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/cli"
+	"github.com/compose-spec/compose-go/v2/loader"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/moby/buildkit/util/gitutil"
 )
diff --git a/pkg/remote/oci.go b/pkg/remote/oci.go
index 3c8b2c995..d8e64509c 100644
--- a/pkg/remote/oci.go
+++ b/pkg/remote/oci.go
@@ -25,7 +25,7 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/compose-spec/compose-go/loader"
+	"github.com/compose-spec/compose-go/v2/loader"
 	"github.com/distribution/reference"
 	"github.com/docker/buildx/store/storeutil"
 	"github.com/docker/buildx/util/imagetools"


Test patch:
diff --git a/cmd/compose/compose_test.go b/cmd/compose/compose_test.go
index c999ab3de..b61e6215f 100644
--- a/cmd/compose/compose_test.go
+++ b/cmd/compose/compose_test.go
@@ -19,27 +19,27 @@ package compose
 import (
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"gotest.tools/v3/assert"
 )
 
 func TestFilterServices(t *testing.T) {
 	p := &types.Project{
 		Services: types.Services{
-			{
+			"foo": {
 				Name:  "foo",
 				Links: []string{"bar"},
 			},
-			{
+			"bar": {
 				Name: "bar",
 				DependsOn: map[string]types.ServiceDependency{
 					"zot": {},
 				},
 			},
-			{
+			"zot": {
 				Name: "zot",
 			},
-			{
+			"qix": {
 				Name: "qix",
 			},
 		},
diff --git a/cmd/compose/create_test.go b/cmd/compose/create_test.go
index 95a2a9614..77a6e9e13 100644
--- a/cmd/compose/create_test.go
+++ b/cmd/compose/create_test.go
@@ -21,7 +21,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/davecgh/go-spew/spew"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/mocks"
@@ -86,7 +86,7 @@ func sampleProject() *types.Project {
 	return &types.Project{
 		Name: "test",
 		Services: types.Services{
-			{
+			"svc": {
 				Name: "svc",
 				Build: &types.BuildConfig{
 					Context: ".",
diff --git a/cmd/compose/options_test.go b/cmd/compose/options_test.go
index 66c72db73..866306e6c 100644
--- a/cmd/compose/options_test.go
+++ b/cmd/compose/options_test.go
@@ -19,7 +19,7 @@ package compose
 import (
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/stretchr/testify/require"
 )
 
@@ -27,7 +27,7 @@ func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
 	makeProject := func() *types.Project {
 		return &types.Project{
 			Services: types.Services{
-				{
+				"test": {
 					Name:  "test",
 					Image: "foo",
 					Build: &types.BuildConfig{
@@ -47,14 +47,14 @@ func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
 	t.Run("SinglePlatform", func(t *testing.T) {
 		project := makeProject()
 		require.NoError(t, applyPlatforms(project, true))
-		require.EqualValues(t, []string{"alice/32"}, project.Services[0].Build.Platforms)
+		require.EqualValues(t, []string{"alice/32"}, project.Services["test"].Build.Platforms)
 	})
 
 	t.Run("MultiPlatform", func(t *testing.T) {
 		project := makeProject()
 		require.NoError(t, applyPlatforms(project, false))
 		require.EqualValues(t, []string{"linux/amd64", "linux/arm64", "alice/32"},
-			project.Services[0].Build.Platforms)
+			project.Services["test"].Build.Platforms)
 	})
 }
 
@@ -65,7 +65,7 @@ func TestApplyPlatforms_DockerDefaultPlatform(t *testing.T) {
 				"DOCKER_DEFAULT_PLATFORM": "linux/amd64",
 			},
 			Services: types.Services{
-				{
+				"test": {
 					Name:  "test",
 					Image: "foo",
 					Build: &types.BuildConfig{
@@ -83,14 +83,14 @@ func TestApplyPlatforms_DockerDefaultPlatform(t *testing.T) {
 	t.Run("SinglePlatform", func(t *testing.T) {
 		project := makeProject()
 		require.NoError(t, applyPlatforms(project, true))
-		require.EqualValues(t, []string{"linux/amd64"}, project.Services[0].Build.Platforms)
+		require.EqualValues(t, []string{"linux/amd64"}, project.Services["test"].Build.Platforms)
 	})
 
 	t.Run("MultiPlatform", func(t *testing.T) {
 		project := makeProject()
 		require.NoError(t, applyPlatforms(project, false))
 		require.EqualValues(t, []string{"linux/amd64", "linux/arm64"},
-			project.Services[0].Build.Platforms)
+			project.Services["test"].Build.Platforms)
 	})
 }
 
@@ -101,7 +101,7 @@ func TestApplyPlatforms_UnsupportedPlatform(t *testing.T) {
 				"DOCKER_DEFAULT_PLATFORM": "commodore/64",
 			},
 			Services: types.Services{
-				{
+				"test": {
 					Name:  "test",
 					Image: "foo",
 					Build: &types.BuildConfig{
diff --git a/cmd/compose/pullOptions_test.go b/cmd/compose/pullOptions_test.go
index cb14c4728..153a050ec 100644
--- a/cmd/compose/pullOptions_test.go
+++ b/cmd/compose/pullOptions_test.go
@@ -19,28 +19,28 @@ package compose
 import (
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"gotest.tools/v3/assert"
 )
 
 func TestApplyPullOptions(t *testing.T) {
 	project := &types.Project{
 		Services: types.Services{
-			{
+			"must-build": {
 				Name: "must-build",
 				// No image, local build only
 				Build: &types.BuildConfig{
 					Context: ".",
 				},
 			},
-			{
+			"has-build": {
 				Name:  "has-build",
 				Image: "registry.example.com/myservice",
 				Build: &types.BuildConfig{
 					Context: ".",
 				},
 			},
-			{
+			"must-pull": {
 				Name:  "must-pull",
 				Image: "registry.example.com/another-service",
 			},
@@ -51,7 +51,7 @@ func TestApplyPullOptions(t *testing.T) {
 	}.apply(project, nil)
 	assert.NilError(t, err)
 
-	assert.Equal(t, project.Services[0].PullPolicy, "") // still default
-	assert.Equal(t, project.Services[1].PullPolicy, types.PullPolicyMissing)
-	assert.Equal(t, project.Services[2].PullPolicy, types.PullPolicyMissing)
+	assert.Equal(t, project.Services["must-build"].PullPolicy, "") // still default
+	assert.Equal(t, project.Services["has-build"].PullPolicy, types.PullPolicyMissing)
+	assert.Equal(t, project.Services["must-pull"].PullPolicy, types.PullPolicyMissing)
 }
diff --git a/cmd/compose/up_test.go b/cmd/compose/up_test.go
index 951b11897..cf41d2d8f 100644
--- a/cmd/compose/up_test.go
+++ b/cmd/compose/up_test.go
@@ -19,25 +19,33 @@ package compose
 import (
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"gotest.tools/v3/assert"
 )
 
 func TestApplyScaleOpt(t *testing.T) {
 	p := types.Project{
 		Services: types.Services{
-			{
+			"foo": {
 				Name: "foo",
 			},
-			{
+			"bar": {
 				Name: "bar",
+				Deploy: &types.DeployConfig{
+					Mode: "test",
+				},
 			},
 		},
 	}
-	opt := createOptions{scale: []string{"foo=2"}}
-	err := opt.Apply(&p)
+	err := applyScaleOpts(&p, []string{"foo=2", "bar=3"})
 	assert.NilError(t, err)
 	foo, err := p.GetService("foo")
 	assert.NilError(t, err)
-	assert.Equal(t, *foo.Deploy.Replicas, uint64(2))
+	assert.Equal(t, *foo.Scale, 2)
+
+	bar, err := p.GetService("bar")
+	assert.NilError(t, err)
+	assert.Equal(t, *bar.Scale, 3)
+	assert.Equal(t, *bar.Deploy.Replicas, 3)
+
 }
diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go
index 0d9633151..fc44abe7f 100644
--- a/pkg/api/api_test.go
+++ b/pkg/api/api_test.go
@@ -19,7 +19,7 @@ package api
 import (
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"gotest.tools/v3/assert"
 )
 
diff --git a/pkg/compose/convergence_test.go b/pkg/compose/convergence_test.go
index d35146c37..eca2e5006 100644
--- a/pkg/compose/convergence_test.go
+++ b/pkg/compose/convergence_test.go
@@ -22,7 +22,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	moby "github.com/docker/docker/api/types"
 	containerType "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
@@ -37,31 +37,33 @@ func TestContainerName(t *testing.T) {
 	s := types.ServiceConfig{
 		Name:          "testservicename",
 		ContainerName: "testcontainername",
-		Scale:         1,
+		Scale:         intPtr(1),
 		Deploy:        &types.DeployConfig{},
 	}
 	ret, err := getScale(s)
 	assert.NilError(t, err)
-	assert.Equal(t, ret, s.Scale)
+	assert.Equal(t, ret, *s.Scale)
 
-	var zero uint64 // = 0
-	s.Deploy.Replicas = &zero
+	s.Scale = intPtr(0)
 	ret, err = getScale(s)
 	assert.NilError(t, err)
-	assert.Equal(t, ret, int(*s.Deploy.Replicas))
+	assert.Equal(t, ret, *s.Scale)
 
-	var two uint64 = 2
-	s.Deploy.Replicas = &two
+	s.Scale = intPtr(2)
 	_, err = getScale(s)
 	assert.Error(t, err, fmt.Sprintf(doubledContainerNameWarning, s.Name, s.ContainerName))
 }
 
+func intPtr(i int) *int {
+	return &i
+}
+
 func TestServiceLinks(t *testing.T) {
 	const dbContainerName = "/" + testProject + "-db-1"
 	const webContainerName = "/" + testProject + "-web-1"
 	s := types.ServiceConfig{
 		Name:  "web",
-		Scale: 1,
+		Scale: intPtr(1),
 	}
 
 	containerListOptions := containerType.ListOptions{
@@ -223,9 +225,12 @@ func TestWaitDependencies(t *testing.T) {
 	cli.EXPECT().Client().Return(apiClient).AnyTimes()
 
 	t.Run("should skip dependencies with scale 0", func(t *testing.T) {
-		dbService := types.ServiceConfig{Name: "db", Scale: 0}
-		redisService := types.ServiceConfig{Name: "redis", Scale: 0}
-		project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{dbService, redisService}}
+		dbService := types.ServiceConfig{Name: "db", Scale: intPtr(0)}
+		redisService := types.ServiceConfig{Name: "redis", Scale: intPtr(0)}
+		project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{
+			"db":    dbService,
+			"redis": redisService,
+		}}
 		dependencies := types.DependsOnConfig{
 			"db":    {Condition: ServiceConditionRunningOrHealthy},
 			"redis": {Condition: ServiceConditionRunningOrHealthy},
@@ -233,9 +238,12 @@ func TestWaitDependencies(t *testing.T) {
 		assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil))
 	})
 	t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
-		dbService := types.ServiceConfig{Name: "db", Scale: 1}
-		redisService := types.ServiceConfig{Name: "redis", Scale: 1}
-		project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{dbService, redisService}}
+		dbService := types.ServiceConfig{Name: "db", Scale: intPtr(1)}
+		redisService := types.ServiceConfig{Name: "redis", Scale: intPtr(1)}
+		project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{
+			"db":    dbService,
+			"redis": redisService,
+		}}
 		dependencies := types.DependsOnConfig{
 			"db":    {Condition: types.ServiceConditionStarted, Required: true},
 			"redis": {Condition: types.ServiceConditionStarted, Required: true},
diff --git a/pkg/compose/create_test.go b/pkg/compose/create_test.go
index 26f7f4d15..1a4734896 100644
--- a/pkg/compose/create_test.go
+++ b/pkg/compose/create_test.go
@@ -26,7 +26,7 @@ import (
 
 	"github.com/docker/compose/v2/pkg/api"
 
-	composetypes "github.com/compose-spec/compose-go/types"
+	composetypes "github.com/compose-spec/compose-go/v2/types"
 	moby "github.com/docker/docker/api/types"
 	mountTypes "github.com/docker/docker/api/types/mount"
 
@@ -101,8 +101,8 @@ func TestPrepareNetworkLabels(t *testing.T) {
 func TestBuildContainerMountOptions(t *testing.T) {
 	project := composetypes.Project{
 		Name: "myProject",
-		Services: []composetypes.ServiceConfig{
-			{
+		Services: composetypes.Services{
+			"myService": {
 				Name: "myService",
 				Volumes: []composetypes.ServiceVolumeConfig{
 					{
@@ -144,7 +144,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
 		},
 	}
 
-	mounts, err := buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit)
+	mounts, err := buildContainerMountOptions(project, project.Services["myService"], moby.ImageInspect{}, inherit)
 	sort.Slice(mounts, func(i, j int) bool {
 		return mounts[i].Target < mounts[j].Target
 	})
@@ -154,7 +154,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
 	assert.Equal(t, mounts[1].Target, "/var/myvolume2")
 	assert.Equal(t, mounts[2].Target, "\\\\.\\pipe\\docker_engine")
 
-	mounts, err = buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit)
+	mounts, err = buildContainerMountOptions(project, project.Services["myService"], moby.ImageInspect{}, inherit)
 	sort.Slice(mounts, func(i, j int) bool {
 		return mounts[i].Target < mounts[j].Target
 	})
@@ -180,8 +180,8 @@ func TestDefaultNetworkSettings(t *testing.T) {
 		}
 		project := composetypes.Project{
 			Name: "myProject",
-			Services: []composetypes.ServiceConfig{
-				service,
+			Services: composetypes.Services{
+				"myService": service,
 			},
 			Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
 				"myNetwork1": {
@@ -205,8 +205,8 @@ func TestDefaultNetworkSettings(t *testing.T) {
 		}
 		project := composetypes.Project{
 			Name: "myProject",
-			Services: []composetypes.ServiceConfig{
-				service,
+			Services: composetypes.Services{
+				"myService": service,
 			},
 			Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
 				"myNetwork1": {
@@ -233,8 +233,8 @@ func TestDefaultNetworkSettings(t *testing.T) {
 		}
 		project := composetypes.Project{
 			Name: "myProject",
-			Services: []composetypes.ServiceConfig{
-				service,
+			Services: composetypes.Services{
+				"myService": service,
 			},
 		}
 
@@ -250,7 +250,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
 		}
 		project := composetypes.Project{
 			Name:     "myProject",
-			Services: []composetypes.ServiceConfig{service},
+			Services: composetypes.Services{"myService": service},
 			Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
 				"default": {
 					Name: "myProject_default",
diff --git a/pkg/compose/dependencies_test.go b/pkg/compose/dependencies_test.go
index a43b22e17..b54702fb3 100644
--- a/pkg/compose/dependencies_test.go
+++ b/pkg/compose/dependencies_test.go
@@ -23,7 +23,7 @@ import (
 	"sync"
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/utils"
 	testify "github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -33,19 +33,19 @@ import (
 func createTestProject() *types.Project {
 	return &types.Project{
 		Services: types.Services{
-			{
+			"test1": {
 				Name: "test1",
 				DependsOn: map[string]types.ServiceDependency{
 					"test2": {},
 				},
 			},
-			{
+			"test2": {
 				Name: "test2",
 				DependsOn: map[string]types.ServiceDependency{
 					"test3": {},
 				},
 			},
-			{
+			"test3": {
 				Name: "test3",
 			},
 		},
@@ -59,7 +59,7 @@ func TestTraversalWithMultipleParents(t *testing.T) {
 	}
 
 	project := types.Project{
-		Services: types.Services{dependent},
+		Services: types.Services{"dependent": dependent},
 	}
 
 	for i := 1; i <= 100; i++ {
@@ -67,7 +67,7 @@ func TestTraversalWithMultipleParents(t *testing.T) {
 		dependent.DependsOn[name] = types.ServiceDependency{}
 
 		svc := types.ServiceConfig{Name: name}
-		project.Services = append(project.Services, svc)
+		project.Services[name] = svc
 	}
 
 	ctx, cancel := context.WithCancel(context.Background())
@@ -132,7 +132,7 @@ func TestBuildGraph(t *testing.T) {
 		{
 			desc: "builds graph with single service",
 			services: types.Services{
-				{
+				"test": {
 					Name:      "test",
 					DependsOn: types.DependsOnConfig{},
 				},
@@ -150,11 +150,11 @@ func TestBuildGraph(t *testing.T) {
 		{
 			desc: "builds graph with two separate services",
 			services: types.Services{
-				{
+				"test": {
 					Name:      "test",
 					DependsOn: types.DependsOnConfig{},
 				},
-				{
+				"another": {
 					Name:      "another",
 					DependsOn: types.DependsOnConfig{},
 				},
@@ -179,13 +179,13 @@ func TestBuildGraph(t *testing.T) {
 		{
 			desc: "builds graph with a service and a dependency",
 			services: types.Services{
-				{
+				"test": {
 					Name: "test",
 					DependsOn: types.DependsOnConfig{
 						"another": types.ServiceDependency{},
 					},
 				},
-				{
+				"another": {
 					Name:      "another",
 					DependsOn: types.DependsOnConfig{},
 				},
@@ -214,19 +214,19 @@ func TestBuildGraph(t *testing.T) {
 		{
 			desc: "builds graph with multiple dependency levels",
 			services: types.Services{
-				{
+				"test": {
 					Name: "test",
 					DependsOn: types.DependsOnConfig{
 						"another": types.ServiceDependency{},
 					},
 				},
-				{
+				"another": {
 					Name: "another",
 					DependsOn: types.DependsOnConfig{
 						"another_dep": types.ServiceDependency{},
 					},
 				},
-				{
+				"another_dep": {
 					Name:      "another_dep",
 					DependsOn: types.DependsOnConfig{},
 				},
diff --git a/pkg/compose/down_test.go b/pkg/compose/down_test.go
index fdd5153c3..08f3dfbfb 100644
--- a/pkg/compose/down_test.go
+++ b/pkg/compose/down_test.go
@@ -23,7 +23,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/streams"
 	moby "github.com/docker/docker/api/types"
 	containerType "github.com/docker/docker/api/types/container"
@@ -184,12 +184,12 @@ func TestDownRemoveImages(t *testing.T) {
 		Project: &types.Project{
 			Name: strings.ToLower(testProject),
 			Services: types.Services{
-				{Name: "local-anonymous"},
-				{Name: "local-named", Image: "local-named-image"},
-				{Name: "remote", Image: "remote-image"},
-				{Name: "remote-tagged", Image: "registry.example.com/remote-image-tagged:v1.0"},
-				{Name: "no-images-anonymous"},
-				{Name: "no-images-named", Image: "missing-named-image"},
+				"local-anonymous":     {Name: "local-anonymous"},
+				"local-named":         {Name: "local-named", Image: "local-named-image"},
+				"remote":              {Name: "remote", Image: "remote-image"},
+				"remote-tagged":       {Name: "remote-tagged", Image: "registry.example.com/remote-image-tagged:v1.0"},
+				"no-images-anonymous": {Name: "no-images-anonymous"},
+				"no-images-named":     {Name: "no-images-named", Image: "missing-named-image"},
 			},
 		},
 	}
diff --git a/pkg/compose/hash_test.go b/pkg/compose/hash_test.go
index 0520d6cee..73b7f3877 100644
--- a/pkg/compose/hash_test.go
+++ b/pkg/compose/hash_test.go
@@ -19,7 +19,7 @@ package compose
 import (
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"gotest.tools/v3/assert"
 )
 
@@ -31,9 +31,9 @@ func TestServiceHash(t *testing.T) {
 	assert.Equal(t, hash1, hash2)
 }
 
-func serviceConfig(replicas uint64) types.ServiceConfig {
+func serviceConfig(replicas int) types.ServiceConfig {
 	return types.ServiceConfig{
-		Scale: int(replicas),
+		Scale: &replicas,
 		Deploy: &types.DeployConfig{
 			Replicas: &replicas,
 		},
diff --git a/pkg/compose/logs_test.go b/pkg/compose/logs_test.go
index 9a96e34de..dad51f0ea 100644
--- a/pkg/compose/logs_test.go
+++ b/pkg/compose/logs_test.go
@@ -23,7 +23,7 @@ import (
 	"sync"
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	moby "github.com/docker/docker/api/types"
 	containerType "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
@@ -83,7 +83,7 @@ func TestComposeService_Logs_Demux(t *testing.T) {
 	opts := compose.LogOptions{
 		Project: &types.Project{
 			Services: types.Services{
-				{Name: "service"},
+				"service": {Name: "service"},
 			},
 		},
 	}
@@ -153,8 +153,8 @@ func TestComposeService_Logs_ServiceFiltering(t *testing.T) {
 	// reference `serviceB` even though it has running services for this proj
 	proj := &types.Project{
 		Services: types.Services{
-			{Name: "serviceA"},
-			{Name: "serviceC"},
+			"serviceA": {Name: "serviceA"},
+			"serviceC": {Name: "serviceC"},
 		},
 	}
 	consumer := &testLogConsumer{}
diff --git a/pkg/compose/viz_test.go b/pkg/compose/viz_test.go
index 9e5fb7312..5591996fb 100644
--- a/pkg/compose/viz_test.go
+++ b/pkg/compose/viz_test.go
@@ -21,7 +21,7 @@ import (
 	"strconv"
 	"testing"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 
@@ -34,7 +34,7 @@ func TestViz(t *testing.T) {
 		Name:       "viz-test",
 		WorkingDir: "/home",
 		Services: types.Services{
-			{
+			"service1": {
 				Name:  "service1",
 				Image: "image-for-service1",
 				Ports: []types.ServicePortConfig{
@@ -53,12 +53,12 @@ func TestViz(t *testing.T) {
 					"internal": nil,
 				},
 			},
-			{
+			"service2": {
 				Name:  "service2",
 				Image: "image-for-service2",
 				Ports: []types.ServicePortConfig{},
 			},
-			{
+			"service3": {
 				Name:  "service3",
 				Image: "some-image",
 				DependsOn: map[string]types.ServiceDependency{
@@ -66,7 +66,7 @@ func TestViz(t *testing.T) {
 					"service1": {},
 				},
 			},
-			{
+			"service4": {
 				Name:  "service4",
 				Image: "another-image",
 				DependsOn: map[string]types.ServiceDependency{
@@ -82,7 +82,7 @@ func TestViz(t *testing.T) {
 					"external": nil,
 				},
 			},
-			{
+			"With host IP": {
 				Name:  "With host IP",
 				Image: "user/image-name",
 				DependsOn: map[string]types.ServiceDependency{
@@ -149,11 +149,12 @@ func TestViz(t *testing.T) {
 
 		// check edges that SHOULD exist in the generated graph
 		allowedEdges := make(map[string][]string)
-		for _, service := range project.Services {
-			allowedEdges[service.Name] = make([]string, 0, len(service.DependsOn))
+		for name, service := range project.Services {
+			allowed := make([]string, 0, len(service.DependsOn))
 			for depName := range service.DependsOn {
-				allowedEdges[service.Name] = append(allowedEdges[service.Name], depName)
+				allowed = append(allowed, depName)
 			}
+			allowedEdges[name] = allowed
 		}
 		for serviceName, dependencies := range allowedEdges {
 			for _, dependencyName := range dependencies {
@@ -163,12 +164,12 @@ func TestViz(t *testing.T) {
 
 		// check edges that SHOULD NOT exist in the generated graph
 		forbiddenEdges := make(map[string][]string)
-		for _, service := range project.Services {
-			forbiddenEdges[service.Name] = make([]string, 0, len(project.ServiceNames())-len(service.DependsOn))
+		for name, service := range project.Services {
+			forbiddenEdges[name] = make([]string, 0, len(project.ServiceNames())-len(service.DependsOn))
 			for _, serviceName := range project.ServiceNames() {
 				_, edgeExists := service.DependsOn[serviceName]
 				if !edgeExists {
-					forbiddenEdges[service.Name] = append(forbiddenEdges[service.Name], serviceName)
+					forbiddenEdges[name] = append(forbiddenEdges[name], serviceName)
 				}
 			}
 		}
diff --git a/pkg/compose/watch_test.go b/pkg/compose/watch_test.go
index 4f8d674d4..c444182d3 100644
--- a/pkg/compose/watch_test.go
+++ b/pkg/compose/watch_test.go
@@ -20,7 +20,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/compose-spec/compose-go/types"
+	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/internal/sync"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/mocks"
@@ -106,7 +106,7 @@ func TestWatch_Sync(t *testing.T) {
 
 	proj := types.Project{
 		Services: types.Services{
-			{
+			"test": {
 				Name: "test",
 			},
 		},
diff --git a/pkg/e2e/e2e_config_plugin.go b/pkg/e2e/e2e_config_plugin.go
index 84ca6eabc..5de99481f 100644
--- a/pkg/e2e/e2e_config_plugin.go
+++ b/pkg/e2e/e2e_config_plugin.go
@@ -18,4 +18,4 @@
 
 package e2e
 
-const composeStandaloneMode = false
+const composeStandaloneMode = true
diff --git a/pkg/e2e/restart_test.go b/pkg/e2e/restart_test.go
index 91ade0b30..2b2a319f8 100644
--- a/pkg/e2e/restart_test.go
+++ b/pkg/e2e/restart_test.go
@@ -79,10 +79,10 @@ func TestRestartWithDependencies(t *testing.T) {
 	c.RunDockerComposeCmd(t, "-f", "./fixtures/restart-test/compose-depends-on.yaml", "up", "-d")
 
 	res := c.RunDockerComposeCmd(t, "restart", baseService)
-	fmt.Println(res.Combined())
-	assert.Assert(t, strings.Contains(res.Combined(), fmt.Sprintf("Container e2e-restart-deps-%s-1  Started", baseService)), res.Combined())
-	assert.Assert(t, strings.Contains(res.Combined(), fmt.Sprintf("Container e2e-restart-deps-%s-1  Started", depWithRestart)), res.Combined())
-	assert.Assert(t, !strings.Contains(res.Combined(), depNoRestart), res.Combined())
+	out := res.Combined()
+	assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1  Started", baseService)), out)
+	assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1  Started", depWithRestart)), out)
+	assert.Assert(t, !strings.Contains(out, depNoRestart), out)
 }
 
 func TestRestartWithProfiles(t *testing.T) {
diff --git a/pkg/e2e/scale_test.go b/pkg/e2e/scale_test.go
index c17751d96..1cd80ad1d 100644
--- a/pkg/e2e/scale_test.go
+++ b/pkg/e2e/scale_test.go
@@ -40,18 +40,21 @@ func TestScaleBasicCases(t *testing.T) {
 
 	t.Log("scale up one service")
 	res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "scale", "dbadmin=2")
-	checkServiceContainer(t, res.Combined(), "scale-basic-tests-dbadmin", "Started", 2)
+	out := res.Combined()
+	checkServiceContainer(t, out, "scale-basic-tests-dbadmin", "Started", 2)
 
 	t.Log("scale up 2 services")
 	res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "scale", "front=3", "back=2")
-	checkServiceContainer(t, res.Combined(), "scale-basic-tests-front", "Running", 2)
-	checkServiceContainer(t, res.Combined(), "scale-basic-tests-front", "Started", 1)
-	checkServiceContainer(t, res.Combined(), "scale-basic-tests-back", "Running", 1)
-	checkServiceContainer(t, res.Combined(), "scale-basic-tests-back", "Started", 1)
+	out = res.Combined()
+	checkServiceContainer(t, out, "scale-basic-tests-front", "Running", 2)
+	checkServiceContainer(t, out, "scale-basic-tests-front", "Started", 1)
+	checkServiceContainer(t, out, "scale-basic-tests-back", "Running", 1)
+	checkServiceContainer(t, out, "scale-basic-tests-back", "Started", 1)
 
 	t.Log("scale down one service")
 	res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "scale", "dbadmin=1")
-	checkServiceContainer(t, res.Combined(), "scale-basic-tests-dbadmin", "Running", 1)
+	out = res.Combined()
+	checkServiceContainer(t, out, "scale-basic-tests-dbadmin", "Running", 1)
 
 	t.Log("scale to 0 a service")
 	res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "scale", "dbadmin=0")
@@ -59,9 +62,10 @@ func TestScaleBasicCases(t *testing.T) {
 
 	t.Log("scale down 2 services")
 	res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "scale", "front=2", "back=1")
-	checkServiceContainer(t, res.Combined(), "scale-basic-tests-front", "Running", 2)
-	assert.Check(t, !strings.Contains(res.Combined(), "Container scale-basic-tests-front-3  Running"), res.Combined())
-	checkServiceContainer(t, res.Combined(), "scale-basic-tests-back", "Running", 1)
+	out = res.Combined()
+	checkServiceContainer(t, out, "scale-basic-tests-front", "Running", 2)
+	assert.Check(t, !strings.Contains(out, "Container scale-basic-tests-front-3  Running"), res.Combined())
+	checkServiceContainer(t, out, "scale-basic-tests-back", "Running", 1)
 }
 
 func TestScaleWithDepsCases(t *testing.T) {
diff --git a/pkg/e2e/start_stop_test.go b/pkg/e2e/start_stop_test.go
index 16730c3cc..75d9298b2 100644
--- a/pkg/e2e/start_stop_test.go
+++ b/pkg/e2e/start_stop_test.go
@@ -106,12 +106,14 @@ func TestStartStopWithDependencies(t *testing.T) {
 		assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-bar-1  Stopped"), res.Combined())
 
 		res = c.RunDockerComposeCmd(t, "--project-name", projectName, "start", "foo")
-		assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-bar-1  Started"), res.Combined())
-		assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-foo-1  Started"), res.Combined())
+		out := res.Combined()
+		assert.Assert(t, strings.Contains(out, "Container e2e-start-stop-with-dependencies-bar-1  Started"), out)
+		assert.Assert(t, strings.Contains(out, "Container e2e-start-stop-with-dependencies-foo-1  Started"), out)
 
 		res = c.RunDockerComposeCmd(t, "--project-name", projectName, "ps", "--status", "running")
-		assert.Assert(t, strings.Contains(res.Combined(), "e2e-start-stop-with-dependencies-bar-1"), res.Combined())
-		assert.Assert(t, strings.Contains(res.Combined(), "e2e-start-stop-with-dependencies-foo-1"), res.Combined())
+		out = res.Combined()
+		assert.Assert(t, strings.Contains(out, "e2e-start-stop-with-dependencies-bar-1"), out)
+		assert.Assert(t, strings.Contains(out, "e2e-start-stop-with-dependencies-foo-1"), out)
 	})
 
 	t.Run("Up no-deps links", func(t *testing.T) {