Repository: docker/compose-cli
Pull request #1578 (2021-04-20 18:07:18)

fix typo in ecs-architecture.md
**What I did**

Fixed a minor typo

**Related issue**
<!-- If this is a bug fix, make sure your description includes "fixes #xxxx", or "closes #xxxx" -->

None

<!-- optional tests
You can add a / mention to run tests executed by default only on main branch :
* `test-kube` to run Kube E2E tests
* `test-aci` to run ACI E2E tests
* `test-ecs` to run ECS E2E tests
* `test-windows` to run tests & E2E tests on windows
-->


gtardif: Ah, Thx for the PR, you need to sign your commit @rjruizes 

Patch:
diff --git a/api/compose/api.go b/api/compose/api.go
index 066d39b9..3014401f 100644
--- a/api/compose/api.go
+++ b/api/compose/api.go
@@ -126,8 +126,6 @@ type RestartOptions struct {
 type StopOptions struct {
 	// Timeout override container stop timeout
 	Timeout *time.Duration
-	// Services passed in the command line to be stopped
-	Services []string
 }
 
 // UpOptions group options of the Up API
@@ -189,8 +187,6 @@ type RemoveOptions struct {
 	Volumes bool
 	// Force don't ask to confirm removal
 	Force bool
-	// Services passed in the command line to be removed
-	Services []string
 }
 
 // RunOptions group options of the Run API
diff --git a/cli/cmd/compose/images.go b/cli/cmd/compose/images.go
index 29f8ff0f..edcf8b07 100644
--- a/cli/cmd/compose/images.go
+++ b/cli/cmd/compose/images.go
@@ -32,7 +32,7 @@ import (
 	"github.com/docker/compose-cli/utils"
 	"github.com/docker/docker/pkg/stringid"
 
-	"github.com/docker/go-units"
+	units "github.com/docker/go-units"
 )
 
 type imageOptions struct {
@@ -99,15 +99,8 @@ func runImages(ctx context.Context, opts imageOptions, services []string) error
 			for _, img := range images {
 				id := stringid.TruncateID(img.ID)
 				size := units.HumanSizeWithPrecision(float64(img.Size), 3)
-				repo := img.Repository
-				if repo == "" {
-					repo = "<none>"
-				}
-				tag := img.Tag
-				if tag == "" {
-					tag = "<none>"
-				}
-				_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, repo, tag, id, size)
+
+				_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, img.Repository, img.Tag, id, size)
 			}
 		},
 		"Container", "Repository", "Tag", "Image Id", "Size")
diff --git a/cli/cmd/compose/remove.go b/cli/cmd/compose/remove.go
index 9f381438..753f8300 100644
--- a/cli/cmd/compose/remove.go
+++ b/cli/cmd/compose/remove.go
@@ -73,9 +73,7 @@ func runRemove(ctx context.Context, opts removeOptions, services []string) error
 
 	if opts.stop {
 		_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
-			err := c.ComposeService().Stop(ctx, project, compose.StopOptions{
-				Services: services,
-			})
+			err := c.ComposeService().Stop(ctx, project, compose.StopOptions{})
 			return "", err
 		})
 		if err != nil {
@@ -84,8 +82,7 @@ func runRemove(ctx context.Context, opts removeOptions, services []string) error
 	}
 
 	reosurces, err := c.ComposeService().Remove(ctx, project, compose.RemoveOptions{
-		DryRun:   true,
-		Services: services,
+		DryRun: true,
 	})
 	if err != nil {
 		return err
diff --git a/cli/cmd/compose/stop.go b/cli/cmd/compose/stop.go
index 1884cf1b..f80dc1c9 100644
--- a/cli/cmd/compose/stop.go
+++ b/cli/cmd/compose/stop.go
@@ -69,8 +69,7 @@ func runStop(ctx context.Context, opts stopOptions, services []string) error {
 	}
 	_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
 		return "", c.ComposeService().Stop(ctx, project, compose.StopOptions{
-			Timeout:  timeout,
-			Services: services,
+			Timeout: timeout,
 		})
 	})
 	return err
diff --git a/docs/ecs-architecture.md b/docs/ecs-architecture.md
index e2ce1002..f4624262 100644
--- a/docs/ecs-architecture.md
+++ b/docs/ecs-architecture.md
@@ -59,7 +59,7 @@ This diagram shows compose model and on same line AWS components that get create
       +------------+                        +---------------+
 ```
 
-Each compose application service is mapped to an ECS `Service`. A `TaksDefinition` is created according to compose definition.
+Each compose application service is mapped to an ECS `Service`. A `TaskDefinition` is created according to compose definition.
 Actual mapping is constrained by both Cloud platform and Fargate limitations. Such a `TaskDefinition` is set with a single container,
 according to the compose model which doesn't offer a syntax to support sidecar containers.
 
diff --git a/go.mod b/go.mod
index 018dcc4a..5ee24a51 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/awslabs/goformation/v4 v4.15.6
 	github.com/buger/goterm v1.0.0
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
-	github.com/compose-spec/compose-go v0.0.0-20210422124345-d4bf0e1bfea5
+	github.com/compose-spec/compose-go v0.0.0-20210412113016-e35895260882
 	github.com/containerd/console v1.0.1
 	github.com/containerd/containerd v1.4.3
 	github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a // indirect
diff --git a/go.sum b/go.sum
index df1917bf..2944374d 100644
--- a/go.sum
+++ b/go.sum
@@ -308,8 +308,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
-github.com/compose-spec/compose-go v0.0.0-20210422124345-d4bf0e1bfea5 h1:uR5dDxz7FboGxh2YjlMJJBOc7RPl87rbV80VV7WJ9N0=
-github.com/compose-spec/compose-go v0.0.0-20210422124345-d4bf0e1bfea5/go.mod h1:6eIT9U2OgdHmkRD6szmqatCrWWEEUSwl/j2iJYH4jLo=
+github.com/compose-spec/compose-go v0.0.0-20210412113016-e35895260882 h1:vLKOiHY9QPS1eiBvGfsEI7wR23B1bGNvwOF5aVl25Fs=
+github.com/compose-spec/compose-go v0.0.0-20210412113016-e35895260882/go.mod h1:6eIT9U2OgdHmkRD6szmqatCrWWEEUSwl/j2iJYH4jLo=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
 github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
 github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340 h1:9atoWyI9RtXFwf7UDbme/6M8Ud0rFrx+Q3ZWgSnsxtw=
diff --git a/local/compose/build.go b/local/compose/build.go
index 5afb1711..35a113f4 100644
--- a/local/compose/build.go
+++ b/local/compose/build.go
@@ -19,7 +19,9 @@ package compose
 import (
 	"context"
 	"fmt"
+	"net/url"
 	"os"
+	"path"
 	"strings"
 
 	"github.com/compose-spec/compose-go/types"
@@ -30,6 +32,7 @@ import (
 	"github.com/docker/buildx/util/progress"
 	cliconfig "github.com/docker/cli/cli/config"
 	moby "github.com/docker/docker/api/types"
+	"github.com/docker/docker/errdefs"
 	bclient "github.com/moby/buildkit/client"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 
@@ -48,7 +51,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
 		if service.Build != nil {
 			imageName := getImageName(service, project.Name)
 			imagesToBuild = append(imagesToBuild, imageName)
-			buildOptions, err := s.toBuildOptions(service, imageName)
+			buildOptions, err := s.toBuildOptions(service, project.WorkingDir, imageName)
 			if err != nil {
 				return err
 			}
@@ -70,7 +73,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
 		}
 	}
 
-	_, err := s.build(ctx, project, opts, Containers{}, options.Progress)
+	err := s.build(ctx, project, opts, Containers{}, options.Progress)
 	if err == nil {
 		if len(imagesToBuild) > 0 && !options.Quiet {
 			utils.DisplayScanSuggestMsg()
@@ -81,60 +84,28 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
 }
 
 func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, observedState Containers, quietPull bool) error {
-	images, err := s.getImageDigests(ctx, project)
-	if err != nil {
-		return err
-	}
-
-	mode := progress.PrinterModeAuto
-	if quietPull {
-		mode = progress.PrinterModeQuiet
-	}
-	opts, imagesToBuild, err := s.getBuildOptions(project, images)
-	if err != nil {
-		return err
-	}
-	builtImages, err := s.build(ctx, project, opts, observedState, mode)
-	if err != nil {
-		return err
-	}
-
-	if len(imagesToBuild) > 0 {
-		utils.DisplayScanSuggestMsg()
-	}
-	for name, digest := range builtImages {
-		images[name] = digest
-	}
-	// set digest as service.Image
-	for i, service := range project.Services {
-		digest, ok := images[getImageName(service, project.Name)]
-		if ok {
-			project.Services[i].Image = digest
-		}
-	}
-	return nil
-}
-
-func (s *composeService) getBuildOptions(project *types.Project, images map[string]string) (map[string]build.Options, []string, error) {
 	opts := map[string]build.Options{}
 	imagesToBuild := []string{}
 	for _, service := range project.Services {
 		if service.Image == "" && service.Build == nil {
-			return nil, nil, 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", service.Name)
 		}
+
 		imageName := getImageName(service, project.Name)
-		_, localImagePresent := images[imageName]
+		localImagePresent, err := s.localImagePresent(ctx, imageName)
+		if err != nil {
+			return err
+		}
 
 		if service.Build != nil {
 			if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
 				continue
 			}
 			imagesToBuild = append(imagesToBuild, imageName)
-			opt, err := s.toBuildOptions(service, imageName)
+			opts[imageName], err = s.toBuildOptions(service, project.WorkingDir, imageName)
 			if err != nil {
-				return nil, nil, err
+				return err
 			}
-			opts[imageName] = opt
 			continue
 		}
 		if service.Image != "" {
@@ -142,6 +113,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
 				continue
 			}
 		}
+
 		// Buildx has no command to "just pull", see
 		// so we bake a temporary dockerfile that will just pull and export pulled image
 		opts[service.Name] = build.Options{
@@ -155,54 +127,57 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
 		}
 
 	}
-	return opts, imagesToBuild, nil
 
-}
+	mode := progress.PrinterModeAuto
+	if quietPull {
+		mode = progress.PrinterModeQuiet
+	}
 
-func (s *composeService) getImageDigests(ctx context.Context, project *types.Project) (map[string]string, error) {
-	imageNames := []string{}
-	for _, s := range project.Services {
-		imgName := getImageName(s, project.Name)
-		if !utils.StringContains(imageNames, imgName) {
-			imageNames = append(imageNames, imgName)
+	err := s.build(ctx, project, opts, observedState, mode)
+	if err == nil {
+		if len(imagesToBuild) > 0 {
+			utils.DisplayScanSuggestMsg()
 		}
 	}
-	imgs, err := s.getImages(ctx, imageNames)
+	return err
+}
+
+func (s *composeService) localImagePresent(ctx context.Context, imageName string) (bool, error) {
+	_, _, err := s.apiClient.ImageInspectWithRaw(ctx, imageName)
 	if err != nil {
-		return nil, err
-	}
-	images := map[string]string{}
-	for name, info := range imgs {
-		images[name] = info.ID
+		if errdefs.IsNotFound(err) {
+			return false, nil
+		}
+		return false, err
 	}
-	return images, nil
+	return true, nil
 }
 
-func (s *composeService) build(ctx context.Context, project *types.Project, opts map[string]build.Options, observedState Containers, mode string) (map[string]string, error) {
+func (s *composeService) build(ctx context.Context, project *types.Project, opts map[string]build.Options, observedState Containers, mode string) error {
 	info, err := s.apiClient.Info(ctx)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	if info.OSType == "windows" {
 		// no support yet for Windows container builds in Buildkit
 		// https://docs.docker.com/develop/develop-images/build_enhancements/#limitations
 		err := s.windowsBuild(opts, mode)
-		return nil, metrics.WrapCategorisedComposeError(err, metrics.BuildFailure)
+		return metrics.WrapCategorisedComposeError(err, metrics.BuildFailure)
 	}
 	if len(opts) == 0 {
-		return nil, nil
+		return nil
 	}
 	const drivername = "default"
 
 	configFile, err := cliconfig.Load(config.Dir())
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, configFile, nil, nil, "", nil, nil, project.WorkingDir)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	driverInfo := []build.DriverInfo{
 		{
@@ -219,13 +194,13 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opts
 	w := progress.NewPrinter(progressCtx, os.Stdout, mode)
 
 	// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
-	response, err := build.Build(ctx, driverInfo, opts, nil, nil, w)
+	_, err = build.Build(ctx, driverInfo, opts, nil, nil, w)
 	errW := w.Wait()
 	if err == nil {
 		err = errW
 	}
 	if err != nil {
-		return nil, metrics.WrapCategorisedComposeError(err, metrics.BuildFailure)
+		return metrics.WrapCategorisedComposeError(err, metrics.BuildFailure)
 	}
 
 	cw := composeprogress.ContextWriter(ctx)
@@ -234,31 +209,22 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opts
 			if c.Image == imageName {
 				err = s.removeContainers(ctx, cw, []moby.Container{c}, nil)
 				if err != nil {
-					return nil, err
+					return err
 				}
 			}
 		}
 	}
 
-	imagesBuilt := map[string]string{}
-	for name, img := range response {
-		if img == nil || len(img.ExporterResponse) == 0 {
-			continue
-		}
-		digest, ok := img.ExporterResponse["containerimage.digest"]
-		if !ok {
-			continue
-		}
-		imagesBuilt[name] = digest
-	}
-
-	return imagesBuilt, err
+	return err
 }
 
-func (s *composeService) toBuildOptions(service types.ServiceConfig, imageTag string) (build.Options, error) {
+func (s *composeService) toBuildOptions(service types.ServiceConfig, contextPath string, imageTag string) (build.Options, error) {
 	var tags []string
 	tags = append(tags, imageTag)
 
+	if service.Build.Dockerfile == "" {
+		service.Build.Dockerfile = "Dockerfile"
+	}
 	var buildArgs map[string]string
 
 	var plats []specs.Platform
@@ -270,11 +236,22 @@ func (s *composeService) toBuildOptions(service types.ServiceConfig, imageTag st
 		plats = append(plats, p)
 	}
 
-	return build.Options{
-		Inputs: build.Inputs{
+	var input build.Inputs
+	_, err := url.ParseRequestURI(service.Build.Context)
+	if err == nil {
+		input = build.Inputs{
 			ContextPath:    service.Build.Context,
 			DockerfilePath: service.Build.Dockerfile,
-		},
+		}
+	} else {
+		input = build.Inputs{
+			ContextPath:    path.Join(contextPath, service.Build.Context),
+			DockerfilePath: path.Join(contextPath, service.Build.Context, service.Build.Dockerfile),
+		}
+	}
+
+	return build.Options{
+		Inputs:    input,
 		BuildArgs: flatten(mergeArgs(service.Build.Args, buildArgs)),
 		Tags:      tags,
 		Target:    service.Build.Target,
diff --git a/local/compose/convergence.go b/local/compose/convergence.go
index d55d1e97..4a19c15a 100644
--- a/local/compose/convergence.go
+++ b/local/compose/convergence.go
@@ -25,10 +25,10 @@ import (
 	"github.com/compose-spec/compose-go/types"
 	"github.com/containerd/containerd/platforms"
 	moby "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/network"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
-	"github.com/sirupsen/logrus"
 	"golang.org/x/sync/errgroup"
 
 	"github.com/docker/compose-cli/api/compose"
@@ -147,13 +147,13 @@ func getContainerProgressName(container moby.Container) string {
 func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
 	eg, _ := errgroup.WithContext(ctx)
 	for dep, config := range service.DependsOn {
-		eg.Go(func() error {
-			ticker := time.NewTicker(500 * time.Millisecond)
-			defer ticker.Stop()
-			for {
-				<-ticker.C
-				switch config.Condition {
-				case types.ServiceConditionHealthy:
+		switch config.Condition {
+		case "service_healthy":
+			eg.Go(func() error {
+				ticker := time.NewTicker(500 * time.Millisecond)
+				defer ticker.Stop()
+				for {
+					<-ticker.C
 					healthy, err := s.isServiceHealthy(ctx, project, dep)
 					if err != nil {
 						return err
@@ -161,26 +161,17 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 					if healthy {
 						return nil
 					}
-				case types.ServiceConditionCompletedSuccessfully:
-					exited, code, err := s.isServiceCompleted(ctx, project, dep)
-					if err != nil {
-						return err
-					}
-					if exited {
-						if code != 0 {
-							return fmt.Errorf("service %q didn't completed successfully: exit %d", dep, code)
-						}
-						return nil
-					}
-				case types.ServiceConditionStarted:
-					// already managed by InDependencyOrder
-					return nil
-				default:
-					logrus.Warnf("unsupported depends_on condition: %s", config.Condition)
-					return nil
 				}
+			})
+		case "service_completed_successfully":
+			exit, err := s.waitCompleted(ctx, project, dep)
+			if err != nil {
+				return err
 			}
-		})
+			if exit != 0 {
+				return fmt.Errorf("service %q didn't completed successfully: exit %d", dep, exit)
+			}
+		}
 	}
 	return eg.Wait()
 }
@@ -369,21 +360,26 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
 	return true, nil
 }
 
-func (s *composeService) isServiceCompleted(ctx context.Context, project *types.Project, dep string) (bool, int, error) {
-	containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, dep)
+func (s *composeService) waitCompleted(ctx context.Context, project *types.Project, dep string) (int64, error) {
+	containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+		Filters: filters.NewArgs(
+			projectFilter(project.Name),
+			serviceFilter(dep),
+		),
+	})
 	if err != nil {
-		return false, 0, err
+		return 0, err
 	}
 	for _, c := range containers {
-		container, err := s.apiClient.ContainerInspect(ctx, c.ID)
-		if err != nil {
-			return false, 0, err
-		}
-		if container.State != nil && container.State.Status == "exited" {
-			return true, container.State.ExitCode, nil
+		wait, errors := s.apiClient.ContainerWait(ctx, c.ID, container.WaitConditionNextExit)
+		select {
+		case w := <-wait:
+			return w.StatusCode, nil
+		case err := <-errors:
+			return 0, err
 		}
 	}
-	return false, 0, nil
+	return 0, nil
 }
 
 func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
diff --git a/local/compose/create.go b/local/compose/create.go
index 996c4ff3..fc69216a 100644
--- a/local/compose/create.go
+++ b/local/compose/create.go
@@ -280,6 +280,14 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
 		return nil, nil, nil, err
 	}
 
+	shmSize := int64(0)
+	if service.ShmSize != "" {
+		shmSize, err = strconv.ParseInt(service.ShmSize, 10, 64)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	}
+
 	tmpfs := map[string]string{}
 	for _, t := range service.Tmpfs {
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
@@ -308,7 +316,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
 		IpcMode:        container.IpcMode(ipcmode),
 		ReadonlyRootfs: service.ReadOnly,
 		RestartPolicy:  getRestartPolicy(service),
-		ShmSize:        int64(service.ShmSize),
+		ShmSize:        shmSize,
 		Sysctls:        service.Sysctls,
 		PortBindings:   portBindings,
 		Resources:      resources,
diff --git a/local/compose/down.go b/local/compose/down.go
index 98184733..ac0efa03 100644
--- a/local/compose/down.go
+++ b/local/compose/down.go
@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 	"fmt"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -207,7 +208,12 @@ func (s *composeService) projectFromContainerLabels(containers Containers, proje
 }
 
 func loadProjectOptionsFromLabels(c moby.Container) (*cli.ProjectOptions, error) {
-	return cli.NewProjectOptions(strings.Split(c.Labels[configFilesLabel], ","),
+	var configFiles []string
+	relativePathConfigFiles := strings.Split(c.Labels[configFilesLabel], ",")
+	for _, c := range relativePathConfigFiles {
+		configFiles = append(configFiles, filepath.Base(c))
+	}
+	return cli.NewProjectOptions(configFiles,
 		cli.WithOsEnv,
 		cli.WithWorkingDirectory(c.Labels[workingDirLabel]),
 		cli.WithName(c.Labels[projectLabel]))
diff --git a/local/compose/images.go b/local/compose/images.go
index dd27cafc..c6f33a9d 100644
--- a/local/compose/images.go
+++ b/local/compose/images.go
@@ -24,7 +24,6 @@ import (
 
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
-	"github.com/docker/docker/errdefs"
 	"golang.org/x/sync/errgroup"
 
 	"github.com/docker/compose-cli/api/compose"
@@ -58,57 +57,52 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
 			imageIDs = append(imageIDs, c.ImageID)
 		}
 	}
-	images, err := s.getImages(ctx, imageIDs)
-	if err != nil {
-		return nil, err
-	}
-	summary := make([]compose.ImageSummary, len(containers))
-	for i, container := range containers {
-		img, ok := images[container.ImageID]
-		if !ok {
-			return nil, fmt.Errorf("failed to retrieve image for container %s", getCanonicalContainerName(container))
-		}
-
-		summary[i] = img
-		summary[i].ContainerName = getCanonicalContainerName(container)
-	}
-	return summary, nil
-}
 
-func (s *composeService) getImages(ctx context.Context, images []string) (map[string]compose.ImageSummary, error) {
-	summary := map[string]compose.ImageSummary{}
+	images := map[string]moby.ImageInspect{}
 	l := sync.Mutex{}
 	eg, ctx := errgroup.WithContext(ctx)
-	for _, img := range images {
+	for _, img := range imageIDs {
 		img := img
 		eg.Go(func() error {
 			inspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, img)
 			if err != nil {
-				if errdefs.IsNotFound(err) {
-					return nil
-				}
 				return err
 			}
-			tag := ""
-			repository := ""
-			if len(inspect.RepoTags) > 0 {
-
-				repotag := strings.Split(inspect.RepoTags[0], ":")
-				repository = repotag[0]
-				if len(repotag) > 1 {
-					tag = repotag[1]
-				}
-			}
 			l.Lock()
-			summary[img] = compose.ImageSummary{
-				ID:         inspect.ID,
-				Repository: repository,
-				Tag:        tag,
-				Size:       inspect.Size,
-			}
+			images[img] = inspect
 			l.Unlock()
 			return nil
 		})
 	}
-	return summary, eg.Wait()
+	err = eg.Wait()
+
+	if err != nil {
+		return nil, err
+	}
+	summary := make([]compose.ImageSummary, len(containers))
+	for i, container := range containers {
+		img, ok := images[container.ImageID]
+		if !ok {
+			return nil, fmt.Errorf("failed to retrieve image for container %s", getCanonicalContainerName(container))
+		}
+		if len(img.RepoTags) == 0 {
+			return nil, fmt.Errorf("no image tag found for %s", img.ID)
+		}
+		tag := ""
+		repository := ""
+		repotag := strings.Split(img.RepoTags[0], ":")
+		repository = repotag[0]
+		if len(repotag) > 1 {
+			tag = repotag[1]
+		}
+
+		summary[i] = compose.ImageSummary{
+			ID:            img.ID,
+			ContainerName: getCanonicalContainerName(container),
+			Repository:    repository,
+			Tag:           tag,
+			Size:          img.Size,
+		}
+	}
+	return summary, nil
 }
diff --git a/local/compose/remove.go b/local/compose/remove.go
index 713077d2..1018bc34 100644
--- a/local/compose/remove.go
+++ b/local/compose/remove.go
@@ -29,12 +29,7 @@ import (
 )
 
 func (s *composeService) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) ([]string, error) {
-	services := options.Services
-	if len(services) == 0 {
-		services = project.ServiceNames()
-	}
-
-	containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true, services...)
+	containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
 	if err != nil {
 		return nil, err
 	}
diff --git a/local/compose/stop.go b/local/compose/stop.go
index 0e950e44..b528312d 100644
--- a/local/compose/stop.go
+++ b/local/compose/stop.go
@@ -27,13 +27,8 @@ import (
 
 func (s *composeService) Stop(ctx context.Context, project *types.Project, options compose.StopOptions) error {
 	w := progress.ContextWriter(ctx)
-
-	services := options.Services
-	if len(services) == 0 {
-		services = project.ServiceNames()
-	}
 	var containers Containers
-	containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true, services...)
+	containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true, project.ServiceNames()...)
 	if err != nil {
 		return err
 	}
diff --git a/utils/hash.go b/utils/hash.go
index 2715f5b0..ee3dc4e2 100644
--- a/utils/hash.go
+++ b/utils/hash.go
@@ -26,9 +26,6 @@ import (
 // ServiceHash compute configuration has for a service
 // TODO move this to compose-go
 func ServiceHash(o types.ServiceConfig) (string, error) {
-	// remove the Build config when generating the service hash
-	o.Build = nil
-	o.PullPolicy = ""
 	bytes, err := json.Marshal(o)
 	if err != nil {
 		return "", err


Test patch:
diff --git a/local/compose/kill_test.go b/local/compose/kill_test.go
index 58b2ec97..0149b579 100644
--- a/local/compose/kill_test.go
+++ b/local/compose/kill_test.go
@@ -83,7 +83,7 @@ func testContainer(service string, id string) apitypes.Container {
 }
 
 func containerLabels(service string) map[string]string {
-	return map[string]string{serviceLabel: service, configFilesLabel: "docker-compose.yml", workingDirLabel: "testdata", projectLabel: testProject}
+	return map[string]string{serviceLabel: service, configFilesLabel: "testdata/docker-compose.yml", workingDirLabel: "testdata", projectLabel: testProject}
 }
 
 func anyCancellableContext() gomock.Matcher {
diff --git a/local/e2e/compose/compose_test.go b/local/e2e/compose/compose_test.go
index d62c5b3b..d65adc23 100644
--- a/local/e2e/compose/compose_test.go
+++ b/local/e2e/compose/compose_test.go
@@ -141,22 +141,6 @@ func TestComposePull(t *testing.T) {
 	assert.Assert(t, strings.Contains(output, "another Pulled"))
 }
 
-func TestDownComposefileInParentFolder(t *testing.T) {
-
-	c := NewParallelE2eCLI(t, binDir)
-
-	tmpFolder, err := os.MkdirTemp("fixtures/simple-composefile", "test-tmp")
-	projectName := strings.TrimPrefix(tmpFolder, "fixtures/simple-composefile/")
-	defer os.Remove(tmpFolder) //nolint: errcheck
-	assert.NilError(t, err)
-
-	res := c.RunDockerCmd("compose", "--project-directory", tmpFolder, "up", "-d")
-	res.Assert(t, icmd.Expected{Err: "Started", ExitCode: 0})
-
-	res = c.RunDockerCmd("compose", "-p", projectName, "down")
-	res.Assert(t, icmd.Expected{Err: "Removed", ExitCode: 0})
-}
-
 func TestAttachRestart(t *testing.T) {
 	c := NewParallelE2eCLI(t, binDir)