Repository: docker/compose
Pull request #12872 (2025-05-26 10:05:03)

build(deps): bump google.golang.org/grpc from 1.72.1 to 1.72.2
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.72.1 to 1.72.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/grpc/grpc-go/releases">google.golang.org/grpc's releases</a>.</em></p>
<blockquote>
<h2>Release 1.72.2</h2>
<h1>Bug Fixes</h1>
<ul>
<li>client: restore support for <code>NO_PROXY</code> environment variable when connecting to locally-resolved addresses (case 2 from <a href="https://github.com/grpc/proposal/blob/master/A1-http-connect-proxy-support.md">gRFC A1</a>). (<a href="https://redirect.github.com/grpc/grpc-go/issues/8329">#8329</a>)</li>
<li>balancer/least_request: fix panic on resolver errors. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8333">#8333</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/grpc/grpc-go/commit/6135a737c2b9ce20cecdcb22014a93f949f8609f"><code>6135a73</code></a> Change version to v1.72.2 (<a href="https://redirect.github.com/grpc/grpc-go/issues/8357">#8357</a>)</li>
<li><a href="https://github.com/grpc/grpc-go/commit/eef8c9c13b0d4f10e7105df74c566c3d02a258b3"><code>eef8c9c</code></a> delegatingresolver: avoid proxy for resolved addresses in NO_PROXY env (<a href="https://redirect.github.com/grpc/grpc-go/issues/8329">#8329</a>...</li>
<li><a href="https://github.com/grpc/grpc-go/commit/3b5fa74022a642fe1185fc07d4c18cf7a87d4225"><code>3b5fa74</code></a> balancer/least_request : Fix panic while handling resolver errors (<a href="https://redirect.github.com/grpc/grpc-go/issues/8333">#8333</a>) (<a href="https://redirect.github.com/grpc/grpc-go/issues/8">#8</a>...</li>
<li><a href="https://github.com/grpc/grpc-go/commit/edf643f50334001e8a434087c480cd5d70e9fe91"><code>edf643f</code></a> Change version to v1.72.2-dev (<a href="https://redirect.github.com/grpc/grpc-go/issues/8326">#8326</a>)</li>
<li>See full diff in <a href="https://github.com/grpc/grpc-go/compare/v1.72.1...v1.72.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.72.1&new-version=1.72.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/cmd/compose/build.go b/cmd/compose/build.go
index 9393745e0..f33689338 100644
--- a/cmd/compose/build.go
+++ b/cmd/compose/build.go
@@ -27,7 +27,6 @@ import (
 	"github.com/docker/cli/cli/command"
 	cliopts "github.com/docker/cli/opts"
 	ui "github.com/docker/compose/v2/pkg/progress"
-	buildkit "github.com/moby/buildkit/util/progress/progressui"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -137,7 +136,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
 	flags.Bool("no-rm", false, "Do not remove intermediate containers after a successful build. DEPRECATED")
 	flags.MarkHidden("no-rm") //nolint:errcheck
 	flags.VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
-	flags.StringVar(&p.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
+	flags.StringVar(&p.Progress, "progress", "", fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
 	flags.MarkHidden("progress") //nolint:errcheck
 	flags.BoolVar(&opts.print, "print", false, "Print equivalent bake file")
 	flags.BoolVar(&opts.check, "check", false, "Check build configuration")
diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go
index 4b8396974..10b53e53f 100644
--- a/cmd/compose/compose.go
+++ b/cmd/compose/compose.go
@@ -47,7 +47,6 @@ import (
 	ui "github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/remote"
 	"github.com/docker/compose/v2/pkg/utils"
-	buildkit "github.com/moby/buildkit/util/progress/progressui"
 	"github.com/morikuni/aec"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -230,7 +229,7 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
 	f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
 	f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
 	f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
-	f.StringVar(&o.Progress, "progress", defaultStringVar(ComposeProgress, string(buildkit.AutoMode)), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
+	f.StringVar(&o.Progress, "progress", os.Getenv(ComposeProgress), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
 	f.BoolVar(&o.All, "all-resources", false, "Include all resources, even those not used by services")
 	_ = f.MarkHidden("workdir")
 }
@@ -242,14 +241,6 @@ func defaultStringArrayVar(env string) []string {
 	})
 }
 
-// get default value for a command line flag from the env variable, if the env variable is not set, it returns the provided default value 'def'
-func defaultStringVar(env, def string) string {
-	if v, ok := os.LookupEnv(env); ok {
-		return v
-	}
-	return def
-}
-
 func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cli, services ...string) (*types.Project, string, error) {
 	name := o.ProjectName
 	var project *types.Project
@@ -516,8 +507,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 			}
 
 			switch opts.Progress {
-			case ui.ModeAuto:
-				ui.Mode = ui.ModeAuto
+			case "", ui.ModeAuto:
 				if ansi == "never" {
 					ui.Mode = ui.ModePlain
 				}
diff --git a/cmd/compose/images.go b/cmd/compose/images.go
index f06f8784c..458bbccd7 100644
--- a/cmd/compose/images.go
+++ b/cmd/compose/images.go
@@ -20,10 +20,12 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"maps"
 	"slices"
-	"sort"
 	"strings"
+	"time"
 
+	"github.com/containerd/platforms"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/go-units"
@@ -86,13 +88,10 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
 		return nil
 	}
 
-	sort.Slice(images, func(i, j int) bool {
-		return images[i].ContainerName < images[j].ContainerName
-	})
-
 	return formatter.Print(images, opts.Format, dockerCli.Out(),
 		func(w io.Writer) {
-			for _, img := range images {
+			for _, container := range slices.Sorted(maps.Keys(images)) {
+				img := images[container]
 				id := stringid.TruncateID(img.ID)
 				size := units.HumanSizeWithPrecision(float64(img.Size), 3)
 				repo := img.Repository
@@ -103,8 +102,10 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
 				if tag == "" {
 					tag = "<none>"
 				}
-				_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, repo, tag, id, size)
+				created := units.HumanDuration(time.Now().UTC().Sub(img.LastTagTime)) + " ago"
+				_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
+					container, repo, tag, platforms.Format(img.Platform), id, size, created)
 			}
 		},
-		"CONTAINER", "REPOSITORY", "TAG", "IMAGE ID", "SIZE")
+		"CONTAINER", "REPOSITORY", "TAG", "PLATFORM", "IMAGE ID", "SIZE", "CREATED")
 }
diff --git a/cmd/compose/up.go b/cmd/compose/up.go
index 8e7f74830..02d5e4a15 100644
--- a/cmd/compose/up.go
+++ b/cmd/compose/up.go
@@ -261,6 +261,7 @@ func runUp(
 			return err
 		}
 		bo.Services = services
+		bo.Deps = !upOptions.noDeps
 		build = &bo
 	}
 
diff --git a/docs/reference/compose.md b/docs/reference/compose.md
index 74a300571..391bf1a97 100644
--- a/docs/reference/compose.md
+++ b/docs/reference/compose.md
@@ -59,7 +59,7 @@ Define and run multi-container applications with Docker
 | `-f`, `--file`         | `stringArray` |         | Compose configuration files                                                                         |
 | `--parallel`           | `int`         | `-1`    | Control max parallelism, -1 for unlimited                                                           |
 | `--profile`            | `stringArray` |         | Specify a profile to enable                                                                         |
-| `--progress`           | `string`      | `auto`  | Set type of progress output (auto, tty, plain, json, quiet)                                         |
+| `--progress`           | `string`      |         | Set type of progress output (auto, tty, plain, json, quiet)                                         |
 | `--project-directory`  | `string`      |         | Specify an alternate working directory<br>(default: the path of the, first specified, Compose file) |
 | `-p`, `--project-name` | `string`      |         | Project name                                                                                        |
 
diff --git a/docs/reference/docker_compose.yaml b/docs/reference/docker_compose.yaml
index ad7c99e59..93332702c 100644
--- a/docs/reference/docker_compose.yaml
+++ b/docs/reference/docker_compose.yaml
@@ -169,7 +169,6 @@ options:
       swarm: false
     - option: progress
       value_type: string
-      default_value: auto
       description: Set type of progress output (auto, tty, plain, json, quiet)
       deprecated: false
       hidden: false
diff --git a/docs/reference/docker_compose_build.yaml b/docs/reference/docker_compose_build.yaml
index 1197d5314..6d1446a51 100644
--- a/docs/reference/docker_compose_build.yaml
+++ b/docs/reference/docker_compose_build.yaml
@@ -118,7 +118,6 @@ options:
       swarm: false
     - option: progress
       value_type: string
-      default_value: auto
       description: Set type of ui output (auto, tty, plain, json, quiet)
       deprecated: false
       hidden: true
diff --git a/go.mod b/go.mod
index d5c4e9ebb..d0a809179 100644
--- a/go.mod
+++ b/go.mod
@@ -43,7 +43,6 @@ require (
 	github.com/spf13/cobra v1.9.1
 	github.com/spf13/pflag v1.0.6
 	github.com/stretchr/testify v1.10.0
-	github.com/theupdateframework/notary v0.7.0
 	github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
 	go.opentelemetry.io/otel v1.35.0
@@ -56,7 +55,7 @@ require (
 	go.uber.org/mock v0.5.2
 	golang.org/x/sync v0.14.0
 	golang.org/x/sys v0.33.0
-	google.golang.org/grpc v1.72.1
+	google.golang.org/grpc v1.72.2
 	gopkg.in/yaml.v3 v3.0.1
 	gotest.tools/v3 v3.5.2
 	tags.cncf.io/container-device-interface v1.0.1
@@ -161,6 +160,7 @@ require (
 	github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
 	github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
 	github.com/shibumi/go-pathspec v1.3.0 // indirect
+	github.com/theupdateframework/notary v0.7.0 // indirect
 	github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
 	github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 // indirect
 	github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
diff --git a/go.sum b/go.sum
index 83a4ffb6e..14d08e83d 100644
--- a/go.sum
+++ b/go.sum
@@ -623,8 +623,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
 google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
-google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
-google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
+google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
+google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 16b1f9344..04b7f6c25 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -24,6 +24,7 @@ import (
 	"time"
 
 	"github.com/compose-spec/compose-go/v2/types"
+	"github.com/containerd/platforms"
 	"github.com/docker/cli/opts"
 )
 
@@ -78,7 +79,7 @@ type Service interface {
 	// Publish executes the equivalent to a `compose publish`
 	Publish(ctx context.Context, project *types.Project, repository string, options PublishOptions) error
 	// Images executes the equivalent of a `compose images`
-	Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
+	Images(ctx context.Context, projectName string, options ImagesOptions) (map[string]ImageSummary, error)
 	// MaxConcurrency defines upper limit for concurrent operations against engine API
 	MaxConcurrency(parallel int)
 	// DryRunMode defines if dry run applies to the command
@@ -535,12 +536,12 @@ type ContainerProcSummary struct {
 
 // ImageSummary holds container image description
 type ImageSummary struct {
-	ID            string
-	ContainerName string
-	Repository    string
-	Tag           string
-	Size          int64
-	LastTagTime   time.Time
+	ID          string
+	Repository  string
+	Tag         string
+	Platform    platforms.Platform
+	Size        int64
+	LastTagTime time.Time
 }
 
 // ServiceStatus hold status about a service
diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go
index e19866790..5f10e3986 100644
--- a/pkg/compose/convergence.go
+++ b/pkg/compose/convergence.go
@@ -191,7 +191,6 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
 		case ContainerCreated:
 		case ContainerRestarting:
 		case ContainerExited:
-			w.Event(progress.CreatedEvent(name))
 		default:
 			container := container
 			eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/start", tracing.ContainerOptions(container), func(ctx context.Context) error {
diff --git a/pkg/compose/create.go b/pkg/compose/create.go
index 2e4964eb6..01e8ca6ea 100644
--- a/pkg/compose/create.go
+++ b/pkg/compose/create.go
@@ -31,9 +31,6 @@ import (
 	"github.com/compose-spec/compose-go/v2/paths"
 	"github.com/compose-spec/compose-go/v2/types"
 	cerrdefs "github.com/containerd/errdefs"
-	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
-	"github.com/docker/compose/v2/pkg/prompt"
 	"github.com/docker/docker/api/types/blkiodev"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
@@ -45,6 +42,10 @@ import (
 	"github.com/docker/go-connections/nat"
 	"github.com/sirupsen/logrus"
 	cdi "tags.cncf.io/container-device-interface/pkg/parser"
+
+	"github.com/docker/compose/v2/pkg/api"
+	"github.com/docker/compose/v2/pkg/progress"
+	"github.com/docker/compose/v2/pkg/prompt"
 )
 
 type createOptions struct {
@@ -1262,6 +1263,9 @@ func (s *composeService) ensureNetwork(ctx context.Context, project *types.Proje
 }
 
 func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) { //nolint:gocyclo
+	// This is containers that could be left after a diverged network was removed
+	var dangledContainers Containers
+
 	// First, try to find a unique network matching by name or ID
 	inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
 	if err == nil {
@@ -1295,7 +1299,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
 				return inspect.ID, nil
 			}
 
-			err = s.removeDivergedNetwork(ctx, project, name, n)
+			dangledContainers, err = s.removeDivergedNetwork(ctx, project, name, n)
 			if err != nil {
 				return "", err
 			}
@@ -1392,10 +1396,16 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
 		return "", fmt.Errorf("failed to create network %s: %w", n.Name, err)
 	}
 	w.Event(progress.CreatedEvent(networkEventName))
+
+	err = s.connectNetwork(ctx, n.Name, dangledContainers, nil)
+	if err != nil {
+		return "", err
+	}
+
 	return resp.ID, nil
 }
 
-func (s *composeService) removeDivergedNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) error {
+func (s *composeService) removeDivergedNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (Containers, error) {
 	// Remove services attached to this network to force recreation
 	var services []string
 	for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
@@ -1412,13 +1422,54 @@ func (s *composeService) removeDivergedNetwork(ctx context.Context, project *typ
 		Project:  project,
 	})
 	if err != nil {
-		return err
+		return nil, err
+	}
+
+	containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, services...)
+	if err != nil {
+		return nil, err
+	}
+
+	err = s.disconnectNetwork(ctx, n.Name, containers)
+	if err != nil {
+		return nil, err
 	}
 
 	err = s.apiClient().NetworkRemove(ctx, n.Name)
 	eventName := fmt.Sprintf("Network %s", n.Name)
 	progress.ContextWriter(ctx).Event(progress.RemovedEvent(eventName))
-	return err
+	return containers, err
+}
+
+func (s *composeService) disconnectNetwork(
+	ctx context.Context,
+	network string,
+	containers Containers,
+) error {
+	for _, c := range containers {
+		err := s.apiClient().NetworkDisconnect(ctx, network, c.ID, true)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (s *composeService) connectNetwork(
+	ctx context.Context,
+	network string,
+	containers Containers,
+	config *network.EndpointSettings,
+) error {
+	for _, c := range containers {
+		err := s.apiClient().NetworkConnect(ctx, network, c.ID, config)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
 }
 
 func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) (string, error) {
diff --git a/pkg/compose/images.go b/pkg/compose/images.go
index 81936180c..4db036266 100644
--- a/pkg/compose/images.go
+++ b/pkg/compose/images.go
@@ -24,15 +24,18 @@ import (
 	"sync"
 
 	cerrdefs "github.com/containerd/errdefs"
+	"github.com/containerd/platforms"
 	"github.com/distribution/reference"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
+	"github.com/docker/docker/api/types/versions"
+	"github.com/docker/docker/client"
 	"golang.org/x/sync/errgroup"
 
 	"github.com/docker/compose/v2/pkg/api"
 )
 
-func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
+func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) (map[string]api.ImageSummary, error) {
 	projectName = strings.ToLower(projectName)
 	allContainers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
 		All:     true,
@@ -53,27 +56,61 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
 		containers = allContainers
 	}
 
-	images := []string{}
-	for _, c := range containers {
-		if !slices.Contains(images, c.Image) {
-			images = append(images, c.Image)
-		}
-	}
-	imageSummaries, err := s.getImageSummaries(ctx, images)
+	version, err := s.RuntimeVersion(ctx)
 	if err != nil {
 		return nil, err
 	}
-	summary := make([]api.ImageSummary, len(containers))
-	for i, c := range containers {
-		img, ok := imageSummaries[c.Image]
-		if !ok {
-			return nil, fmt.Errorf("failed to retrieve image for container %s", getCanonicalContainerName(c))
-		}
+	withPlatform := versions.GreaterThanOrEqualTo(version, "1.49")
+
+	summary := map[string]api.ImageSummary{}
+	var mux sync.Mutex
+	eg, ctx := errgroup.WithContext(ctx)
+	for _, c := range containers {
+		eg.Go(func() error {
+			image, err := s.apiClient().ImageInspect(ctx, c.Image)
+			if err != nil {
+				return err
+			}
+			id := image.ID // platform-specific image ID can't be combined with image tag, see https://github.com/moby/moby/issues/49995
+
+			if withPlatform && c.ImageManifestDescriptor != nil && c.ImageManifestDescriptor.Platform != nil {
+				image, err = s.apiClient().ImageInspect(ctx, c.Image, client.ImageInspectWithPlatform(c.ImageManifestDescriptor.Platform))
+				if err != nil {
+					return err
+				}
+			}
 
-		summary[i] = img
-		summary[i].ContainerName = getCanonicalContainerName(c)
+			var repository, tag string
+			ref, err := reference.ParseDockerRef(c.Image)
+			if err == nil {
+				// ParseDockerRef will reject a local image ID
+				repository = reference.FamiliarName(ref)
+				if tagged, ok := ref.(reference.Tagged); ok {
+					tag = tagged.Tag()
+				}
+			}
+
+			mux.Lock()
+			defer mux.Unlock()
+			summary[getCanonicalContainerName(c)] = api.ImageSummary{
+				ID:         id,
+				Repository: repository,
+				Tag:        tag,
+				Platform: platforms.Platform{
+					Architecture: image.Architecture,
+					OS:           image.Os,
+					OSVersion:    image.OsVersion,
+					Variant:      image.Variant,
+				},
+				Size:        image.Size,
+				LastTagTime: image.Metadata.LastTagTime,
+			}
+			return nil
+		})
 	}
-	return summary, nil
+
+	err = eg.Wait()
+	return summary, err
 }
 
 func (s *composeService) getImageSummaries(ctx context.Context, repoTags []string) (map[string]api.ImageSummary, error) {
diff --git a/pkg/mocks/mock_docker_api.go b/pkg/mocks/mock_docker_api.go
index 5a73aab3d..818d84583 100644
--- a/pkg/mocks/mock_docker_api.go
+++ b/pkg/mocks/mock_docker_api.go
@@ -5,7 +5,6 @@
 //
 //	mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
 //
-
 // Package mocks is a generated GoMock package.
 package mocks
 
diff --git a/pkg/mocks/mock_docker_cli.go b/pkg/mocks/mock_docker_cli.go
index f08d62d4a..34ccb0446 100644
--- a/pkg/mocks/mock_docker_cli.go
+++ b/pkg/mocks/mock_docker_cli.go
@@ -5,7 +5,6 @@
 //
 //	mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
 //
-
 // Package mocks is a generated GoMock package.
 package mocks
 
@@ -16,12 +15,8 @@ import (
 	configfile "github.com/docker/cli/cli/config/configfile"
 	docker "github.com/docker/cli/cli/context/docker"
 	store "github.com/docker/cli/cli/context/store"
-	store0 "github.com/docker/cli/cli/manifest/store"
-	client "github.com/docker/cli/cli/registry/client"
 	streams "github.com/docker/cli/cli/streams"
-	trust "github.com/docker/cli/cli/trust"
-	client0 "github.com/docker/docker/client"
-	client1 "github.com/theupdateframework/notary/client"
+	client "github.com/docker/docker/client"
 	metric "go.opentelemetry.io/otel/metric"
 	resource "go.opentelemetry.io/otel/sdk/resource"
 	trace "go.opentelemetry.io/otel/trace"
@@ -85,10 +80,10 @@ func (mr *MockCliMockRecorder) BuildKitEnabled() *gomock.Call {
 }
 
 // Client mocks base method.
-func (m *MockCli) Client() client0.APIClient {
+func (m *MockCli) Client() client.APIClient {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Client")
-	ret0, _ := ret[0].(client0.APIClient)
+	ret0, _ := ret[0].(client.APIClient)
 	return ret0
 }
 
@@ -224,20 +219,6 @@ func (mr *MockCliMockRecorder) In() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "In", reflect.TypeOf((*MockCli)(nil).In))
 }
 
-// ManifestStore mocks base method.
-func (m *MockCli) ManifestStore() store0.Store {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ManifestStore")
-	ret0, _ := ret[0].(store0.Store)
-	return ret0
-}
-
-// ManifestStore indicates an expected call of ManifestStore.
-func (mr *MockCliMockRecorder) ManifestStore() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ManifestStore", reflect.TypeOf((*MockCli)(nil).ManifestStore))
-}
-
 // MeterProvider mocks base method.
 func (m *MockCli) MeterProvider() metric.MeterProvider {
 	m.ctrl.T.Helper()
@@ -252,21 +233,6 @@ func (mr *MockCliMockRecorder) MeterProvider() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MeterProvider", reflect.TypeOf((*MockCli)(nil).MeterProvider))
 }
 
-// NotaryClient mocks base method.
-func (m *MockCli) NotaryClient(arg0 trust.ImageRefAndAuth, arg1 []string) (client1.Repository, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "NotaryClient", arg0, arg1)
-	ret0, _ := ret[0].(client1.Repository)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// NotaryClient indicates an expected call of NotaryClient.
-func (mr *MockCliMockRecorder) NotaryClient(arg0, arg1 any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotaryClient", reflect.TypeOf((*MockCli)(nil).NotaryClient), arg0, arg1)
-}
-
 // Out mocks base method.
 func (m *MockCli) Out() *streams.Out {
 	m.ctrl.T.Helper()
@@ -281,20 +247,6 @@ func (mr *MockCliMockRecorder) Out() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Out", reflect.TypeOf((*MockCli)(nil).Out))
 }
 
-// RegistryClient mocks base method.
-func (m *MockCli) RegistryClient(arg0 bool) client.RegistryClient {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "RegistryClient", arg0)
-	ret0, _ := ret[0].(client.RegistryClient)
-	return ret0
-}
-
-// RegistryClient indicates an expected call of RegistryClient.
-func (mr *MockCliMockRecorder) RegistryClient(arg0 any) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegistryClient", reflect.TypeOf((*MockCli)(nil).RegistryClient), arg0)
-}
-
 // Resource mocks base method.
 func (m *MockCli) Resource() *resource.Resource {
 	m.ctrl.T.Helper()
diff --git a/pkg/mocks/mock_docker_compose_api.go b/pkg/mocks/mock_docker_compose_api.go
index adf811a90..2fd3a3e72 100644
--- a/pkg/mocks/mock_docker_compose_api.go
+++ b/pkg/mocks/mock_docker_compose_api.go
@@ -5,7 +5,6 @@
 //
 //	mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service
 //
-
 // Package mocks is a generated GoMock package.
 package mocks
 
@@ -199,10 +198,10 @@ func (mr *MockServiceMockRecorder) Generate(ctx, options any) *gomock.Call {
 }
 
 // Images mocks base method.
-func (m *MockService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
+func (m *MockService) Images(ctx context.Context, projectName string, options api.ImagesOptions) (map[string]api.ImageSummary, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Images", ctx, projectName, options)
-	ret0, _ := ret[0].([]api.ImageSummary)
+	ret0, _ := ret[0].(map[string]api.ImageSummary)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
diff --git a/pkg/progress/writer.go b/pkg/progress/writer.go
index 3f4a74f56..793adb762 100644
--- a/pkg/progress/writer.go
+++ b/pkg/progress/writer.go
@@ -18,6 +18,7 @@ package progress
 
 import (
 	"context"
+	"fmt"
 	"io"
 	"sync"
 
@@ -121,29 +122,30 @@ func NewWriter(ctx context.Context, out *streams.Out, progressTitle string) (Wri
 	if !ok {
 		dryRun = false
 	}
-	if Mode == ModeQuiet {
+	switch Mode {
+	case ModeQuiet:
 		return quiet{}, nil
-	}
-
-	tty := Mode == ModeTTY
-	if Mode == ModeAuto && isTerminal {
-		tty = true
-	}
-	if tty {
-		return newTTYWriter(out, dryRun, progressTitle)
-	}
-	if Mode == ModeJSON {
+	case ModeJSON:
 		return &jsonWriter{
 			out:    out,
 			done:   make(chan bool),
 			dryRun: dryRun,
 		}, nil
+	case ModeTTY:
+		return newTTYWriter(out, dryRun, progressTitle)
+	case ModeAuto, "":
+		if isTerminal {
+			return newTTYWriter(out, dryRun, progressTitle)
+		}
+		fallthrough
+	case ModePlain:
+		return &plainWriter{
+			out:    out,
+			done:   make(chan bool),
+			dryRun: dryRun,
+		}, nil
 	}
-	return &plainWriter{
-		out:    out,
-		done:   make(chan bool),
-		dryRun: dryRun,
-	}, nil
+	return nil, fmt.Errorf("unknown progress mode: %s", Mode)
 }
 
 func newTTYWriter(out io.Writer, dryRun bool, progressTitle string) (Writer, error) {


Test patch:
diff --git a/pkg/compose/images_test.go b/pkg/compose/images_test.go
index 62fbd1dc5..a7a1d5e9a 100644
--- a/pkg/compose/images_test.go
+++ b/pkg/compose/images_test.go
@@ -21,6 +21,7 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/image"
@@ -42,9 +43,10 @@ func TestImages(t *testing.T) {
 	ctx := context.Background()
 	args := filters.NewArgs(projectFilter(strings.ToLower(testProject)))
 	listOpts := container.ListOptions{All: true, Filters: args}
+	api.EXPECT().ServerVersion(gomock.Any()).Return(types.Version{APIVersion: "1.96"}, nil).AnyTimes()
 	image1 := imageInspect("image1", "foo:1", 12345)
 	image2 := imageInspect("image2", "bar:2", 67890)
-	api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(image1, nil)
+	api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(image1, nil).MaxTimes(2)
 	api.EXPECT().ImageInspect(anyCancellableContext(), "bar:2").Return(image2, nil)
 	c1 := containerDetail("service1", "123", "running", "foo:1")
 	c2 := containerDetail("service1", "456", "running", "bar:2")
@@ -54,27 +56,24 @@ func TestImages(t *testing.T) {
 
 	images, err := tested.Images(ctx, strings.ToLower(testProject), compose.ImagesOptions{})
 
-	expected := []compose.ImageSummary{
-		{
-			ID:            "image1",
-			ContainerName: "123",
-			Repository:    "foo",
-			Tag:           "1",
-			Size:          12345,
+	expected := map[string]compose.ImageSummary{
+		"123": {
+			ID:         "image1",
+			Repository: "foo",
+			Tag:        "1",
+			Size:       12345,
 		},
-		{
-			ID:            "image2",
-			ContainerName: "456",
-			Repository:    "bar",
-			Tag:           "2",
-			Size:          67890,
+		"456": {
+			ID:         "image2",
+			Repository: "bar",
+			Tag:        "2",
+			Size:       67890,
 		},
-		{
-			ID:            "image1",
-			ContainerName: "789",
-			Repository:    "foo",
-			Tag:           "1",
-			Size:          12345,
+		"789": {
+			ID:         "image1",
+			Repository: "foo",
+			Tag:        "1",
+			Size:       12345,
 		},
 	}
 	assert.NilError(t, err)
diff --git a/pkg/e2e/build_test.go b/pkg/e2e/build_test.go
index dec1d37e6..806b1c958 100644
--- a/pkg/e2e/build_test.go
+++ b/pkg/e2e/build_test.go
@@ -524,3 +524,15 @@ func TestBuildEntitlements(t *testing.T) {
 		}
 	})
 }
+
+func TestBuildDependsOn(t *testing.T) {
+	c := NewParallelCLI(t)
+
+	t.Cleanup(func() {
+		c.RunDockerComposeCmd(t, "-f", "fixtures/build-dependencies/compose-depends_on.yaml", "down", "--rmi=local")
+	})
+
+	res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-dependencies/compose-depends_on.yaml", "--progress=plain", "up", "test2")
+	out := res.Combined()
+	assert.Check(t, strings.Contains(out, "test1  Built"))
+}
diff --git a/pkg/e2e/fixtures/build-dependencies/compose-depends_on.yaml b/pkg/e2e/fixtures/build-dependencies/compose-depends_on.yaml
new file mode 100644
index 000000000..90b2beaef
--- /dev/null
+++ b/pkg/e2e/fixtures/build-dependencies/compose-depends_on.yaml
@@ -0,0 +1,15 @@
+services:
+  test1:
+    pull_policy: build
+    build:
+      dockerfile_inline: FROM alpine
+    command:
+      - echo
+      - "test 1 success"
+  test2:
+    image: alpine
+    depends_on:
+      - test1
+    command:
+      - echo
+      - "test 2 success"
\ No newline at end of file
diff --git a/pkg/e2e/fixtures/network-recreate/compose.yaml b/pkg/e2e/fixtures/network-recreate/compose.yaml
new file mode 100644
index 000000000..06a0a3e63
--- /dev/null
+++ b/pkg/e2e/fixtures/network-recreate/compose.yaml
@@ -0,0 +1,10 @@
+services:
+  web:
+    image: nginx
+    networks:
+      - test
+
+networks:
+  test:
+    labels:
+      - foo=${FOO:-foo}
\ No newline at end of file
diff --git a/pkg/e2e/networks_test.go b/pkg/e2e/networks_test.go
index cbd4e0e71..68c8f5f45 100644
--- a/pkg/e2e/networks_test.go
+++ b/pkg/e2e/networks_test.go
@@ -199,3 +199,24 @@ func TestInterfaceName(t *testing.T) {
 	})
 	res.Assert(t, icmd.Expected{Out: "foobar@"})
 }
+
+func TestNetworkRecreate(t *testing.T) {
+	c := NewCLI(t)
+	const projectName = "network_recreate"
+	t.Cleanup(func() {
+		c.cleanupWithDown(t, projectName)
+	})
+	c.RunDockerComposeCmd(t, "-f", "./fixtures/network-recreate/compose.yaml", "--project-name", projectName, "up", "-d")
+
+	c = NewCLI(t, WithEnv("FOO=bar"))
+	res := c.RunDockerComposeCmd(t, "-f", "./fixtures/network-recreate/compose.yaml", "--project-name", projectName, "--progress=plain", "up", "-d")
+	err := res.Stderr()
+	fmt.Println(err)
+	res.Assert(t, icmd.Expected{Err: `
+ Container network_recreate-web-1  Stopped
+ Network network_recreate_test  Removed
+ Network network_recreate_test  Creating
+ Network network_recreate_test  Created
+ Container network_recreate-web-1  Starting
+ Container network_recreate-web-1  Started`})
+}