Repository: docker/compose

Issue:
mem_swappiness not working
<!--
Welcome to the docker-compose issue tracker! Before creating an issue, please heed the following:

1. This tracker should only be used to report bugs and request features / enhancements to docker-compose
    - For questions and general support, use https://forums.docker.com
    - For documentation issues, use https://github.com/docker/docker.github.io
    - For issues with the `docker stack` commands and the version 3 of the Compose file, use
      https://github.com/docker/cli
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
   the original discussion.
3. When making a bug report, make sure you provide all required information. The easier it is for
   maintainers to reproduce, the faster it'll be fixed.
-->

## Description of the issue

Docker compose file version 2.4.
Service config `mem_swappiness` has no effect.

## Context information (for bug reports)

**Output of `docker-compose version`**
```
docker-compose version 1.22.0, build f46880fe
docker-py version: 3.4.1
CPython version: 3.6.6
OpenSSL version: OpenSSL 1.1.0f  25 May 2017
```
/!\ If `docker-compose version` reports v2.0...` you're using the wrong repository, please report issues on [Compose-CLI](https://github.com/docker/compose-cli/issues/new)

**Output of `docker version`**
```
Client: Docker Engine - Community
 Version:           19.03.15
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        99e3ed8919
 Built:             Sat Jan 30 03:17:11 2021
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.15
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       99e3ed8919
  Built:            Sat Jan 30 03:15:40 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
```

**Output of `docker-compose config`**
(Make sure to add the relevant `-f` and other flags)
```
services:
   my-service;
     mem_swappiness: 75
```


## Steps to reproduce the issue

1. set mem_swappiness
2. run the service
3. docker exec -it my-service sh
4. cat /proc/sys/vm/swappiness

### Observed result
60

### Expected result
75

### Stacktrace / full error message

```
(paste here)
```

## Additional information

Linux li1401-73.members.linode.com 4.9.0-14-amd64 #1 SMP Debian 4.9.246-2 (2020-12-17) x86_64 GNU/Linux

docker info shows: `WARNING: No swap limit support`


Hints from issue comments:
@stale: This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
@stale: This issue has been automatically closed because it had not recent activity during the stale period.
@LinFor: Have the same issue. docker inspect shows `"MemorySwappiness": null,` while compose file contains `mem_swappiness: 100` for that service@Fs00: @glours @laurazard Sorry for the random tagging, but the bug is still present on Docker Compose version v2.12.1.
Can this issue be reopened and triaged? Thanks for your patience 🙏 @stale: This issue has been automatically marked as not stale anymore due to the recent activity.
@teresanovielloatinstana: Hello team, the bug is still present on Docker Compose version v2.20.3.
Can this issue be reopened and triaged? Thanks for your patience 🙏@mdeeter: I'm seeing this issue as well.@jsecrest: Still an issue in Docker Compose version v2.34.0-desktop.1 as well.@ryanjtang: does anyone have a fix for this?

Solution patch:
diff --git a/cmd/compose/run.go b/cmd/compose/run.go
index 9922d792..f2611cd6 100644
--- a/cmd/compose/run.go
+++ b/cmd/compose/run.go
@@ -113,6 +113,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
 			projectOptions: p,
 		},
 	}
+	createOpts := createOptions{}
 	cmd := &cobra.Command{
 		Use:   "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
 		Short: "Run a one-off command on a service.",
@@ -141,7 +142,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
 			}
 			ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
 			opts.ignoreOrphans = strings.ToLower(ignore) == "true"
-			return runRun(ctx, backend, project, opts)
+			return runRun(ctx, backend, project, opts, createOpts)
 		}),
 		ValidArgsFunction: completeServiceNames(p),
 	}
@@ -161,6 +162,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
 	flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
 	flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
 	flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
+	flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container.")
 
 	cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
 	cmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
@@ -181,12 +183,14 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
 	return pflag.NormalizedName(name)
 }
 
-func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions) error {
+func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions, createOpts createOptions) error {
 	err := opts.apply(project)
 	if err != nil {
 		return err
 	}
 
+	createOpts.Apply(project)
+
 	err = progress.Run(ctx, func(ctx context.Context) error {
 		return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
 	})
diff --git a/docs/reference/compose_run.md b/docs/reference/compose_run.md
index 01be4664..af8f135d 100644
--- a/docs/reference/compose_run.md
+++ b/docs/reference/compose_run.md
@@ -7,6 +7,7 @@ Run a one-off command on a service.
 
 | Name | Type | Default | Description |
 | --- | --- | --- | --- |
+| `--build` |  |  | Build image before starting container. |
 | `-d`, `--detach` |  |  | Run container in background and print container ID |
 | `--entrypoint` | `string` |  | Override the entrypoint of the image |
 | `-e`, `--env` | `stringArray` |  | Set environment variables |
diff --git a/docs/reference/docker_compose_run.yaml b/docs/reference/docker_compose_run.yaml
index 3783605b..9d5715f4 100644
--- a/docs/reference/docker_compose_run.yaml
+++ b/docs/reference/docker_compose_run.yaml
@@ -58,6 +58,16 @@ usage: docker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]
 pname: docker compose
 plink: docker_compose.yaml
 options:
+    - option: build
+      value_type: bool
+      default_value: "false"
+      description: Build image before starting container.
+      deprecated: false
+      hidden: false
+      experimental: false
+      experimentalcli: false
+      kubernetes: false
+      swarm: false
     - option: detach
       shorthand: d
       value_type: bool
diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go
index 71bbbe76..d81d40dd 100644
--- a/pkg/compose/convergence.go
+++ b/pkg/compose/convergence.go
@@ -226,10 +226,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
 		updated[i] = container
 	}
 
-	next, err := nextContainerNumber(containers)
-	if err != nil {
-		return err
-	}
+	next := nextContainerNumber(containers)
 	for i := 0; i < expected-actual; i++ {
 		// Scale UP
 		number := next + i
@@ -370,18 +367,23 @@ func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceD
 	return true, nil
 }
 
-func nextContainerNumber(containers []moby.Container) (int, error) {
+func nextContainerNumber(containers []moby.Container) int {
 	max := 0
 	for _, c := range containers {
-		n, err := strconv.Atoi(c.Labels[api.ContainerNumberLabel])
+		s, ok := c.Labels[api.ContainerNumberLabel]
+		if !ok {
+			logrus.Warnf("container %s is missing %s label", c.ID, api.ContainerNumberLabel)
+		}
+		n, err := strconv.Atoi(s)
 		if err != nil {
-			return 0, err
+			logrus.Warnf("container %s has invalid %s label: %s", c.ID, api.ContainerNumberLabel, s)
+			continue
 		}
 		if n > max {
 			max = n
 		}
 	}
-	return max + 1, nil
+	return max + 1
 
 }
 
@@ -403,7 +405,7 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro
 	w := progress.ContextWriter(ctx)
 	eventName := "Container " + name
 	w.Event(progress.CreatingEvent(eventName))
-	container, err = s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove, useNetworkAliases, attachStdin)
+	container, err = s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove, useNetworkAliases, attachStdin, w)
 	if err != nil {
 		return
 	}
@@ -437,7 +439,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 		inherited = &replaced
 	}
 	name = getContainerName(project.Name, service, number)
-	created, err = s.createMobyContainer(ctx, project, service, name, number, inherited, false, true, false)
+	created, err = s.createMobyContainer(ctx, project, service, name, number, inherited, false, true, false, w)
 	if err != nil {
 		return created, err
 	}
@@ -475,7 +477,7 @@ func (s *composeService) startContainer(ctx context.Context, container moby.Cont
 }
 
 func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
-	name string, number int, inherit *moby.Container, autoRemove bool, useNetworkAliases bool, attachStdin bool) (moby.Container, error) {
+	name string, number int, inherit *moby.Container, autoRemove bool, useNetworkAliases bool, attachStdin bool, w progress.Writer) (moby.Container, error) {
 	var created moby.Container
 	containerConfig, hostConfig, networkingConfig, err := s.getCreateOptions(ctx, project, service, number, inherit, autoRemove, attachStdin)
 	if err != nil {
@@ -494,6 +496,13 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
 	if err != nil {
 		return created, err
 	}
+	for _, warning := range response.Warnings {
+		w.Event(progress.Event{
+			ID:     service.Name,
+			Status: progress.Warning,
+			Text:   warning,
+		})
+	}
 	inspectedContainer, err := s.apiClient().ContainerInspect(ctx, response.ID)
 	if err != nil {
 		return created, err
diff --git a/pkg/compose/pull.go b/pkg/compose/pull.go
index 88cfefbf..80bb3b8a 100644
--- a/pkg/compose/pull.go
+++ b/pkg/compose/pull.go
@@ -110,7 +110,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 		imagesBeingPulled[service.Image] = service.Name
 
 		eg.Go(func() error {
-			_, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false)
+			_, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false, project.Environment["DOCKER_DEFAULT_PLATFORM"])
 			if err != nil {
 				if !opts.IgnoreFailures {
 					if service.Build != nil {
@@ -146,7 +146,8 @@ func imageAlreadyPresent(serviceImage string, localImages map[string]string) boo
 	return ok && tagged.Tag() != "latest"
 }
 
-func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPull bool) (string, error) {
+func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info,
+	configFile driver.Auth, w progress.Writer, quietPull bool, defaultPlatform string) (string, error) {
 	w.Event(progress.Event{
 		ID:     service.Name,
 		Status: progress.Working,
@@ -176,10 +177,14 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
 	if err != nil {
 		return "", err
 	}
+	platform := service.Platform
+	if platform == "" {
+		platform = defaultPlatform
+	}
 
 	stream, err := s.apiClient().ImagePull(ctx, service.Image, moby.ImagePullOptions{
 		RegistryAuth: base64.URLEncoding.EncodeToString(buf),
-		Platform:     service.Platform,
+		Platform:     platform,
 	})
 
 	// check if has error and the service has a build section
@@ -269,9 +274,9 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
 		for i, service := range needPull {
 			i, service := i, service
 			eg.Go(func() error {
-				id, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull)
+				id, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull, project.Environment["DOCKER_DEFAULT_PLATFORM"])
 				pulledImages[i] = id
-				if err != nil && service.Build != nil {
+				if err != nil && isServiceImageToBuild(service, project.Services) {
 					// image can be built, so we can ignore pull failure
 					return nil
 				}
@@ -291,6 +296,19 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
 	})
 }
 
+func isServiceImageToBuild(service types.ServiceConfig, services []types.ServiceConfig) bool {
+	if service.Build != nil {
+		return true
+	}
+
+	for _, depService := range services {
+		if depService.Image == service.Image && depService.Build != nil {
+			return true
+		}
+	}
+	return false
+}
+
 func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) {
 	if jm.ID == "" || jm.Progress == nil {
 		return
diff --git a/pkg/e2e/fixtures/dependencies/Dockerfile b/pkg/e2e/fixtures/dependencies/Dockerfile
new file mode 100644
index 00000000..fe5992a8
--- /dev/null
+++ b/pkg/e2e/fixtures/dependencies/Dockerfile
@@ -0,0 +1,16 @@
+#   Copyright 2020 Docker Compose CLI authors
+
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+
+#       http://www.apache.org/licenses/LICENSE-2.0
+
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+FROM busybox:1.35.0
+RUN echo "hello"
\ No newline at end of file
diff --git a/pkg/e2e/fixtures/dependencies/service-image-depends-on.yaml b/pkg/e2e/fixtures/dependencies/service-image-depends-on.yaml
new file mode 100644
index 00000000..31399783
--- /dev/null
+++ b/pkg/e2e/fixtures/dependencies/service-image-depends-on.yaml
@@ -0,0 +1,9 @@
+services:
+  foo:
+    image: built-image-dependency
+    build:
+      context: .
+  bar:
+    image: built-image-dependency
+    depends_on:
+      - foo



Test patch:
diff --git a/pkg/e2e/up_test.go b/pkg/e2e/up_test.go
index c86599c0..ecb4595b 100644
--- a/pkg/e2e/up_test.go
+++ b/pkg/e2e/up_test.go
@@ -104,3 +104,22 @@ func TestUpDependenciesNotStopped(t *testing.T) {
 	RequireServiceState(t, c, "dependency", "running")
 	RequireServiceState(t, c, "orphan", "running")
 }
+
+func TestUpWithBuildDependencies(t *testing.T) {
+	c := NewParallelCLI(t)
+
+	t.Run("up with service using image build by an another service", func(t *testing.T) {
+		// ensure local test run does not reuse previously build image
+		c.RunDockerOrExitError(t, "rmi", "built-image-dependency")
+
+		res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/dependencies",
+			"-f", "fixtures/dependencies/service-image-depends-on.yaml", "up", "-d")
+
+		t.Cleanup(func() {
+			c.RunDockerComposeCmd(t, "--project-directory", "fixtures/dependencies",
+				"-f", "fixtures/dependencies/service-image-depends-on.yaml", "down", "--rmi", "all")
+		})
+
+		res.Assert(t, icmd.Success)
+	})
+}