浏览代码

Merge branch 'v2' into 8768-avoid-pulling-same-image-multiple-times

Vedant Koditkar 3 年之前
父节点
当前提交
960453fa22
共有 100 个文件被更改,包括 2360 次插入1054 次删除
  1. 0 1
      .dockerignore
  2. 8 2
      .github/workflows/artifacts.yml
  3. 28 12
      .github/workflows/ci.yml
  4. 51 0
      .github/workflows/docs.yml
  5. 5 4
      .github/workflows/release.yaml
  6. 10 4
      .golangci.yml
  7. 1 1
      BUILDING.md
  8. 1 1
      CONTRIBUTING.md
  9. 8 2
      Dockerfile
  10. 26 2
      Makefile
  11. 1 4
      builder.Makefile
  12. 7 6
      cmd/compatibility/convert.go
  13. 15 0
      cmd/compatibility/convert_test.go
  14. 33 9
      cmd/compose/build.go
  15. 56 13
      cmd/compose/compose.go
  16. 4 2
      cmd/compose/cp.go
  17. 3 2
      cmd/compose/down.go
  18. 15 29
      cmd/compose/exec.go
  19. 24 5
      cmd/compose/kill.go
  20. 15 67
      cmd/compose/ps.go
  21. 84 0
      cmd/compose/ps_test.go
  22. 2 2
      cmd/compose/remove.go
  23. 36 10
      cmd/compose/run.go
  24. 5 2
      cmd/compose/up.go
  25. 1 1
      cmd/compose/version.go
  26. 2 2
      cmd/formatter/logs.go
  27. 3 8
      cmd/main.go
  28. 57 0
      docs/docs.Dockerfile
  29. 70 17
      docs/reference/compose.md
  30. 21 3
      docs/reference/compose_build.md
  31. 28 2
      docs/reference/compose_convert.md
  32. 16 0
      docs/reference/compose_cp.md
  33. 17 0
      docs/reference/compose_create.md
  34. 15 0
      docs/reference/compose_down.md
  35. 13 0
      docs/reference/compose_events.md
  36. 21 2
      docs/reference/compose_exec.md
  37. 14 0
      docs/reference/compose_images.md
  38. 13 0
      docs/reference/compose_kill.md
  39. 19 0
      docs/reference/compose_logs.md
  40. 16 0
      docs/reference/compose_ls.md
  41. 7 0
      docs/reference/compose_pause.md
  42. 14 0
      docs/reference/compose_port.md
  43. 109 3
      docs/reference/compose_ps.md
  44. 20 5
      docs/reference/compose_pull.md
  45. 13 0
      docs/reference/compose_push.md
  46. 19 3
      docs/reference/compose_restart.md
  47. 20 0
      docs/reference/compose_rm.md
  48. 36 8
      docs/reference/compose_run.md
  49. 7 0
      docs/reference/compose_start.md
  50. 13 0
      docs/reference/compose_stop.md
  51. 8 1
      docs/reference/compose_top.md
  52. 7 0
      docs/reference/compose_unpause.md
  53. 36 4
      docs/reference/compose_up.md
  54. 14 0
      docs/reference/compose_version.md
  55. 37 2
      docs/reference/docker_compose.yaml
  56. 21 1
      docs/reference/docker_compose_build.yaml
  57. 38 0
      docs/reference/docker_compose_convert.yaml
  58. 7 3
      docs/reference/docker_compose_cp.yaml
  59. 4 0
      docs/reference/docker_compose_create.yaml
  60. 4 0
      docs/reference/docker_compose_down.yaml
  61. 1 0
      docs/reference/docker_compose_events.yaml
  62. 30 1
      docs/reference/docker_compose_exec.yaml
  63. 1 0
      docs/reference/docker_compose_images.yaml
  64. 1 0
      docs/reference/docker_compose_kill.yaml
  65. 7 0
      docs/reference/docker_compose_logs.yaml
  66. 4 0
      docs/reference/docker_compose_ls.yaml
  67. 1 1
      docs/reference/docker_compose_pause.yaml
  68. 2 0
      docs/reference/docker_compose_port.yaml
  69. 103 6
      docs/reference/docker_compose_ps.yaml
  70. 46 0
      docs/reference/docker_compose_pull.yaml
  71. 1 0
      docs/reference/docker_compose_push.yaml
  72. 12 1
      docs/reference/docker_compose_restart.yaml
  73. 4 0
      docs/reference/docker_compose_rm.yaml
  74. 49 4
      docs/reference/docker_compose_run.yaml
  75. 1 0
      docs/reference/docker_compose_stop.yaml
  76. 1 1
      docs/reference/docker_compose_unpause.yaml
  77. 29 10
      docs/reference/docker_compose_up.yaml
  78. 2 0
      docs/reference/docker_compose_version.yaml
  79. 15 8
      docs/yaml/main/generate.go
  80. 75 66
      go.mod
  81. 118 217
      go.sum
  82. 15 14
      pkg/api/api.go
  83. 18 24
      pkg/api/proxy.go
  84. 11 9
      pkg/compose/attach.go
  85. 76 28
      pkg/compose/build.go
  86. 3 3
      pkg/compose/build_buildkit.go
  87. 22 22
      pkg/compose/build_classic.go
  88. 91 10
      pkg/compose/compose.go
  89. 35 20
      pkg/compose/containers.go
  90. 35 22
      pkg/compose/convergence.go
  91. 21 7
      pkg/compose/convergence_test.go
  92. 50 29
      pkg/compose/cp.go
  93. 124 104
      pkg/compose/create.go
  94. 0 9
      pkg/compose/create_test.go
  95. 3 3
      pkg/compose/dependencies.go
  96. 86 40
      pkg/compose/down.go
  97. 39 16
      pkg/compose/down_test.go
  98. 4 3
      pkg/compose/events.go
  99. 23 128
      pkg/compose/exec.go
  100. 3 3
      pkg/compose/images.go

+ 0 - 1
.dockerignore

@@ -1,3 +1,2 @@
-.git/
 bin/
 bin/
 dist/
 dist/

+ 8 - 2
.github/workflows/artifacts.yml

@@ -7,10 +7,10 @@ jobs:
     if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
     if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
-      - name: Set up Go 1.17
+      - name: Set up Go 1.18
         uses: actions/setup-go@v2
         uses: actions/setup-go@v2
         with:
         with:
-          go-version: 1.17
+          go-version: 1.18.3
         id: go
         id: go
 
 
       - name: Checkout code into the Go module directory
       - name: Checkout code into the Go module directory
@@ -42,6 +42,12 @@ jobs:
           name: docker-compose-linux-amd64
           name: docker-compose-linux-amd64
           path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
           path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
 
 
+      - name: Upload linux-ppc64le binary
+        uses: actions/upload-artifact@v2
+        with:
+          name: docker-compose-linux-ppc64le
+          path: ${{ github.workspace }}/bin/docker-compose-linux-ppc64le
+
       - name: Upload windows-amd64 binary
       - name: Upload windows-amd64 binary
         uses: actions/upload-artifact@v2
         uses: actions/upload-artifact@v2
         with:
         with:

+ 28 - 12
.github/workflows/ci.yml

@@ -5,6 +5,12 @@ on:
     branches:
     branches:
       - v2
       - v2
   pull_request:
   pull_request:
+  workflow_dispatch:
+    inputs:
+      debug_enabled:
+        description: 'To run with tmate enter "debug_enabled"'
+        required: false
+        default: "false"
 
 
 jobs:
 jobs:
   lint:
   lint:
@@ -13,16 +19,16 @@ jobs:
     env:
     env:
       GO111MODULE: "on"
       GO111MODULE: "on"
     steps:
     steps:
-      - name: Set up Go 1.17
+      - name: Set up Go 1.18
         uses: actions/setup-go@v2
         uses: actions/setup-go@v2
         with:
         with:
-          go-version: 1.17
+          go-version: 1.18.3
         id: go
         id: go
 
 
       - name: Checkout code into the Go module directory
       - name: Checkout code into the Go module directory
         uses: actions/checkout@v2
         uses: actions/checkout@v2
 
 
-      - name: Validate go-mod is up-to-date and license headers
+      - name: Validate go-mod, license headers and docs are up-to-date
         run: make validate
         run: make validate
 
 
       - name: Run golangci-lint
       - name: Run golangci-lint
@@ -40,10 +46,10 @@ jobs:
     env:
     env:
       GO111MODULE: "on"
       GO111MODULE: "on"
     steps:
     steps:
-      - name: Set up Go 1.17
+      - name: Set up Go 1.18
         uses: actions/setup-go@v2
         uses: actions/setup-go@v2
         with:
         with:
-          go-version: 1.17
+          go-version: 1.18.3
         id: go
         id: go
 
 
       - name: Checkout code into the Go module directory
       - name: Checkout code into the Go module directory
@@ -65,10 +71,10 @@ jobs:
     env:
     env:
       GO111MODULE: "on"
       GO111MODULE: "on"
     steps:
     steps:
-      - name: Set up Go 1.17
+      - name: Set up Go 1.18
         uses: actions/setup-go@v2
         uses: actions/setup-go@v2
         with:
         with:
-          go-version: 1.17
+          go-version: 1.18.3
         id: go
         id: go
 
 
       - name: Setup docker CLI
       - name: Setup docker CLI
@@ -90,7 +96,7 @@ jobs:
       - name: Build for local E2E
       - name: Build for local E2E
         env:
         env:
           BUILD_TAGS: e2e
           BUILD_TAGS: e2e
-        run: make -f builder.Makefile compose-plugin
+        run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
 
 
       - name: E2E Test in plugin mode
       - name: E2E Test in plugin mode
         run: make e2e-compose
         run: make e2e-compose
@@ -101,10 +107,10 @@ jobs:
     env:
     env:
       GO111MODULE: "on"
       GO111MODULE: "on"
     steps:
     steps:
-      - name: Set up Go 1.17
+      - name: Set up Go 1.18
         uses: actions/setup-go@v2
         uses: actions/setup-go@v2
         with:
         with:
-          go-version: 1.17
+          go-version: 1.18.3
         id: go
         id: go
 
 
       - name: Setup docker CLI
       - name: Setup docker CLI
@@ -123,7 +129,17 @@ jobs:
       - name: Build for local E2E
       - name: Build for local E2E
         env:
         env:
           BUILD_TAGS: e2e
           BUILD_TAGS: e2e
-        run: make -f builder.Makefile compose-plugin
+        run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
+
+      - name: Setup tmate session
+        uses: mxschmitt/action-tmate@v3
+        with:
+          limit-access-to-actor: true
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+        if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
 
 
       - name: E2E Test in standalone mode
       - name: E2E Test in standalone mode
-        run: make e2e-compose-standalone
+        run: |
+          rm -f /usr/local/bin/docker-compose
+          cp bin/docker-compose /usr/local/bin
+          make e2e-compose-standalone

+ 51 - 0
.github/workflows/docs.yml

@@ -0,0 +1,51 @@
+name: Docs
+
+on:
+  release:
+    types: [published]
+
+jobs:
+  open-pr:
+    runs-on: ubuntu-latest
+    steps:
+      -
+        name: Checkout docs repo
+        uses: actions/checkout@v3
+        with:
+          token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
+          repository: docker/docker.github.io
+          ref: master
+      -
+        name: Prepare
+        run: |
+          rm -rf ./_data/compose-cli/*
+      -
+        name: Build
+        uses: docker/build-push-action@v3
+        with:
+          context: ${{ github.server_url }}/${{ github.repository }}.git#${{ github.event.release.name }}
+          target: docs-reference
+          outputs: ./_data/compose-cli
+      -
+        name: Update compose_version in _config.yml
+        run: |
+          sed -i "s|^compose_version\:.*|compose_version\: \"${{ github.event.release.name }}\"|g" _config.yml
+          cat _config.yml | yq .compose_version
+      -
+        name: Commit changes
+        run: |
+          git add -A .
+      -
+        name: Create PR on docs repo
+        uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 # v4.0.4
+        with:
+          token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
+          commit-message: Update Compose reference API to ${{ github.event.release.name }}
+          signoff: true
+          branch: dispatch/compose-api-reference-${{ github.event.release.name }}
+          delete-branch: true
+          title: Update Compose reference API to ${{ github.event.release.name }}
+          body: |
+            Update the Compose reference API documentation to keep in sync with the latest release `${{ github.event.release.name }}`
+          labels: area/Compose
+          draft: false

+ 5 - 4
.github/workflows/release.yaml

@@ -11,10 +11,10 @@ jobs:
   upload-release:
   upload-release:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
-      - name: Set up Go 1.17
+      - name: Set up Go 1.18
         uses: actions/setup-go@v2
         uses: actions/setup-go@v2
         with:
         with:
-          go-version: 1.17
+          go-version: 1.18.3
         id: go
         id: go
 
 
       - name: Setup docker CLI
       - name: Setup docker CLI
@@ -36,7 +36,7 @@ jobs:
         run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
         run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
 
 
       - name: Compute checksums
       - name: Compute checksums
-        run: cd bin; for f in *; do shasum --algorithm 256 $f > $f.sha256; done
+        run: cd bin; for f in *; do shasum --binary --algorithm 256 $f | tee -a checksums.txt > $f.sha256; done
 
 
       - name: License
       - name: License
         run: cp packaging/* bin/
         run: cp packaging/* bin/
@@ -44,7 +44,8 @@ jobs:
       - uses: ncipollo/release-action@v1
       - uses: ncipollo/release-action@v1
         with:
         with:
           artifacts: "bin/*"
           artifacts: "bin/*"
-          prerelease: true
+          generateReleaseNotes: true
+          draft: true
           commit: "v2"
           commit: "v2"
           token: ${{ secrets.GITHUB_TOKEN }}
           token: ${{ secrets.GITHUB_TOKEN }}
           tag: ${{ github.event.inputs.tag }}
           tag: ${{ github.event.inputs.tag }}

+ 10 - 4
.golangci.yml

@@ -1,12 +1,11 @@
+run:
+  concurrency: 2
 linters:
 linters:
-  run:
-    concurrency: 2
-    skip-dirs:
-      - tests/composefiles
   enable-all: false
   enable-all: false
   disable-all: true
   disable-all: true
   enable:
   enable:
     - deadcode
     - deadcode
+    - depguard
     - errcheck
     - errcheck
     - gocyclo
     - gocyclo
     - gofmt
     - gofmt
@@ -26,6 +25,13 @@ linters:
     - unused
     - unused
     - varcheck
     - varcheck
 linters-settings:
 linters-settings:
+  depguard:
+    list-type: blacklist
+    include-go-root: true
+    packages:
+      # The io/ioutil package has been deprecated.
+      # https://go.dev/doc/go1.16#ioutil
+      - io/ioutil
   gocyclo:
   gocyclo:
     min-complexity: 16
     min-complexity: 16
   lll:
   lll:

+ 1 - 1
BUILDING.md

@@ -8,7 +8,7 @@
   * [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
   * [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
   * make
   * make
 * Linux:
 * Linux:
-  * [Docker 19.03 or later](https://docs.docker.com/engine/install/)
+  * [Docker 20.10 or later](https://docs.docker.com/engine/install/)
   * make
   * make
 
 
 ### Building the CLI
 ### Building the CLI

+ 1 - 1
CONTRIBUTING.md

@@ -83,7 +83,7 @@ don't get discouraged! Our contributor's guide explains
   <tr>
   <tr>
     <td>Community Slack</td>
     <td>Community Slack</td>
     <td>
     <td>
-      The Docker Community has a dedicated Slack chat to discuss features and issues.  You can sign-up <a href="https://dockercommunity.slack.com/ssb/redirect" target="_blank">with this link</a>.
+      The Docker Community has a dedicated Slack chat to discuss features and issues.  You can sign-up <a href="https://www.docker.com/docker-community" target="_blank">with this link</a>.
     </td>
     </td>
   </tr>
   </tr>
   <tr>
   <tr>

+ 8 - 2
Dockerfile

@@ -15,7 +15,7 @@
 #   See the License for the specific language governing permissions and
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 #   limitations under the License.
 
 
-ARG GO_VERSION=1.17-alpine
+ARG GO_VERSION=1.18.3-alpine
 ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
 ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
 ARG PROTOC_GEN_GO_VERSION=v1.4.3
 ARG PROTOC_GEN_GO_VERSION=v1.4.3
 
 
@@ -88,7 +88,7 @@ RUN --mount=target=. \
     make -f builder.Makefile test
     make -f builder.Makefile test
 
 
 FROM base AS check-license-headers
 FROM base AS check-license-headers
-RUN go get -u github.com/kunalkushwaha/ltag
+RUN go install github.com/kunalkushwaha/ltag@latest
 RUN --mount=target=. \
 RUN --mount=target=. \
     make -f builder.Makefile check-license-headers
     make -f builder.Makefile check-license-headers
 
 
@@ -105,3 +105,9 @@ COPY --from=make-go-mod-tidy /compose-cli/go.sum .
 FROM base AS check-go-mod
 FROM base AS check-go-mod
 COPY . .
 COPY . .
 RUN make -f builder.Makefile check-go-mod
 RUN make -f builder.Makefile check-go-mod
+
+# docs-reference is a target used as remote context to update docs on release
+# with latest changes on docker.github.io.
+# see open-pr job in .github/workflows/docs.yml for more details
+FROM scratch AS docs-reference
+COPY docs/reference/*.yaml .

+ 26 - 2
Makefile

@@ -43,12 +43,19 @@ compose-plugin: ## Compile the compose cli-plugin
 
 
 .PHONY: e2e-compose
 .PHONY: e2e-compose
 e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
 e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
+	docker compose version
 	go test $(TEST_FLAGS) -count=1 ./pkg/e2e
 	go test $(TEST_FLAGS) -count=1 ./pkg/e2e
 
 
 .PHONY: e2e-compose-standalone
 .PHONY: e2e-compose-standalone
 e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
 e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
-	go test $(TEST_FLAGS) -count=1 --tags=standalone ./pkg/e2e
+	docker-compose version
+	go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
 
 
+.PHONY: mocks
+mocks:
+	mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
+	mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
+	mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service
 
 
 .PHONY: e2e
 .PHONY: e2e
 e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
 e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
@@ -78,6 +85,23 @@ lint: ## run linter(s)
 	--build-arg GIT_TAG=$(GIT_TAG) \
 	--build-arg GIT_TAG=$(GIT_TAG) \
 	--target lint
 	--target lint
 
 
+.PHONY: docs
+docs: ## generate documentation
+	$(eval $@_TMP_OUT := $(shell mktemp -d -t dockercli-output.XXXXXXXXXX))
+	docker build . \
+	--output type=local,dest=$($@_TMP_OUT) \
+	-f ./docs/docs.Dockerfile \
+	--target update
+	rm -rf ./docs/internal
+	cp -R "$($@_TMP_OUT)"/out/* ./docs/
+	rm -rf "$($@_TMP_OUT)"/*
+
+.PHONY: validate-docs
+validate-docs: ## validate the doc does not change
+	@docker build . \
+	-f ./docs/docs.Dockerfile \
+	--target validate
+
 .PHONY: check-dependencies
 .PHONY: check-dependencies
 check-dependencies: ## check dependency updates
 check-dependencies: ## check dependency updates
 	go list -u -m -f '{{if not .Indirect}}{{if .Update}}{{.}}{{end}}{{end}}' all
 	go list -u -m -f '{{if not .Indirect}}{{if .Update}}{{.}}{{end}}{{end}}' all
@@ -94,7 +118,7 @@ go-mod-tidy: ## Run go mod tidy in a container and output resulting go.mod and g
 validate-go-mod: ## Validate go.mod and go.sum are up-to-date
 validate-go-mod: ## Validate go.mod and go.sum are up-to-date
 	@docker build . --target check-go-mod
 	@docker build . --target check-go-mod
 
 
-validate: validate-go-mod validate-headers ## Validate sources
+validate: validate-go-mod validate-headers validate-docs ## Validate sources
 
 
 pre-commit: validate check-dependencies lint compose-plugin test e2e-compose
 pre-commit: validate check-dependencies lint compose-plugin test e2e-compose
 
 

+ 1 - 4
builder.Makefile

@@ -47,6 +47,7 @@ compose-plugin:
 .PHONY: cross
 .PHONY: cross
 cross:
 cross:
 	GOOS=linux   GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
 	GOOS=linux   GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
+	GOOS=linux   GOARCH=ppc64le $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-ppc64le ./cmd
 	GOOS=linux   GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
 	GOOS=linux   GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
 	GOOS=linux   GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
 	GOOS=linux   GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
 	GOOS=linux   GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
 	GOOS=linux   GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
@@ -70,7 +71,3 @@ check-license-headers:
 .PHONY: check-go-mod
 .PHONY: check-go-mod
 check-go-mod:
 check-go-mod:
 	./scripts/validate/check-go-mod
 	./scripts/validate/check-go-mod
-
-.PHONY: yamldocs
-yamldocs:
-	go run docs/yaml/main/generate.go

+ 7 - 6
cmd/compatibility/convert.go

@@ -50,7 +50,7 @@ func Convert(args []string) []string {
 	l := len(args)
 	l := len(args)
 	for i := 0; i < l; i++ {
 	for i := 0; i < l; i++ {
 		arg := args[i]
 		arg := args[i]
-		if arg[0] != '-' {
+		if len(arg) > 0 && arg[0] != '-' {
 			// not a top-level flag anymore, keep the rest of the command unmodified
 			// not a top-level flag anymore, keep the rest of the command unmodified
 			if arg == compose.PluginName {
 			if arg == compose.PluginName {
 				i++
 				i++
@@ -58,17 +58,18 @@ func Convert(args []string) []string {
 			command = append(command, args[i:]...)
 			command = append(command, args[i:]...)
 			break
 			break
 		}
 		}
-		if arg == "--verbose" {
+
+		switch arg {
+		case "--verbose":
 			arg = "--debug"
 			arg = "--debug"
-		}
-		if arg == "-h" {
+		case "-h":
 			// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
 			// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
 			arg = "--help"
 			arg = "--help"
-		}
-		if arg == "--version" || arg == "-v" {
+		case "--version", "-v":
 			// redirect --version pseudo-command to actual command
 			// redirect --version pseudo-command to actual command
 			arg = "version"
 			arg = "version"
 		}
 		}
+
 		if contains(getBoolFlags(), arg) {
 		if contains(getBoolFlags(), arg) {
 			rootFlags = append(rootFlags, arg)
 			rootFlags = append(rootFlags, arg)
 			continue
 			continue

+ 15 - 0
cmd/compatibility/convert_test.go

@@ -43,11 +43,21 @@ func Test_convert(t *testing.T) {
 			args: []string{"--host", "tcp://1.2.3.4", "up"},
 			args: []string{"--host", "tcp://1.2.3.4", "up"},
 			want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
 			want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
 		},
 		},
+		{
+			name: "compose --verbose",
+			args: []string{"--verbose"},
+			want: []string{"--debug", "compose"},
+		},
 		{
 		{
 			name: "compose --version",
 			name: "compose --version",
 			args: []string{"--version"},
 			args: []string{"--version"},
 			want: []string{"compose", "version"},
 			want: []string{"compose", "version"},
 		},
 		},
+		{
+			name: "compose -v",
+			args: []string{"-v"},
+			want: []string{"compose", "version"},
+		},
 		{
 		{
 			name: "help",
 			name: "help",
 			args: []string{"-h"},
 			args: []string{"-h"},
@@ -68,6 +78,11 @@ func Test_convert(t *testing.T) {
 			args: []string{"--log-level", "INFO", "up"},
 			args: []string{"--log-level", "INFO", "up"},
 			want: []string{"--log-level", "INFO", "compose", "up"},
 			want: []string{"--log-level", "INFO", "compose", "up"},
 		},
 		},
+		{
+			name: "empty string argument",
+			args: []string{"--project-directory", "", "ps"},
+			want: []string{"compose", "--project-directory", "", "ps"},
+		},
 	}
 	}
 	for _, tt := range tests {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {

+ 33 - 9
cmd/compose/build.go

@@ -23,6 +23,7 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/compose-spec/compose-go/cli"
 	"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/types"
 	buildx "github.com/docker/buildx/util/progress"
 	buildx "github.com/docker/buildx/util/progress"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
@@ -40,6 +41,28 @@ type buildOptions struct {
 	args     []string
 	args     []string
 	noCache  bool
 	noCache  bool
 	memory   string
 	memory   string
+	ssh      string
+}
+
+func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
+	var SSHKeys []types.SSHKey
+	var err error
+	if opts.ssh != "" {
+		SSHKeys, err = loader.ParseShortSSHSyntax(opts.ssh)
+		if err != nil {
+			return api.BuildOptions{}, err
+		}
+	}
+
+	return api.BuildOptions{
+		Pull:     opts.pull,
+		Progress: opts.progress,
+		Args:     types.NewMappingWithEquals(opts.args),
+		NoCache:  opts.noCache,
+		Quiet:    opts.quiet,
+		Services: services,
+		SSHs:     SSHKeys,
+	}, nil
 }
 }
 
 
 var printerModes = []string{
 var printerModes = []string{
@@ -73,7 +96,10 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
 			}
 			}
 			return nil
 			return nil
 		}),
 		}),
-		RunE: Adapt(func(ctx context.Context, args []string) error {
+		RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
+			if cmd.Flags().Changed("ssh") && opts.ssh == "" {
+				opts.ssh = "default"
+			}
 			return runBuild(ctx, backend, opts, args)
 			return runBuild(ctx, backend, opts, args)
 		}),
 		}),
 		ValidArgsFunction: serviceCompletion(p),
 		ValidArgsFunction: serviceCompletion(p),
@@ -82,6 +108,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
 	cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
 	cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
 	cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
 	cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
 	cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
 	cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
+	cmd.Flags().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
 	cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
 	cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
 	cmd.Flags().MarkHidden("parallel") //nolint:errcheck
 	cmd.Flags().MarkHidden("parallel") //nolint:errcheck
 	cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
 	cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
@@ -103,12 +130,9 @@ func runBuild(ctx context.Context, backend api.Service, opts buildOptions, servi
 		return err
 		return err
 	}
 	}
 
 
-	return backend.Build(ctx, project, api.BuildOptions{
-		Pull:     opts.pull,
-		Progress: opts.progress,
-		Args:     types.NewMappingWithEquals(opts.args),
-		NoCache:  opts.noCache,
-		Quiet:    opts.quiet,
-		Services: services,
-	})
+	apiBuildOptions, err := opts.toAPIBuildOptions(services)
+	if err != nil {
+		return err
+	}
+	return backend.Build(ctx, project, apiBuildOptions)
 }
 }

+ 56 - 13
cmd/compose/compose.go

@@ -27,17 +27,21 @@ import (
 
 
 	"github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
+	composegoutils "github.com/compose-spec/compose-go/utils"
 	dockercli "github.com/docker/cli/cli"
 	dockercli "github.com/docker/cli/cli"
 	"github.com/docker/cli/cli-plugins/manager"
 	"github.com/docker/cli/cli-plugins/manager"
-	"github.com/docker/compose/v2/cmd/formatter"
+	"github.com/docker/cli/cli/command"
 	"github.com/morikuni/aec"
 	"github.com/morikuni/aec"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
 
 
+	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/docker/compose/v2/pkg/compose"
+	"github.com/docker/compose/v2/pkg/progress"
+	"github.com/docker/compose/v2/pkg/utils"
 )
 )
 
 
 // Command defines a compose CLI command as a func with args
 // Command defines a compose CLI command as a func with args
@@ -86,9 +90,6 @@ func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
 	})
 	})
 }
 }
 
 
-// Warning is a global warning to be displayed to user on command failure
-var Warning string
-
 type projectOptions struct {
 type projectOptions struct {
 	ProjectName   string
 	ProjectName   string
 	Profiles      []string
 	Profiles      []string
@@ -129,8 +130,8 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
 	f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
 	f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
 	f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
 	f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
 	f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
 	f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
-	f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the Compose file)")
-	f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the Compose file)")
+	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.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
 	_ = f.MarkHidden("workdir")
 	_ = f.MarkHidden("workdir")
 }
 }
@@ -140,6 +141,11 @@ func (o *projectOptions) toProjectName() (string, error) {
 		return o.ProjectName, nil
 		return o.ProjectName, nil
 	}
 	}
 
 
+	envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
+	if envProjectName != "" {
+		return envProjectName, nil
+	}
+
 	project, err := o.toProject(nil)
 	project, err := o.toProject(nil)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
@@ -158,13 +164,16 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
 		return nil, compose.WrapComposeError(err)
 		return nil, compose.WrapComposeError(err)
 	}
 	}
 
 
-	if o.Compatibility || project.Environment["COMPOSE_COMPATIBILITY"] == "true" {
+	if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
 		compose.Separator = "_"
 		compose.Separator = "_"
 	}
 	}
 
 
 	ef := o.EnvFile
 	ef := o.EnvFile
 	if ef != "" && !filepath.IsAbs(ef) {
 	if ef != "" && !filepath.IsAbs(ef) {
-		ef = filepath.Join(project.WorkingDir, o.EnvFile)
+		ef, err = filepath.Abs(ef)
+		if err != nil {
+			return nil, err
+		}
 	}
 	}
 	for i, s := range project.Services {
 	for i, s := range project.Services {
 		s.CustomLabels = map[string]string{
 		s.CustomLabels = map[string]string{
@@ -205,9 +214,9 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
 	return cli.NewProjectOptions(o.ConfigPaths,
 	return cli.NewProjectOptions(o.ConfigPaths,
 		append(po,
 		append(po,
 			cli.WithWorkingDirectory(o.ProjectDir),
 			cli.WithWorkingDirectory(o.ProjectDir),
+			cli.WithOsEnv,
 			cli.WithEnvFile(o.EnvFile),
 			cli.WithEnvFile(o.EnvFile),
 			cli.WithDotEnv,
 			cli.WithDotEnv,
-			cli.WithOsEnv,
 			cli.WithConfigFileEnv,
 			cli.WithConfigFileEnv,
 			cli.WithDefaultConfigPath,
 			cli.WithDefaultConfigPath,
 			cli.WithName(o.ProjectName))...)
 			cli.WithName(o.ProjectName))...)
@@ -222,7 +231,7 @@ func RunningAsStandalone() bool {
 }
 }
 
 
 // RootCommand returns the compose command with its child commands
 // RootCommand returns the compose command with its child commands
-func RootCommand(backend api.Service) *cobra.Command {
+func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := projectOptions{}
 	opts := projectOptions{}
 	var (
 	var (
 		ansi    string
 		ansi    string
@@ -249,6 +258,10 @@ func RootCommand(backend api.Service) *cobra.Command {
 			}
 			}
 		},
 		},
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			err := setEnvWithDotEnv(&opts)
+			if err != nil {
+				return err
+			}
 			parent := cmd.Root()
 			parent := cmd.Root()
 			if parent != nil {
 			if parent != nil {
 				parentPrerun := parent.PersistentPreRunE
 				parentPrerun := parent.PersistentPreRunE
@@ -264,12 +277,18 @@ func RootCommand(backend api.Service) *cobra.Command {
 					return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
 					return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
 				}
 				}
 				ansi = "never"
 				ansi = "never"
-				fmt.Fprint(os.Stderr, aec.Apply("option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n", aec.RedF))
+				fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n")
 			}
 			}
 			if verbose {
 			if verbose {
 				logrus.SetLevel(logrus.TraceLevel)
 				logrus.SetLevel(logrus.TraceLevel)
 			}
 			}
 			formatter.SetANSIMode(ansi)
 			formatter.SetANSIMode(ansi)
+			switch ansi {
+			case "never":
+				progress.Mode = progress.ModePlain
+			case "tty":
+				progress.Mode = progress.ModeTTY
+			}
 			if opts.WorkDir != "" {
 			if opts.WorkDir != "" {
 				if opts.ProjectDir != "" {
 				if opts.ProjectDir != "" {
 					return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
 					return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
@@ -292,9 +311,9 @@ func RootCommand(backend api.Service) *cobra.Command {
 		logsCommand(&opts, backend),
 		logsCommand(&opts, backend),
 		convertCommand(&opts, backend),
 		convertCommand(&opts, backend),
 		killCommand(&opts, backend),
 		killCommand(&opts, backend),
-		runCommand(&opts, backend),
+		runCommand(&opts, dockerCli, backend),
 		removeCommand(&opts, backend),
 		removeCommand(&opts, backend),
-		execCommand(&opts, backend),
+		execCommand(&opts, dockerCli, backend),
 		pauseCommand(&opts, backend),
 		pauseCommand(&opts, backend),
 		unpauseCommand(&opts, backend),
 		unpauseCommand(&opts, backend),
 		topCommand(&opts, backend),
 		topCommand(&opts, backend),
@@ -319,3 +338,27 @@ func RootCommand(backend api.Service) *cobra.Command {
 	command.Flags().MarkHidden("verbose") //nolint:errcheck
 	command.Flags().MarkHidden("verbose") //nolint:errcheck
 	return command
 	return command
 }
 }
+
+func setEnvWithDotEnv(prjOpts *projectOptions) error {
+	options, err := prjOpts.toProjectOptions()
+	if err != nil {
+		return compose.WrapComposeError(err)
+	}
+	workingDir, err := options.GetWorkingDir()
+	if err != nil {
+		return err
+	}
+
+	envFromFile, err := cli.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFile)
+	if err != nil {
+		return err
+	}
+	for k, v := range envFromFile {
+		if _, ok := os.LookupEnv(k); !ok {
+			if err = os.Setenv(k, v); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}

+ 4 - 2
cmd/compose/cp.go

@@ -55,7 +55,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
 			}
 			}
 			return nil
 			return nil
 		}),
 		}),
-		RunE: Adapt(func(ctx context.Context, args []string) error {
+		RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
 			opts.source = args[0]
 			opts.source = args[0]
 			opts.destination = args[1]
 			opts.destination = args[1]
 			return runCopy(ctx, backend, opts)
 			return runCopy(ctx, backend, opts)
@@ -64,8 +64,10 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
 	}
 	}
 
 
 	flags := copyCmd.Flags()
 	flags := copyCmd.Flags()
-	flags.IntVar(&opts.index, "index", 1, "Index of the container if there are multiple instances of a service [default: 1].")
+	flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")
 	flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.")
 	flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.")
+	flags.MarkHidden("all")                                                                                                      //nolint:errcheck
+	flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
 	flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
 	flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
 	flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
 	flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
 
 

+ 3 - 2
cmd/compose/down.go

@@ -20,10 +20,10 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
-	"strings"
 	"time"
 	"time"
 
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
@@ -59,10 +59,11 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
 		RunE: Adapt(func(ctx context.Context, args []string) error {
 		RunE: Adapt(func(ctx context.Context, args []string) error {
 			return runDown(ctx, backend, opts)
 			return runDown(ctx, backend, opts)
 		}),
 		}),
+		Args:              cobra.NoArgs,
 		ValidArgsFunction: noCompletion(),
 		ValidArgsFunction: noCompletion(),
 	}
 	}
 	flags := downCmd.Flags()
 	flags := downCmd.Flags()
-	removeOrphans := strings.ToLower(os.Getenv("COMPOSE_REMOVE_ORPHANS ")) == "true"
+	removeOrphans := utils.StringToBool(os.Getenv("COMPOSE_REMOVE_ORPHANS"))
 	flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
 	flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
 	flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
 	flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
 	flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
 	flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")

+ 15 - 29
cmd/compose/exec.go

@@ -18,12 +18,10 @@ package compose
 
 
 import (
 import (
 	"context"
 	"context"
-	"fmt"
-	"os"
 
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
-	"github.com/containerd/console"
 	"github.com/docker/cli/cli"
 	"github.com/docker/cli/cli"
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
@@ -37,14 +35,15 @@ type execOpts struct {
 	environment []string
 	environment []string
 	workingDir  string
 	workingDir  string
 
 
-	noTty      bool
-	user       string
-	detach     bool
-	index      int
-	privileged bool
+	noTty       bool
+	user        string
+	detach      bool
+	index       int
+	privileged  bool
+	interactive bool
 }
 }
 
 
-func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
+func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := execOpts{
 	opts := execOpts{
 		composeOptions: &composeOptions{
 		composeOptions: &composeOptions{
 			projectOptions: p,
 			projectOptions: p,
@@ -70,9 +69,14 @@ func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
 	runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
 	runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
 	runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
 	runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
 	runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
 	runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
-	runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", false, "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
+	runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
 	runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
 	runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
 
 
+	runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
+	runCmd.Flags().MarkHidden("interactive") //nolint:errcheck
+	runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
+	runCmd.Flags().MarkHidden("tty") //nolint:errcheck
+
 	runCmd.Flags().SetInterspersed(false)
 	runCmd.Flags().SetInterspersed(false)
 	return runCmd
 	return runCmd
 }
 }
@@ -100,27 +104,9 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
 		Index:       opts.index,
 		Index:       opts.index,
 		Detach:      opts.detach,
 		Detach:      opts.detach,
 		WorkingDir:  opts.workingDir,
 		WorkingDir:  opts.workingDir,
-
-		Stdin:  os.Stdin,
-		Stdout: os.Stdout,
-		Stderr: os.Stderr,
+		Interactive: opts.interactive,
 	}
 	}
 
 
-	if execOpts.Tty {
-		con := console.Current()
-		if err := con.SetRaw(); err != nil {
-			return err
-		}
-		defer func() {
-			if err := con.Reset(); err != nil {
-				fmt.Println("Unable to close the console")
-			}
-		}()
-
-		execOpts.Stdin = con
-		execOpts.Stdout = con
-		execOpts.Stderr = con
-	}
 	exitCode, err := backend.Exec(ctx, projectName, execOpts)
 	exitCode, err := backend.Exec(ctx, projectName, execOpts)
 	if exitCode != 0 {
 	if exitCode != 0 {
 		errMsg := ""
 		errMsg := ""

+ 24 - 5
cmd/compose/kill.go

@@ -19,25 +19,44 @@ package compose
 import (
 import (
 	"context"
 	"context"
 
 
-	"github.com/compose-spec/compose-go/types"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 )
 )
 
 
+type killOptions struct {
+	*projectOptions
+	signal string
+}
+
 func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
 func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
-	var opts api.KillOptions
+	opts := killOptions{
+		projectOptions: p,
+	}
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:   "kill [options] [SERVICE...]",
 		Use:   "kill [options] [SERVICE...]",
 		Short: "Force stop service containers.",
 		Short: "Force stop service containers.",
-		RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
-			return backend.Kill(ctx, project, opts)
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runKill(ctx, backend, opts, args)
 		}),
 		}),
 		ValidArgsFunction: serviceCompletion(p),
 		ValidArgsFunction: serviceCompletion(p),
 	}
 	}
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
-	flags.StringVarP(&opts.Signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
+	flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
 
 
 	return cmd
 	return cmd
 }
 }
+
+func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
+	projectName, err := opts.toProjectName()
+	if err != nil {
+		return err
+	}
+
+	return backend.Kill(ctx, projectName, api.KillOptions{
+		Services: services,
+		Signal:   opts.signal,
+	})
+
+}

+ 15 - 67
cmd/compose/ps.go

@@ -27,6 +27,7 @@ import (
 
 
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
+	"github.com/docker/docker/api/types"
 
 
 	formatter2 "github.com/docker/cli/cli/command/formatter"
 	formatter2 "github.com/docker/cli/cli/command/formatter"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
@@ -81,12 +82,11 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
 	}
 	}
 	flags := psCmd.Flags()
 	flags := psCmd.Flags()
 	flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
 	flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
-	flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property. Deprecated, use --status instead")
+	flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).")
 	flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
 	flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
 	flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
 	flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
 	flags.BoolVar(&opts.Services, "services", false, "Display services")
 	flags.BoolVar(&opts.Services, "services", false, "Display services")
 	flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)")
 	flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)")
-	flags.Lookup("filter").Hidden = true
 	return psCmd
 	return psCmd
 }
 }
 
 
@@ -140,14 +140,14 @@ SERVICES:
 	}
 	}
 
 
 	return formatter.Print(containers, opts.Format, os.Stdout,
 	return formatter.Print(containers, opts.Format, os.Stdout,
-		writter(containers),
+		writer(containers),
 		"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
 		"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
 }
 }
 
 
-func writter(containers []api.ContainerSummary) func(w io.Writer) {
+func writer(containers []api.ContainerSummary) func(w io.Writer) {
 	return func(w io.Writer) {
 	return func(w io.Writer) {
 		for _, container := range containers {
 		for _, container := range containers {
-			ports := DisplayablePorts(container)
+			ports := displayablePorts(container)
 			status := container.State
 			status := container.State
 			if status == "running" && container.Health != "" {
 			if status == "running" && container.Health != "" {
 				status = fmt.Sprintf("%s (%s)", container.State, container.Health)
 				status = fmt.Sprintf("%s (%s)", container.State, container.Health)
@@ -179,72 +179,20 @@ func hasStatus(c api.ContainerSummary, statuses []string) bool {
 	return false
 	return false
 }
 }
 
 
-type portRange struct {
-	pStart   int
-	pEnd     int
-	tStart   int
-	tEnd     int
-	IP       string
-	protocol string
-}
-
-func (pr portRange) String() string {
-	var (
-		pub string
-		tgt string
-	)
-
-	if pr.pEnd > pr.pStart {
-		pub = fmt.Sprintf("%s:%d-%d->", pr.IP, pr.pStart, pr.pEnd)
-	} else if pr.pStart > 0 {
-		pub = fmt.Sprintf("%s:%d->", pr.IP, pr.pStart)
-	}
-	if pr.tEnd > pr.tStart {
-		tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd)
-	} else {
-		tgt = fmt.Sprintf("%d", pr.tStart)
-	}
-	return fmt.Sprintf("%s%s/%s", pub, tgt, pr.protocol)
-}
-
-// DisplayablePorts is copy pasted from https://github.com/docker/cli/pull/581/files
-func DisplayablePorts(c api.ContainerSummary) string {
+func displayablePorts(c api.ContainerSummary) string {
 	if c.Publishers == nil {
 	if c.Publishers == nil {
 		return ""
 		return ""
 	}
 	}
 
 
-	sort.Sort(c.Publishers)
-
-	pr := portRange{}
-	ports := []string{}
-	for _, p := range c.Publishers {
-		prIsRange := pr.tEnd != pr.tStart
-		tOverlaps := p.TargetPort <= pr.tEnd
-
-		// Start a new port-range if:
-		// - the protocol is different from the current port-range
-		// - published or target port are not consecutive to the current port-range
-		// - the current port-range is a _range_, and the target port overlaps with the current range's target-ports
-		if p.Protocol != pr.protocol || p.URL != pr.IP || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps {
-			// start a new port-range, and print the previous port-range (if any)
-			if pr.pStart > 0 {
-				ports = append(ports, pr.String())
-			}
-			pr = portRange{
-				pStart:   p.PublishedPort,
-				pEnd:     p.PublishedPort,
-				tStart:   p.TargetPort,
-				tEnd:     p.TargetPort,
-				protocol: p.Protocol,
-				IP:       p.URL,
-			}
-			continue
+	ports := make([]types.Port, len(c.Publishers))
+	for i, pub := range c.Publishers {
+		ports[i] = types.Port{
+			IP:          pub.URL,
+			PrivatePort: uint16(pub.TargetPort),
+			PublicPort:  uint16(pub.PublishedPort),
+			Type:        pub.Protocol,
 		}
 		}
-		pr.pEnd = p.PublishedPort
-		pr.tEnd = p.TargetPort
-	}
-	if pr.tStart > 0 {
-		ports = append(ports, pr.String())
 	}
 	}
-	return strings.Join(ports, ", ")
+
+	return formatter2.DisplayablePorts(ports)
 }
 }

+ 84 - 0
cmd/compose/ps_test.go

@@ -0,0 +1,84 @@
+/*
+   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.
+*/
+
+package compose
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/docker/compose/v2/pkg/api"
+	"github.com/docker/compose/v2/pkg/mocks"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPsPretty(t *testing.T) {
+	ctx := context.Background()
+	origStdout := os.Stdout
+	t.Cleanup(func() {
+		os.Stdout = origStdout
+	})
+	dir := t.TempDir()
+	f, err := os.Create(filepath.Join(dir, "output.txt"))
+	if err != nil {
+		t.Fatal("could not create output file")
+	}
+	defer func() { _ = f.Close() }()
+
+	os.Stdout = f
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	backend := mocks.NewMockService(ctrl)
+	backend.EXPECT().
+		Ps(gomock.Eq(ctx), gomock.Any(), gomock.Any()).
+		DoAndReturn(func(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
+			return []api.ContainerSummary{
+				{
+					ID:   "abc123",
+					Name: "ABC",
+					Publishers: api.PortPublishers{
+						{
+							TargetPort:    8080,
+							PublishedPort: 8080,
+							Protocol:      "tcp",
+						},
+						{
+							TargetPort:    8443,
+							PublishedPort: 8443,
+							Protocol:      "tcp",
+						},
+					},
+				},
+			}, nil
+		}).AnyTimes()
+
+	opts := psOptions{projectOptions: &projectOptions{ProjectName: "test"}}
+	err = runPs(ctx, backend, nil, opts)
+	assert.NoError(t, err)
+
+	_, err = f.Seek(0, 0)
+	assert.NoError(t, err)
+
+	output := make([]byte, 256)
+	_, err = f.Read(output)
+	assert.NoError(t, err)
+
+	assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
+}

+ 2 - 2
cmd/compose/remove.go

@@ -59,13 +59,13 @@ Any data which is not in a volume will be lost.`,
 }
 }
 
 
 func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
 func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
-	project, err := opts.toProject(services)
+	project, err := opts.toProjectName()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	if opts.stop {
 	if opts.stop {
-		err := backend.Stop(ctx, project.Name, api.StopOptions{
+		err := backend.Stop(ctx, project, api.StopOptions{
 			Services: services,
 			Services: services,
 		})
 		})
 		if err != nil {
 		if err != nil {

+ 36 - 10
cmd/compose/run.go

@@ -19,12 +19,12 @@ package compose
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
-	"os"
 	"strings"
 	"strings"
 
 
 	cgo "github.com/compose-spec/compose-go/cli"
 	cgo "github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/loader"
 	"github.com/compose-spec/compose-go/loader"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/cli/command"
 	"github.com/mattn/go-shellwords"
 	"github.com/mattn/go-shellwords"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
@@ -42,6 +42,7 @@ type runOptions struct {
 	Detach        bool
 	Detach        bool
 	Remove        bool
 	Remove        bool
 	noTty         bool
 	noTty         bool
+	interactive   bool
 	user          string
 	user          string
 	workdir       string
 	workdir       string
 	entrypoint    string
 	entrypoint    string
@@ -53,6 +54,7 @@ type runOptions struct {
 	servicePorts  bool
 	servicePorts  bool
 	name          string
 	name          string
 	noDeps        bool
 	noDeps        bool
+	ignoreOrphans bool
 	quietPull     bool
 	quietPull     bool
 }
 }
 
 
@@ -61,6 +63,9 @@ func (opts runOptions) apply(project *types.Project) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+
+	target.Tty = !opts.noTty
+	target.StdinOpen = opts.interactive
 	if !opts.servicePorts {
 	if !opts.servicePorts {
 		target.Ports = []types.ServicePortConfig{}
 		target.Ports = []types.ServicePortConfig{}
 	}
 	}
@@ -102,7 +107,7 @@ func (opts runOptions) apply(project *types.Project) error {
 	return nil
 	return nil
 }
 }
 
 
-func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
+func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := runOptions{
 	opts := runOptions{
 		composeOptions: &composeOptions{
 		composeOptions: &composeOptions{
 			projectOptions: p,
 			projectOptions: p,
@@ -134,6 +139,8 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
+			ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
+			opts.ignoreOrphans = strings.ToLower(ignore) == "true"
 			return runRun(ctx, backend, project, opts)
 			return runRun(ctx, backend, project, opts)
 		}),
 		}),
 		ValidArgsFunction: serviceCompletion(p),
 		ValidArgsFunction: serviceCompletion(p),
@@ -143,7 +150,7 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
 	flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
 	flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
 	flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
 	flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
 	flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
 	flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
-	flags.BoolVarP(&opts.noTty, "no-TTY", "T", false, "Disable pseudo-noTty allocation. By default docker compose run allocates a TTY")
+	flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
 	flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
 	flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
 	flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
 	flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
 	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
 	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
@@ -155,6 +162,10 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
 	flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
 	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(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
 
 
+	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.")
+	cmd.Flags().MarkHidden("tty") //nolint:errcheck
+
 	flags.SetNormalizeFunc(normalizeRunFlags)
 	flags.SetNormalizeFunc(normalizeRunFlags)
 	flags.SetInterspersed(false)
 	flags.SetInterspersed(false)
 	return cmd
 	return cmd
@@ -177,7 +188,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 	}
 	}
 
 
 	err = progress.Run(ctx, func(ctx context.Context) error {
 	err = progress.Run(ctx, func(ctx context.Context) error {
-		return startDependencies(ctx, backend, *project, opts.Service)
+		return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
 	})
 	})
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -199,10 +210,8 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 		Command:           opts.Command,
 		Command:           opts.Command,
 		Detach:            opts.Detach,
 		Detach:            opts.Detach,
 		AutoRemove:        opts.Remove,
 		AutoRemove:        opts.Remove,
-		Stdin:             os.Stdin,
-		Stdout:            os.Stdout,
-		Stderr:            os.Stderr,
 		Tty:               !opts.noTty,
 		Tty:               !opts.noTty,
+		Interactive:       opts.interactive,
 		WorkingDir:        opts.workdir,
 		WorkingDir:        opts.workdir,
 		User:              opts.user,
 		User:              opts.user,
 		Environment:       opts.environment,
 		Environment:       opts.environment,
@@ -213,6 +222,14 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 		Index:             0,
 		Index:             0,
 		QuietPull:         opts.quietPull,
 		QuietPull:         opts.quietPull,
 	}
 	}
+
+	for i, service := range project.Services {
+		if service.Name == opts.Service {
+			service.StdinOpen = opts.interactive
+			project.Services[i] = service
+		}
+	}
+
 	exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
 	exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
 	if exitCode != 0 {
 	if exitCode != 0 {
 		errMsg := ""
 		errMsg := ""
@@ -224,7 +241,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 	return err
 	return err
 }
 }
 
 
-func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string) error {
+func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string, ignoreOrphans bool) error {
 	dependencies := types.Services{}
 	dependencies := types.Services{}
 	var requestedService types.ServiceConfig
 	var requestedService types.ServiceConfig
 	for _, service := range project.Services {
 	for _, service := range project.Services {
@@ -237,8 +254,17 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
 
 
 	project.Services = dependencies
 	project.Services = dependencies
 	project.DisabledServices = append(project.DisabledServices, requestedService)
 	project.DisabledServices = append(project.DisabledServices, requestedService)
-	if err := backend.Create(ctx, &project, api.CreateOptions{}); err != nil {
+	err := backend.Create(ctx, &project, api.CreateOptions{
+		IgnoreOrphans: ignoreOrphans,
+	})
+	if err != nil {
 		return err
 		return err
 	}
 	}
-	return backend.Start(ctx, project.Name, api.StartOptions{})
+
+	if len(dependencies) > 0 {
+		return backend.Start(ctx, project.Name, api.StartOptions{
+			Project: &project,
+		})
+	}
+	return nil
 }
 }

+ 5 - 2
cmd/compose/up.go

@@ -103,8 +103,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
 			return validateFlags(&up, &create)
 			return validateFlags(&up, &create)
 		}),
 		}),
 		RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
 		RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
-			ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
-			create.ignoreOrphans = strings.ToLower(ignore) == "true"
+			create.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
 			if create.ignoreOrphans && create.removeOrphans {
 			if create.ignoreOrphans && create.removeOrphans {
 				return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
 				return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
 			}
 			}
@@ -186,6 +185,9 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
 	if upOptions.attachDependencies {
 	if upOptions.attachDependencies {
 		attachTo = project.ServiceNames()
 		attachTo = project.ServiceNames()
 	}
 	}
+	if len(attachTo) == 0 {
+		attachTo = project.ServiceNames()
+	}
 
 
 	create := api.CreateOptions{
 	create := api.CreateOptions{
 		Services:             services,
 		Services:             services,
@@ -205,6 +207,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
 	return backend.Up(ctx, project, api.UpOptions{
 	return backend.Up(ctx, project, api.UpOptions{
 		Create: create,
 		Create: create,
 		Start: api.StartOptions{
 		Start: api.StartOptions{
+			Project:      project,
 			Attach:       consumer,
 			Attach:       consumer,
 			AttachTo:     attachTo,
 			AttachTo:     attachTo,
 			ExitCodeFrom: upOptions.exitCodeFrom,
 			ExitCodeFrom: upOptions.exitCodeFrom,

+ 1 - 1
cmd/compose/version.go

@@ -57,7 +57,7 @@ func runVersion(opts versionOptions) {
 		return
 		return
 	}
 	}
 	if opts.format == formatter.JSON {
 	if opts.format == formatter.JSON {
-		fmt.Printf(`{"version":%q}\n`, internal.Version)
+		fmt.Printf("{\"version\":%q}\n", internal.Version)
 		return
 		return
 	}
 	}
 	fmt.Println("Docker Compose version", internal.Version)
 	fmt.Println("Docker Compose version", internal.Version)

+ 2 - 2
cmd/formatter/logs.go

@@ -79,7 +79,7 @@ func (l *logConsumer) Log(container, service, message string) {
 	}
 	}
 	p := l.getPresenter(container)
 	p := l.getPresenter(container)
 	for _, line := range strings.Split(message, "\n") {
 	for _, line := range strings.Split(message, "\n") {
-		fmt.Fprintf(l.writer, "%s %s\n", p.prefix, line) // nolint:errcheck
+		fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line) // nolint:errcheck
 	}
 	}
 }
 }
 
 
@@ -118,5 +118,5 @@ type presenter struct {
 }
 }
 
 
 func (p *presenter) setPrefix(width int) {
 func (p *presenter) setPrefix(width int) {
-	p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s |", p.name))
+	p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
 }
 }

+ 3 - 8
cmd/main.go

@@ -32,21 +32,16 @@ import (
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/docker/compose/v2/pkg/compose"
 )
 )
 
 
-func init() {
-	commands.Warning = "The new 'docker compose' command is currently experimental. " +
-		"To provide feedback or request new features please open issues at https://github.com/docker/compose"
-}
-
 func pluginMain() {
 func pluginMain() {
 	plugin.Run(func(dockerCli command.Cli) *cobra.Command {
 	plugin.Run(func(dockerCli command.Cli) *cobra.Command {
 		lazyInit := api.NewServiceProxy()
 		lazyInit := api.NewServiceProxy()
-		cmd := commands.RootCommand(lazyInit)
+		cmd := commands.RootCommand(dockerCli, lazyInit)
 		originalPreRun := cmd.PersistentPreRunE
 		originalPreRun := cmd.PersistentPreRunE
 		cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
 		cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
 			if err := plugin.PersistentPreRunE(cmd, args); err != nil {
 			if err := plugin.PersistentPreRunE(cmd, args); err != nil {
 				return err
 				return err
 			}
 			}
-			lazyInit.WithService(compose.NewComposeService(dockerCli.Client(), dockerCli.ConfigFile()))
+			lazyInit.WithService(compose.NewComposeService(dockerCli))
 			if originalPreRun != nil {
 			if originalPreRun != nil {
 				return originalPreRun(cmd, args)
 				return originalPreRun(cmd, args)
 			}
 			}
@@ -68,7 +63,7 @@ func pluginMain() {
 }
 }
 
 
 func main() {
 func main() {
-	if commands.RunningAsStandalone() {
+	if plugin.RunningStandalone() {
 		os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
 		os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
 	}
 	}
 	pluginMain()
 	pluginMain()

+ 57 - 0
docs/docs.Dockerfile

@@ -0,0 +1,57 @@
+# syntax=docker/dockerfile:1.3-labs
+
+
+#   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.
+
+ARG GO_VERSION=1.18.3
+ARG FORMATS=md,yaml
+
+FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS docsgen
+WORKDIR /src
+RUN --mount=target=. \
+  --mount=target=/root/.cache,type=cache \
+  go build -o /out/docsgen ./docs/yaml/main/generate.go
+
+FROM --platform=${BUILDPLATFORM} alpine AS gen
+RUN apk add --no-cache rsync git
+WORKDIR /src
+COPY --from=docsgen /out/docsgen /usr/bin
+ARG FORMATS
+RUN --mount=target=/context \
+  --mount=target=.,type=tmpfs <<EOT
+set -e
+rsync -a /context/. .
+docsgen --formats "$FORMATS" --source "docs/reference"
+mkdir /out
+cp -r docs/reference /out
+EOT
+
+FROM scratch AS update
+COPY --from=gen /out /out
+
+FROM gen AS validate
+RUN --mount=target=/context \
+  --mount=target=.,type=tmpfs <<EOT
+set -e
+rsync -a /context/. .
+git add -A
+rm -rf docs/reference/*
+cp -rf /out/* ./docs/
+if [ -n "$(git status --porcelain -- docs/reference)" ]; then
+  echo >&2 'ERROR: Docs result differs. Please update with "make docs"'
+  git status --porcelain -- docs/reference
+  exit 1
+fi
+EOT

+ 70 - 17
docs/reference/compose.md

@@ -1,4 +1,54 @@
-
+# docker compose
+
+<!---MARKER_GEN_START-->
+Docker Compose
+
+### Subcommands
+
+| Name | Description |
+| --- | --- |
+| [`build`](compose_build.md) | Build or rebuild services |
+| [`convert`](compose_convert.md) | Converts the compose file to platform's canonical format |
+| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
+| [`create`](compose_create.md) | Creates containers for a service. |
+| [`down`](compose_down.md) | Stop and remove containers, networks |
+| [`events`](compose_events.md) | Receive real time events from containers. |
+| [`exec`](compose_exec.md) | Execute a command in a running container. |
+| [`images`](compose_images.md) | List images used by the created containers |
+| [`kill`](compose_kill.md) | Force stop service containers. |
+| [`logs`](compose_logs.md) | View output from containers |
+| [`ls`](compose_ls.md) | List running compose projects |
+| [`pause`](compose_pause.md) | Pause services |
+| [`port`](compose_port.md) | Print the public port for a port binding. |
+| [`ps`](compose_ps.md) | List containers |
+| [`pull`](compose_pull.md) | Pull service images |
+| [`push`](compose_push.md) | Push service images |
+| [`restart`](compose_restart.md) | Restart containers |
+| [`rm`](compose_rm.md) | Removes stopped service containers |
+| [`run`](compose_run.md) | Run a one-off command on a service. |
+| [`start`](compose_start.md) | Start services |
+| [`stop`](compose_stop.md) | Stop services |
+| [`top`](compose_top.md) | Display the running processes |
+| [`unpause`](compose_unpause.md) | Unpause services |
+| [`up`](compose_up.md) | Create and start containers |
+| [`version`](compose_version.md) | Show the Docker Compose version information |
+
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") |
+| `--compatibility` |  |  | Run compose in backward compatibility mode |
+| `--env-file` | `string` |  | Specify an alternate environment file. |
+| `-f`, `--file` | `stringArray` |  | Compose configuration files |
+| `--profile` | `stringArray` |  | Specify a profile to enable |
+| `--project-directory` | `string` |  | Specify an alternate working directory
+(default: the path of the, first specified, Compose file) |
+| `-p`, `--project-name` | `string` |  | Project name |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
@@ -9,8 +59,8 @@ multiple services in Docker containers.
 Use the `-f` flag to specify the location of a Compose configuration file.
 Use the `-f` flag to specify the location of a Compose configuration file.
 
 
 #### Specifying multiple Compose files
 #### Specifying multiple Compose files
-You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single 
-configuration. Compose builds the configuration in the order you supply the files. Subsequent files override and add 
+You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
+configuration. Compose builds the configuration in the order you supply the files. Subsequent files override and add
 to their predecessors.
 to their predecessors.
 
 
 For example, consider this command line:
 For example, consider this command line:
@@ -30,7 +80,7 @@ services:
     volumes:
     volumes:
       - "/data"
       - "/data"
 ```
 ```
-If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file. 
+If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
 New values, add to the `webapp` service configuration.
 New values, add to the `webapp` service configuration.
 
 
 ```yaml
 ```yaml
@@ -41,22 +91,22 @@ services:
       - DEBUG=1
       - DEBUG=1
 ```
 ```
 
 
-When you use multiple Compose files, all paths in the files are relative to the first configuration file specified 
+When you use multiple Compose files, all paths in the files are relative to the first configuration file specified
 with `-f`. You can use the `--project-directory` option to override this base path.
 with `-f`. You can use the `--project-directory` option to override this base path.
 
 
-Use a `-f` with `-` (dash) as the filename to read the configuration from stdin. When stdin is used all paths in the 
+Use a `-f` with `-` (dash) as the filename to read the configuration from stdin. When stdin is used all paths in the
 configuration are relative to the current working directory.
 configuration are relative to the current working directory.
 
 
-The `-f` flag is optional. If you don’t provide this flag on the command line, Compose traverses the working directory 
+The `-f` flag is optional. If you don’t provide this flag on the command line, Compose traverses the working directory
 and its parent directories looking for a `compose.yaml` or `docker-compose.yaml` file.
 and its parent directories looking for a `compose.yaml` or `docker-compose.yaml` file.
 
 
 #### Specifying a path to a single Compose file
 #### Specifying a path to a single Compose file
-You can use the `-f` flag to specify a path to a Compose file that is not located in the current directory, either 
+You can use the `-f` flag to specify a path to a Compose file that is not located in the current directory, either
 from the command line or by setting up a `COMPOSE_FILE` environment variable in your shell or in an environment file.
 from the command line or by setting up a `COMPOSE_FILE` environment variable in your shell or in an environment file.
 
 
-For an example of using the `-f` option at the command line, suppose you are running the Compose Rails sample, and 
-have a `compose.yaml` file in a directory called `sandbox/rails`. You can use a command like `docker compose pull` to 
-get the postgres image for the db service from anywhere by using the `-f` flag as follows: 
+For an example of using the `-f` option at the command line, suppose you are running the Compose Rails sample, and
+have a `compose.yaml` file in a directory called `sandbox/rails`. You can use a command like `docker compose pull` to
+get the postgres image for the db service from anywhere by using the `-f` flag as follows:
 
 
 ```console
 ```console
 $ docker compose -f ~/sandbox/rails/compose.yaml pull db
 $ docker compose -f ~/sandbox/rails/compose.yaml pull db
@@ -64,17 +114,17 @@ $ docker compose -f ~/sandbox/rails/compose.yaml pull db
 
 
 ### Use `-p` to specify a project name
 ### Use `-p` to specify a project name
 
 
-Each configuration has a project name. If you supply a `-p` flag, you can specify a project name. If you don’t 
-specify the flag, Compose uses the current directory name. 
+Each configuration has a project name. If you supply a `-p` flag, you can specify a project name. If you don’t
+specify the flag, Compose uses the current directory name.
 Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
 Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
 
 
-Most compose subcommand can be ran without a compose file, just passing 
+Most compose subcommand can be ran without a compose file, just passing
 project name to retrieve the relevant resources.
 project name to retrieve the relevant resources.
 
 
 ```console
 ```console
 $ docker compose -p my_project ps -a
 $ docker compose -p my_project ps -a
 NAME                 SERVICE    STATUS     PORTS
 NAME                 SERVICE    STATUS     PORTS
-my_project_demo_1    demo       running             
+my_project_demo_1    demo       running
 
 
 $ docker compose -p my_project logs
 $ docker compose -p my_project logs
 demo_1  | PING localhost (127.0.0.1): 56 data bytes
 demo_1  | PING localhost (127.0.0.1): 56 data bytes
@@ -84,8 +134,8 @@ demo_1  | 64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.095 ms
 ### Use profiles to enable optional services
 ### Use profiles to enable optional services
 
 
 Use `--profile` to specify one or more active profiles
 Use `--profile` to specify one or more active profiles
-Calling `docker compose --profile frontend up` will start the services with the profile `frontend` and services 
-without any specified profiles. 
+Calling `docker compose --profile frontend up` will start the services with the profile `frontend` and services
+without any specified profiles.
 You can also enable multiple profiles, e.g. with `docker compose --profile frontend --profile debug up` the profiles `frontend` and `debug` will be enabled.
 You can also enable multiple profiles, e.g. with `docker compose --profile frontend --profile debug up` the profiles `frontend` and `debug` will be enabled.
 
 
 Profiles can also be set by `COMPOSE_PROFILES` environment variable.
 Profiles can also be set by `COMPOSE_PROFILES` environment variable.
@@ -99,3 +149,6 @@ Setting the `COMPOSE_FILE` environment variable is equivalent to passing the `-f
 and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
 and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
 
 
 If flags are explicitly set on command line, associated environment variable is ignored
 If flags are explicitly set on command line, associated environment variable is ignored
+
+Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
+containers for the project.

+ 21 - 3
docs/reference/compose_build.md

@@ -1,12 +1,30 @@
+# docker compose build
+
+<!---MARKER_GEN_START-->
+Build or rebuild services
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--build-arg` | `stringArray` |  | Set build-time variables for services. |
+| `--no-cache` |  |  | Do not use cache when building the image |
+| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
+| `--pull` |  |  | Always attempt to pull a newer version of the image. |
+| `-q`, `--quiet` |  |  | Don't print anything to STDOUT |
+| `--ssh` | `string` |  | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
-Services are built once and then tagged, by default as `project_service`. 
+Services are built once and then tagged, by default as `project_service`.
 
 
 If the Compose file specifies an
 If the Compose file specifies an
-[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name, 
+[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
 the image is tagged with that name, substituting any variables beforehand. See
 the image is tagged with that name, substituting any variables beforehand. See
 [variable interpolation](https://github.com/compose-spec/compose-spec/blob/master/spec.md#interpolation).
 [variable interpolation](https://github.com/compose-spec/compose-spec/blob/master/spec.md#interpolation).
 
 
-If you change a service's `Dockerfile` or the contents of its build directory, 
+If you change a service's `Dockerfile` or the contents of its build directory,
 run `docker compose build` to rebuild it.
 run `docker compose build` to rebuild it.

+ 28 - 2
docs/reference/compose_convert.md

@@ -1,9 +1,35 @@
+# docker compose convert
 
 
+<!---MARKER_GEN_START-->
+Converts the compose file to platform's canonical format
+
+### Aliases
+
+`convert`, `config`
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
+| `--hash` | `string` |  | Print the service config hash, one per line. |
+| `--images` |  |  | Print the image names, one per line. |
+| `--no-interpolate` |  |  | Don't interpolate environment variables. |
+| `--no-normalize` |  |  | Don't normalize compose model. |
+| `-o`, `--output` | `string` |  | Save to file (default to stdout) |
+| `--profiles` |  |  | Print the profile names, one per line. |
+| `-q`, `--quiet` |  |  | Only validate the configuration, don't print anything. |
+| `--resolve-image-digests` |  |  | Pin image tags to digests. |
+| `--services` |  |  | Print the service names, one per line. |
+| `--volumes` |  |  | Print the volume names, one per line. |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
 `docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
 `docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
-it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into 
-fully defined Compose model. 
+it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
+fully defined Compose model.
 
 
 To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
 To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`

+ 16 - 0
docs/reference/compose_cp.md

@@ -0,0 +1,16 @@
+# docker compose cp
+
+<!---MARKER_GEN_START-->
+Copy files/folders between a service container and the local filesystem
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-a`, `--archive` |  |  | Archive mode (copy all uid/gid information) |
+| `-L`, `--follow-link` |  |  | Always follow symbol link in SRC_PATH |
+| `--index` | `int` | `0` | Index of the container if there are multiple instances of a service . |
+
+
+<!---MARKER_GEN_END-->
+

+ 17 - 0
docs/reference/compose_create.md

@@ -0,0 +1,17 @@
+# docker compose create
+
+<!---MARKER_GEN_START-->
+Creates containers for a service.
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--build` |  |  | Build images before starting containers. |
+| `--force-recreate` |  |  | Recreate containers even if their configuration and image haven't changed. |
+| `--no-build` |  |  | Don't build an image, even if it's missing. |
+| `--no-recreate` |  |  | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
+
+
+<!---MARKER_GEN_END-->
+

+ 15 - 0
docs/reference/compose_down.md

@@ -1,4 +1,19 @@
+# docker compose down
 
 
+<!---MARKER_GEN_START-->
+Stop and remove containers, networks
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--remove-orphans` |  |  | Remove containers for services not defined in the Compose file. |
+| `--rmi` | `string` |  | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") |
+| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
+| `-v`, `--volumes` |  |  |  Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers. |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 13 - 0
docs/reference/compose_events.md

@@ -1,3 +1,16 @@
+# docker compose events
+
+<!---MARKER_GEN_START-->
+Receive real time events from containers.
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--json` |  |  | Output events as a stream of json objects |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 21 - 2
docs/reference/compose_exec.md

@@ -1,7 +1,26 @@
+# docker compose exec
+
+<!---MARKER_GEN_START-->
+Execute a command in a running container.
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-d`, `--detach` |  |  | Detached mode: Run command in the background. |
+| `-e`, `--env` | `stringArray` |  | Set environment variables |
+| `--index` | `int` | `1` | index of the container if there are multiple instances of a service [default: 1]. |
+| `-T`, `--no-TTY` |  |  | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
+| `--privileged` |  |  | Give extended privileges to the process. |
+| `-u`, `--user` | `string` |  | Run the command as this user. |
+| `-w`, `--workdir` | `string` |  | Path to workdir directory for this command. |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
-This is the equivalent of `docker exec` targeting a Compose service. 
+This is the equivalent of `docker exec` targeting a Compose service.
 
 
-With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so 
+With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
 you can use a command such as `docker compose exec web sh` to get an interactive prompt.
 you can use a command such as `docker compose exec web sh` to get an interactive prompt.

+ 14 - 0
docs/reference/compose_images.md

@@ -0,0 +1,14 @@
+# docker compose images
+
+<!---MARKER_GEN_START-->
+List images used by the created containers
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-q`, `--quiet` |  |  | Only display IDs |
+
+
+<!---MARKER_GEN_END-->
+

+ 13 - 0
docs/reference/compose_kill.md

@@ -1,3 +1,16 @@
+# docker compose kill
+
+<!---MARKER_GEN_START-->
+Force stop service containers.
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container. |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 19 - 0
docs/reference/compose_logs.md

@@ -1,3 +1,22 @@
+# docker compose logs
+
+<!---MARKER_GEN_START-->
+View output from containers
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-f`, `--follow` |  |  | Follow log output. |
+| `--no-color` |  |  | Produce monochrome output. |
+| `--no-log-prefix` |  |  | Don't print prefix in logs. |
+| `--since` | `string` |  | Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
+| `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container. |
+| `-t`, `--timestamps` |  |  | Show timestamps. |
+| `--until` | `string` |  | Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 16 - 0
docs/reference/compose_ls.md

@@ -1,3 +1,19 @@
+# docker compose ls
+
+<!---MARKER_GEN_START-->
+List running compose projects
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-a`, `--all` |  |  | Show all stopped Compose projects |
+| `--filter` | `filter` |  | Filter output based on conditions provided. |
+| `--format` | `string` | `pretty` | Format the output. Values: [pretty \| json]. |
+| `-q`, `--quiet` |  |  | Only display IDs. |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 7 - 0
docs/reference/compose_pause.md

@@ -1,3 +1,10 @@
+# docker compose pause
+
+<!---MARKER_GEN_START-->
+Pause services
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 14 - 0
docs/reference/compose_port.md

@@ -1,3 +1,17 @@
+# docker compose port
+
+<!---MARKER_GEN_START-->
+Print the public port for a port binding.
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--index` | `int` | `1` | index of the container if service has multiple replicas |
+| `--protocol` | `string` | `tcp` | tcp or udp |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 109 - 3
docs/reference/compose_ps.md

@@ -1,11 +1,117 @@
+# docker compose ps
+
+<!---MARKER_GEN_START-->
+List containers
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-a`, `--all` |  |  | Show all stopped containers (including those created by the run command) |
+| [`--filter`](#filter) | `string` |  | Filter services by a property (supported filters: status). |
+| [`--format`](#format) | `string` | `pretty` | Format the output. Values: [pretty \| json] |
+| `-q`, `--quiet` |  |  | Only display IDs |
+| `--services` |  |  | Display services |
+| [`--status`](#status) | `stringArray` |  | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
 Lists containers for a Compose project, with current status and exposed ports.
 Lists containers for a Compose project, with current status and exposed ports.
+By default, both running and stopped containers are shown:
 
 
 ```console
 ```console
 $ docker compose ps
 $ docker compose ps
-NAME                SERVICE             STATUS              PORTS
-example_foo_1       foo                 running (healthy)   0.0.0.0:8000->80/tcp
-example_bar_1       bar                 exited (1)          
+NAME           COMMAND                  SERVICE   STATUS       PORTS
+example-bar-1  "/docker-entrypoint.…"   bar       exited (0)
+example-foo-1  "/docker-entrypoint.…"   foo       running      0.0.0.0:8080->80/tcp
+```
+
+## Examples
+
+### <a name="format"></a> Format the output (--format)
+
+By default, the `docker compose ps` command uses a table ("pretty") format to
+show the containers. The `--format` flag allows you to specify alternative
+presentations for the output. Currently supported options are `pretty` (default),
+and `json`, which outputs information about the containers as a JSON array:
+
+```console
+$ docker compose ps --format json
+[{"ID":"1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a","Name":"example-bar-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"bar","State":"exited","Health":"","ExitCode":0,"Publishers":null},{"ID":"f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0","Name":"example-foo-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"foo","State":"running","Health":"","ExitCode":0,"Publishers":[{"URL":"0.0.0.0","TargetPort":80,"PublishedPort":8080,"Protocol":"tcp"}]}]
+```
+
+The JSON output allows you to use the information in other tools for further
+processing, for example, using the [`jq` utility](https://stedolan.github.io/jq/){:target="_blank" rel="noopener" class="_"}
+to pretty-print the JSON:
+
+```console
+$ docker compose ps --format json | jq .
+[
+  {
+    "ID": "1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a",
+    "Name": "example-bar-1",
+    "Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
+    "Project": "example",
+    "Service": "bar",
+    "State": "exited",
+    "Health": "",
+    "ExitCode": 0,
+    "Publishers": null
+  },
+  {
+    "ID": "f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0",
+    "Name": "example-foo-1",
+    "Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
+    "Project": "example",
+    "Service": "foo",
+    "State": "running",
+    "Health": "",
+    "ExitCode": 0,
+    "Publishers": [
+      {
+        "URL": "0.0.0.0",
+        "TargetPort": 80,
+        "PublishedPort": 8080,
+        "Protocol": "tcp"
+      }
+    ]
+  }
+]
+```
+
+### <a name="status"></a> Filter containers by status (--status)
+
+Use the `--status` flag to filter the list of containers by status. For example,
+to show only containers that are running, or only containers that have exited:
+
+```console
+$ docker compose ps --status=running
+NAME           COMMAND                  SERVICE   STATUS       PORTS
+example-foo-1  "/docker-entrypoint.…"   foo       running      0.0.0.0:8080->80/tcp
+
+$ docker compose ps --status=exited
+NAME           COMMAND                  SERVICE   STATUS       PORTS
+example-bar-1  "/docker-entrypoint.…"   bar       exited (0)
 ```
 ```
+
+### <a name="filter"></a> Filter containers by status (--filter)
+
+The [`--status` flag](#status) is a convenience shorthand for the `--filter status=<status>`
+flag. The example below is the equivalent to the example from the previous section,
+this time using the `--filter` flag:
+
+```console
+$ docker compose ps --filter status=running
+NAME           COMMAND                  SERVICE   STATUS       PORTS
+example-foo-1  "/docker-entrypoint.…"   foo       running      0.0.0.0:8080->80/tcp
+
+$ docker compose ps --filter status=running
+NAME           COMMAND                  SERVICE   STATUS       PORTS
+example-bar-1  "/docker-entrypoint.…"   bar       exited (0)
+```
+
+The `docker compose ps` command currently only supports the `--filter status=<status>`
+option, but additional filter options may be added in future.

+ 20 - 5
docs/reference/compose_pull.md

@@ -1,11 +1,26 @@
+# docker compose pull
+
+<!---MARKER_GEN_START-->
+Pull service images
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--ignore-pull-failures` |  |  | Pull what it can and ignores images with pull failures |
+| `--include-deps` |  |  | Also pull services declared as dependencies |
+| `-q`, `--quiet` |  |  | Pull without printing progress information |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
-Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on 
+Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
 those images.
 those images.
 
 
 
 
-## Examples 
+## Examples
 
 
 suppose you have this `compose.yaml`:
 suppose you have this `compose.yaml`:
 
 
@@ -24,8 +39,8 @@ services:
       - db
       - db
 ```
 ```
 
 
-If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service, 
-Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example, 
+If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service,
+Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example,
 you would run `docker compose pull db`.
 you would run `docker compose pull db`.
 
 
 ```console
 ```console
@@ -46,4 +61,4 @@ $ docker compose pull db
    ⠹ f63c47038e66 Waiting                                                  9.3s
    ⠹ f63c47038e66 Waiting                                                  9.3s
    ⠹ 77a0c198cde5 Waiting                                                  9.3s
    ⠹ 77a0c198cde5 Waiting                                                  9.3s
    ⠹ c8752d5b785c Waiting                                                  9.3s
    ⠹ c8752d5b785c Waiting                                                  9.3s
-``̀
+``̀`

+ 13 - 0
docs/reference/compose_push.md

@@ -1,3 +1,16 @@
+# docker compose push
+
+<!---MARKER_GEN_START-->
+Push service images
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--ignore-push-failures` |  |  | Push what it can and ignores images with push failures |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 19 - 3
docs/reference/compose_restart.md

@@ -1,8 +1,24 @@
+# docker compose restart
+
+<!---MARKER_GEN_START-->
+Restart containers
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
+
+
+<!---MARKER_GEN_END-->
+
+## Description
+
 Restarts all stopped and running services.
 Restarts all stopped and running services.
 
 
-If you make changes to your `compose.yml` configuration, these changes are not reflected 
-after running this command. For example, changes to environment variables (which are added 
-after a container is built, but before the container's command is executed) are not updated 
+If you make changes to your `compose.yml` configuration, these changes are not reflected
+after running this command. For example, changes to environment variables (which are added
+after a container is built, but before the container's command is executed) are not updated
 after restarting.
 after restarting.
 
 
 If you are looking to configure a service's restart policy, please refer to
 If you are looking to configure a service's restart policy, please refer to

+ 20 - 0
docs/reference/compose_rm.md

@@ -1,3 +1,23 @@
+# docker compose rm
+
+<!---MARKER_GEN_START-->
+Removes stopped service containers
+
+By default, anonymous volumes attached to containers will not be removed. You
+can override this with -v. To list all volumes, use "docker volume ls".
+
+Any data which is not in a volume will be lost.
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-f`, `--force` |  |  | Don't ask to confirm removal |
+| `-s`, `--stop` |  |  | Stop the containers, if required, before removing |
+| `-v`, `--volumes` |  |  | Remove any anonymous volumes attached to containers |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 36 - 8
docs/reference/compose_run.md

@@ -1,7 +1,35 @@
+# docker compose run
+
+<!---MARKER_GEN_START-->
+Run a one-off command on a service.
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-d`, `--detach` |  |  | Run container in background and print container ID |
+| `--entrypoint` | `string` |  | Override the entrypoint of the image |
+| `-e`, `--env` | `stringArray` |  | Set environment variables |
+| `-i`, `--interactive` |  |  | Keep STDIN open even if not attached. |
+| `-l`, `--label` | `stringArray` |  | Add or override a label |
+| `--name` | `string` |  |  Assign a name to the container |
+| `-T`, `--no-TTY` |  |  | Disable pseudo-TTY allocation (default: auto-detected). |
+| `--no-deps` |  |  | Don't start linked services. |
+| `-p`, `--publish` | `stringArray` |  | Publish a container's port(s) to the host. |
+| `--quiet-pull` |  |  | Pull without printing progress information. |
+| `--rm` |  |  | Automatically remove the container when it exits |
+| `--service-ports` |  |  | Run command with the service's ports enabled and mapped to the host. |
+| `--use-aliases` |  |  | Use the service's network useAliases in the network(s) the container connects to. |
+| `-u`, `--user` | `string` |  | Run as specified username or uid |
+| `-v`, `--volume` | `stringArray` |  | Bind mount a volume. |
+| `-w`, `--workdir` | `string` |  | Working directory inside the container |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
-Runs a one-time command against a service. 
+Runs a one-time command against a service.
 
 
 the following command starts the `web` service and runs `bash` as its command:
 the following command starts the `web` service and runs `bash` as its command:
 
 
@@ -12,12 +40,12 @@ $ docker compose run web bash
 Commands you use with run start in new containers with configuration defined by that of the service,
 Commands you use with run start in new containers with configuration defined by that of the service,
 including volumes, links, and other details. However, there are two important differences:
 including volumes, links, and other details. However, there are two important differences:
 
 
-First, the command passed by `run` overrides the command defined in the service configuration. For example, if the 
-`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with 
+First, the command passed by `run` overrides the command defined in the service configuration. For example, if the
+`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with
 `python app.py`.
 `python app.py`.
 
 
-The second difference is that the `docker compose run` command does not create any of the ports specified in the 
-service configuration. This prevents port collisions with already-open ports. If you do want the service’s ports 
+The second difference is that the `docker compose run` command does not create any of the ports specified in the
+service configuration. This prevents port collisions with already-open ports. If you do want the service’s ports
 to be created and mapped to the host, specify the `--service-ports`
 to be created and mapped to the host, specify the `--service-ports`
 
 
 ```console
 ```console
@@ -30,8 +58,8 @@ Alternatively, manual port mapping can be specified with the `--publish` or `-p`
 $ docker compose run --publish 8080:80 -p 2022:22 -p 127.0.0.1:2021:21 web python manage.py shell
 $ docker compose run --publish 8080:80 -p 2022:22 -p 127.0.0.1:2021:21 web python manage.py shell
 ```
 ```
 
 
-If you start a service configured with links, the run command first checks to see if the linked service is running 
-and starts the service if it is stopped. Once all the linked services are running, the run executes the command you 
+If you start a service configured with links, the run command first checks to see if the linked service is running
+and starts the service if it is stopped. Once all the linked services are running, the run executes the command you
 passed it. For example, you could run:
 passed it. For example, you could run:
 
 
 ```console
 ```console
@@ -52,5 +80,5 @@ If you want to remove the container after running while overriding the container
 $ docker compose run --rm web python manage.py db upgrade
 $ docker compose run --rm web python manage.py db upgrade
 ```
 ```
 
 
-This runs a database upgrade script, and removes the container when finished running, even if a restart policy is 
+This runs a database upgrade script, and removes the container when finished running, even if a restart policy is
 specified in the service configuration.
 specified in the service configuration.

+ 7 - 0
docs/reference/compose_start.md

@@ -1,3 +1,10 @@
+# docker compose start
+
+<!---MARKER_GEN_START-->
+Start services
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 13 - 0
docs/reference/compose_stop.md

@@ -1,3 +1,16 @@
+# docker compose stop
+
+<!---MARKER_GEN_START-->
+Stop services
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 8 - 1
docs/reference/compose_top.md

@@ -1,3 +1,10 @@
+# docker compose top
+
+<!---MARKER_GEN_START-->
+Display the running processes
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
@@ -9,5 +16,5 @@ Displays the running processes.
 $ docker compose top
 $ docker compose top
 example_foo_1
 example_foo_1
 UID    PID      PPID     C    STIME   TTY   TIME       CMD
 UID    PID      PPID     C    STIME   TTY   TIME       CMD
-root   142353   142331   2    15:33   ?     00:00:00   ping localhost -c 5 
+root   142353   142331   2    15:33   ?     00:00:00   ping localhost -c 5
 ```
 ```

+ 7 - 0
docs/reference/compose_unpause.md

@@ -1,3 +1,10 @@
+# docker compose unpause
+
+<!---MARKER_GEN_START-->
+Unpause services
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 

+ 36 - 4
docs/reference/compose_up.md

@@ -1,3 +1,35 @@
+# docker compose up
+
+<!---MARKER_GEN_START-->
+Create and start containers
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `--abort-on-container-exit` |  |  | Stops all containers if any container was stopped. Incompatible with -d |
+| `--always-recreate-deps` |  |  | Recreate dependent containers. Incompatible with --no-recreate. |
+| `--attach` | `stringArray` |  | Attach to service output. |
+| `--attach-dependencies` |  |  | Attach to dependent containers. |
+| `--build` |  |  | Build images before starting containers. |
+| `-d`, `--detach` |  |  | Detached mode: Run containers in the background |
+| `--exit-code-from` | `string` |  | Return the exit code of the selected service container. Implies --abort-on-container-exit |
+| `--force-recreate` |  |  | Recreate containers even if their configuration and image haven't changed. |
+| `--no-build` |  |  | Don't build an image, even if it's missing. |
+| `--no-color` |  |  | Produce monochrome output. |
+| `--no-deps` |  |  | Don't start linked services. |
+| `--no-log-prefix` |  |  | Don't print prefix in logs. |
+| `--no-recreate` |  |  | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
+| `--no-start` |  |  | Don't start the services after creating them. |
+| `--quiet-pull` |  |  | Pull without printing progress information. |
+| `--remove-orphans` |  |  | Remove containers for services not defined in the Compose file. |
+| `-V`, `--renew-anon-volumes` |  |  | Recreate anonymous volumes instead of retrieving data from the previous containers. |
+| `--scale` | `stringArray` |  | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
+| `-t`, `--timeout` | `int` | `10` | Use this timeout in seconds for container shutdown when attached or when containers are already running. |
+| `--wait` |  |  | Wait for services to be running\|healthy. Implies detached mode. |
+
+
+<!---MARKER_GEN_END-->
 
 
 ## Description
 ## Description
 
 
@@ -5,12 +37,12 @@ Builds, (re)creates, starts, and attaches to containers for a service.
 
 
 Unless they are already running, this command also starts any linked services.
 Unless they are already running, this command also starts any linked services.
 
 
-The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does). 
-When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the 
+The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does).
+When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the
 background and leaves them running.
 background and leaves them running.
 
 
-If there are existing containers for a service, and the service’s configuration or image was changed after the 
-container’s creation, `docker compose up` picks up the changes by stopping and recreating the containers 
+If there are existing containers for a service, and the service’s configuration or image was changed after the
+container’s creation, `docker compose up` picks up the changes by stopping and recreating the containers
 (preserving mounted volumes). To prevent Compose from picking up changes, use the `--no-recreate` flag.
 (preserving mounted volumes). To prevent Compose from picking up changes, use the `--no-recreate` flag.
 
 
 If you want to force Compose to stop and recreate all containers, use the `--force-recreate` flag.
 If you want to force Compose to stop and recreate all containers, use the `--force-recreate` flag.

+ 14 - 0
docs/reference/compose_version.md

@@ -0,0 +1,14 @@
+# docker compose version
+
+<!---MARKER_GEN_START-->
+Show the Docker Compose version information
+
+### Options
+
+| Name | Type | Default | Description |
+| --- | --- | --- | --- |
+| `-f`, `--format` | `string` |  | Format the output. Values: [pretty \| json]. (Default: pretty) |
+| `--short` |  |  | Shows only Compose's version number. |
+
+
+<!---MARKER_GEN_END-->

+ 37 - 2
docs/reference/docker_compose.yaml

@@ -98,6 +98,9 @@ long: |-
   and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
   and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
 
 
   If flags are explicitly set on command line, associated environment variable is ignored
   If flags are explicitly set on command line, associated environment variable is ignored
+
+  Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
+  containers for the project.
 usage: docker compose
 usage: docker compose
 pname: docker
 pname: docker
 plink: docker.yaml
 plink: docker.yaml
@@ -126,6 +129,7 @@ cname:
 - docker compose top
 - docker compose top
 - docker compose unpause
 - docker compose unpause
 - docker compose up
 - docker compose up
+- docker compose version
 clink:
 clink:
 - docker_compose_build.yaml
 - docker_compose_build.yaml
 - docker_compose_convert.yaml
 - docker_compose_convert.yaml
@@ -151,6 +155,7 @@ clink:
 - docker_compose_top.yaml
 - docker_compose_top.yaml
 - docker_compose_unpause.yaml
 - docker_compose_unpause.yaml
 - docker_compose_up.yaml
 - docker_compose_up.yaml
+- docker_compose_version.yaml
 options:
 options:
 - option: ansi
 - option: ansi
   value_type: string
   value_type: string
@@ -158,6 +163,17 @@ options:
   description: |
   description: |
     Control when to print ANSI control characters ("never"|"always"|"auto")
     Control when to print ANSI control characters ("never"|"always"|"auto")
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: compatibility
+  value_type: bool
+  default_value: "false"
+  description: Run compose in backward compatibility mode
+  deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -166,6 +182,7 @@ options:
   value_type: string
   value_type: string
   description: Specify an alternate environment file.
   description: Specify an alternate environment file.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -176,6 +193,7 @@ options:
   default_value: '[]'
   default_value: '[]'
   description: Compose configuration files
   description: Compose configuration files
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -185,6 +203,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Do not print ANSI control characters (DEPRECATED)
   description: Do not print ANSI control characters (DEPRECATED)
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -194,6 +213,7 @@ options:
   default_value: '[]'
   default_value: '[]'
   description: Specify a profile to enable
   description: Specify a profile to enable
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -202,8 +222,9 @@ options:
   value_type: string
   value_type: string
   description: |-
   description: |-
     Specify an alternate working directory
     Specify an alternate working directory
-    (default: the path of the Compose file)
+    (default: the path of the, first specified, Compose file)
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -213,6 +234,7 @@ options:
   value_type: string
   value_type: string
   description: Project name
   description: Project name
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -222,6 +244,18 @@ options:
   default_value: "false"
   default_value: "false"
   description: Show more output
   description: Show more output
   deprecated: false
   deprecated: false
+  hidden: true
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: version
+  shorthand: v
+  value_type: bool
+  default_value: "false"
+  description: Show the Docker Compose version information
+  deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -231,8 +265,9 @@ options:
   description: |-
   description: |-
     DEPRECATED! USE --project-directory INSTEAD.
     DEPRECATED! USE --project-directory INSTEAD.
     Specify an alternate working directory
     Specify an alternate working directory
-    (default: the path of the Compose file)
+    (default: the path of the, first specified, Compose file)
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 21 - 1
docs/reference/docker_compose_build.yaml

@@ -19,6 +19,7 @@ options:
   default_value: '[]'
   default_value: '[]'
   description: Set build-time variables for services.
   description: Set build-time variables for services.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -28,6 +29,7 @@ options:
   default_value: "true"
   default_value: "true"
   description: Compress the build context using gzip. DEPRECATED
   description: Compress the build context using gzip. DEPRECATED
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -37,6 +39,7 @@ options:
   default_value: "true"
   default_value: "true"
   description: Always remove intermediate containers. DEPRECATED
   description: Always remove intermediate containers. DEPRECATED
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -47,6 +50,7 @@ options:
   description: |
   description: |
     Set memory limit for the build container. Not supported on buildkit yet.
     Set memory limit for the build container. Not supported on buildkit yet.
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -56,6 +60,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Do not use cache when building the image
   description: Do not use cache when building the image
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -66,6 +71,7 @@ options:
   description: |
   description: |
     Do not remove intermediate containers after a successful build. DEPRECATED
     Do not remove intermediate containers after a successful build. DEPRECATED
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -75,6 +81,7 @@ options:
   default_value: "true"
   default_value: "true"
   description: Build images in parallel. DEPRECATED
   description: Build images in parallel. DEPRECATED
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -82,8 +89,9 @@ options:
 - option: progress
 - option: progress
   value_type: string
   value_type: string
   default_value: auto
   default_value: auto
-  description: Set type of progress output ("auto", "plain", "noTty")
+  description: Set type of progress output (auto, tty, plain, quiet)
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -93,6 +101,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Always attempt to pull a newer version of the image.
   description: Always attempt to pull a newer version of the image.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -103,6 +112,17 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't print anything to STDOUT
   description: Don't print anything to STDOUT
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: ssh
+  value_type: string
+  description: |
+    Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)
+  deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 38 - 0
docs/reference/docker_compose_convert.yaml

@@ -16,6 +16,7 @@ options:
   default_value: yaml
   default_value: yaml
   description: 'Format the output. Values: [yaml | json]'
   description: 'Format the output. Values: [yaml | json]'
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -24,6 +25,17 @@ options:
   value_type: string
   value_type: string
   description: Print the service config hash, one per line.
   description: Print the service config hash, one per line.
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: images
+  value_type: bool
+  default_value: "false"
+  description: Print the image names, one per line.
+  deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -33,6 +45,27 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't interpolate environment variables.
   description: Don't interpolate environment variables.
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: no-normalize
+  value_type: bool
+  default_value: "false"
+  description: Don't normalize compose model.
+  deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: output
+  shorthand: o
+  value_type: string
+  description: Save to file (default to stdout)
+  deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -42,6 +75,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Print the profile names, one per line.
   description: Print the profile names, one per line.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -52,6 +86,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Only validate the configuration, don't print anything.
   description: Only validate the configuration, don't print anything.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -61,6 +96,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Pin image tags to digests.
   description: Pin image tags to digests.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -70,6 +106,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Print the service names, one per line.
   description: Print the service names, one per line.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -79,6 +116,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Print the volume names, one per line.
   description: Print the volume names, one per line.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 7 - 3
docs/reference/docker_compose_cp.yaml

@@ -10,7 +10,8 @@ options:
   value_type: bool
   value_type: bool
   default_value: "false"
   default_value: "false"
   description: Copy to all the containers of the service.
   description: Copy to all the containers of the service.
-  deprecated: false
+  deprecated: true
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -21,6 +22,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Archive mode (copy all uid/gid information)
   description: Archive mode (copy all uid/gid information)
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -31,16 +33,18 @@ options:
   default_value: "false"
   default_value: "false"
   description: Always follow symbol link in SRC_PATH
   description: Always follow symbol link in SRC_PATH
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
   swarm: false
   swarm: false
 - option: index
 - option: index
   value_type: int
   value_type: int
-  default_value: "1"
+  default_value: "0"
   description: |
   description: |
-    Index of the container if there are multiple instances of a service [default: 1].
+    Index of the container if there are multiple instances of a service .
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 4 - 0
docs/reference/docker_compose_create.yaml

@@ -10,6 +10,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Build images before starting containers.
   description: Build images before starting containers.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -20,6 +21,7 @@ options:
   description: |
   description: |
     Recreate containers even if their configuration and image haven't changed.
     Recreate containers even if their configuration and image haven't changed.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -29,6 +31,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't build an image, even if it's missing.
   description: Don't build an image, even if it's missing.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -39,6 +42,7 @@ options:
   description: |
   description: |
     If containers already exist, don't recreate them. Incompatible with --force-recreate.
     If containers already exist, don't recreate them. Incompatible with --force-recreate.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 4 - 0
docs/reference/docker_compose_down.yaml

@@ -23,6 +23,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Remove containers for services not defined in the Compose file.
   description: Remove containers for services not defined in the Compose file.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -32,6 +33,7 @@ options:
   description: |
   description: |
     Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")
     Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -42,6 +44,7 @@ options:
   default_value: "10"
   default_value: "10"
   description: Specify a shutdown timeout in seconds
   description: Specify a shutdown timeout in seconds
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -53,6 +56,7 @@ options:
   description: |
   description: |
     Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.
     Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 1 - 0
docs/reference/docker_compose_events.yaml

@@ -29,6 +29,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Output events as a stream of json objects
   description: Output events as a stream of json objects
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 30 - 1
docs/reference/docker_compose_exec.yaml

@@ -15,6 +15,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: 'Detached mode: Run command in the background.'
   description: 'Detached mode: Run command in the background.'
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -25,6 +26,7 @@ options:
   default_value: '[]'
   default_value: '[]'
   description: Set environment variables
   description: Set environment variables
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -35,6 +37,18 @@ options:
   description: |
   description: |
     index of the container if there are multiple instances of a service [default: 1].
     index of the container if there are multiple instances of a service [default: 1].
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: interactive
+  shorthand: i
+  value_type: bool
+  default_value: "true"
+  description: Keep STDIN open even if not attached.
+  deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -42,10 +56,11 @@ options:
 - option: no-TTY
 - option: no-TTY
   shorthand: T
   shorthand: T
   value_type: bool
   value_type: bool
-  default_value: "false"
+  default_value: "true"
   description: |
   description: |
     Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.
     Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -55,6 +70,18 @@ options:
   default_value: "false"
   default_value: "false"
   description: Give extended privileges to the process.
   description: Give extended privileges to the process.
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: tty
+  shorthand: t
+  value_type: bool
+  default_value: "true"
+  description: Allocate a pseudo-TTY.
+  deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -64,6 +91,7 @@ options:
   value_type: string
   value_type: string
   description: Run the command as this user.
   description: Run the command as this user.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -73,6 +101,7 @@ options:
   value_type: string
   value_type: string
   description: Path to workdir directory for this command.
   description: Path to workdir directory for this command.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 1 - 0
docs/reference/docker_compose_images.yaml

@@ -11,6 +11,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Only display IDs
   description: Only display IDs
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 1 - 0
docs/reference/docker_compose_kill.yaml

@@ -16,6 +16,7 @@ options:
   default_value: SIGKILL
   default_value: SIGKILL
   description: SIGNAL to send to the container.
   description: SIGNAL to send to the container.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 7 - 0
docs/reference/docker_compose_logs.yaml

@@ -11,6 +11,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Follow log output.
   description: Follow log output.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -20,6 +21,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Produce monochrome output.
   description: Produce monochrome output.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -29,6 +31,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't print prefix in logs.
   description: Don't print prefix in logs.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -38,6 +41,7 @@ options:
   description: |
   description: |
     Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
     Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -48,6 +52,7 @@ options:
   description: |
   description: |
     Number of lines to show from the end of the logs for each container.
     Number of lines to show from the end of the logs for each container.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -58,6 +63,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Show timestamps.
   description: Show timestamps.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -67,6 +73,7 @@ options:
   description: |
   description: |
     Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
     Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 4 - 0
docs/reference/docker_compose_ls.yaml

@@ -11,6 +11,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Show all stopped Compose projects
   description: Show all stopped Compose projects
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -19,6 +20,7 @@ options:
   value_type: filter
   value_type: filter
   description: Filter output based on conditions provided.
   description: Filter output based on conditions provided.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -28,6 +30,7 @@ options:
   default_value: pretty
   default_value: pretty
   description: 'Format the output. Values: [pretty | json].'
   description: 'Format the output. Values: [pretty | json].'
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -38,6 +41,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Only display IDs.
   description: Only display IDs.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 1 - 1
docs/reference/docker_compose_pause.yaml

@@ -1,5 +1,5 @@
 command: docker compose pause
 command: docker compose pause
-short: pause services
+short: Pause services
 long: |
 long: |
   Pauses running containers of a service. They can be unpaused with `docker compose unpause`.
   Pauses running containers of a service. They can be unpaused with `docker compose unpause`.
 usage: docker compose pause [SERVICE...]
 usage: docker compose pause [SERVICE...]

+ 2 - 0
docs/reference/docker_compose_port.yaml

@@ -10,6 +10,7 @@ options:
   default_value: "1"
   default_value: "1"
   description: index of the container if service has multiple replicas
   description: index of the container if service has multiple replicas
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -19,6 +20,7 @@ options:
   default_value: tcp
   default_value: tcp
   description: tcp or udp
   description: tcp or udp
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 103 - 6
docs/reference/docker_compose_ps.yaml

@@ -2,12 +2,13 @@ command: docker compose ps
 short: List containers
 short: List containers
 long: |-
 long: |-
   Lists containers for a Compose project, with current status and exposed ports.
   Lists containers for a Compose project, with current status and exposed ports.
+  By default, both running and stopped containers are shown:
 
 
   ```console
   ```console
   $ docker compose ps
   $ docker compose ps
-  NAME                SERVICE             STATUS              PORTS
-  example_foo_1       foo                 running (healthy)   0.0.0.0:8000->80/tcp
-  example_bar_1       bar                 exited (1)
+  NAME           COMMAND                  SERVICE   STATUS       PORTS
+  example-bar-1  "/docker-entrypoint.…"   bar       exited (0)
+  example-foo-1  "/docker-entrypoint.…"   foo       running      0.0.0.0:8080->80/tcp
   ```
   ```
 usage: docker compose ps [SERVICE...]
 usage: docker compose ps [SERVICE...]
 pname: docker compose
 pname: docker compose
@@ -20,14 +21,17 @@ options:
   description: |
   description: |
     Show all stopped containers (including those created by the run command)
     Show all stopped containers (including those created by the run command)
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
   swarm: false
   swarm: false
 - option: filter
 - option: filter
   value_type: string
   value_type: string
-  description: Filter services by a property
+  description: 'Filter services by a property (supported filters: status).'
+  details_url: '#filter'
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -36,7 +40,9 @@ options:
   value_type: string
   value_type: string
   default_value: pretty
   default_value: pretty
   description: 'Format the output. Values: [pretty | json]'
   description: 'Format the output. Values: [pretty | json]'
+  details_url: '#format'
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -47,6 +53,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Only display IDs
   description: Only display IDs
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -56,18 +63,108 @@ options:
   default_value: "false"
   default_value: "false"
   description: Display services
   description: Display services
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
   swarm: false
   swarm: false
 - option: status
 - option: status
-  value_type: string
-  description: Filter services by status
+  value_type: stringArray
+  default_value: '[]'
+  description: |
+    Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]
+  details_url: '#status'
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
   swarm: false
   swarm: false
+examples: |-
+  ### Format the output (--format) {#format}
+
+  By default, the `docker compose ps` command uses a table ("pretty") format to
+  show the containers. The `--format` flag allows you to specify alternative
+  presentations for the output. Currently supported options are `pretty` (default),
+  and `json`, which outputs information about the containers as a JSON array:
+
+  ```console
+  $ docker compose ps --format json
+  [{"ID":"1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a","Name":"example-bar-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"bar","State":"exited","Health":"","ExitCode":0,"Publishers":null},{"ID":"f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0","Name":"example-foo-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"foo","State":"running","Health":"","ExitCode":0,"Publishers":[{"URL":"0.0.0.0","TargetPort":80,"PublishedPort":8080,"Protocol":"tcp"}]}]
+  ```
+
+  The JSON output allows you to use the information in other tools for further
+  processing, for example, using the [`jq` utility](https://stedolan.github.io/jq/){:target="_blank" rel="noopener" class="_"}
+  to pretty-print the JSON:
+
+  ```console
+  $ docker compose ps --format json | jq .
+  [
+    {
+      "ID": "1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a",
+      "Name": "example-bar-1",
+      "Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
+      "Project": "example",
+      "Service": "bar",
+      "State": "exited",
+      "Health": "",
+      "ExitCode": 0,
+      "Publishers": null
+    },
+    {
+      "ID": "f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0",
+      "Name": "example-foo-1",
+      "Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
+      "Project": "example",
+      "Service": "foo",
+      "State": "running",
+      "Health": "",
+      "ExitCode": 0,
+      "Publishers": [
+        {
+          "URL": "0.0.0.0",
+          "TargetPort": 80,
+          "PublishedPort": 8080,
+          "Protocol": "tcp"
+        }
+      ]
+    }
+  ]
+  ```
+
+  ### Filter containers by status (--status) {#status}
+
+  Use the `--status` flag to filter the list of containers by status. For example,
+  to show only containers that are running, or only containers that have exited:
+
+  ```console
+  $ docker compose ps --status=running
+  NAME           COMMAND                  SERVICE   STATUS       PORTS
+  example-foo-1  "/docker-entrypoint.…"   foo       running      0.0.0.0:8080->80/tcp
+
+  $ docker compose ps --status=exited
+  NAME           COMMAND                  SERVICE   STATUS       PORTS
+  example-bar-1  "/docker-entrypoint.…"   bar       exited (0)
+  ```
+
+  ### Filter containers by status (--filter) {#filter}
+
+  The [`--status` flag](#status) is a convenience shorthand for the `--filter status=<status>`
+  flag. The example below is the equivalent to the example from the previous section,
+  this time using the `--filter` flag:
+
+  ```console
+  $ docker compose ps --filter status=running
+  NAME           COMMAND                  SERVICE   STATUS       PORTS
+  example-foo-1  "/docker-entrypoint.…"   foo       running      0.0.0.0:8080->80/tcp
+
+  $ docker compose ps --filter status=running
+  NAME           COMMAND                  SERVICE   STATUS       PORTS
+  example-bar-1  "/docker-entrypoint.…"   bar       exited (0)
+  ```
+
+  The `docker compose ps` command currently only supports the `--filter status=<status>`
+  option, but additional filter options may be added in future.
 deprecated: false
 deprecated: false
 experimental: false
 experimental: false
 experimentalcli: false
 experimentalcli: false

+ 46 - 0
docs/reference/docker_compose_pull.yaml

@@ -12,6 +12,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Pull what it can and ignores images with pull failures
   description: Pull what it can and ignores images with pull failures
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -21,6 +22,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Also pull services declared as dependencies
   description: Also pull services declared as dependencies
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -30,6 +32,7 @@ options:
   default_value: "true"
   default_value: "true"
   description: DEPRECATED disable parallel pulling.
   description: DEPRECATED disable parallel pulling.
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -39,6 +42,7 @@ options:
   default_value: "true"
   default_value: "true"
   description: DEPRECATED pull multiple images in parallel.
   description: DEPRECATED pull multiple images in parallel.
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -49,10 +53,52 @@ options:
   default_value: "false"
   default_value: "false"
   description: Pull without printing progress information
   description: Pull without printing progress information
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
   swarm: false
   swarm: false
+examples: |-
+  suppose you have this `compose.yaml`:
+
+  ```yaml
+  services:
+    db:
+      image: postgres
+    web:
+      build: .
+      command: bundle exec rails s -p 3000 -b '0.0.0.0'
+      volumes:
+        - .:/myapp
+      ports:
+        - "3000:3000"
+      depends_on:
+        - db
+  ```
+
+  If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service,
+  Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example,
+  you would run `docker compose pull db`.
+
+  ```console
+  $ docker compose pull db
+  [+] Running 1/15
+   ⠸ db Pulling                                                             12.4s
+     ⠿ 45b42c59be33 Already exists                                           0.0s
+     ⠹ 40adec129f1a Downloading  3.374MB/4.178MB                             9.3s
+     ⠹ b4c431d00c78 Download complete                                        9.3s
+     ⠹ 2696974e2815 Download complete                                        9.3s
+     ⠹ 564b77596399 Downloading  5.622MB/7.965MB                             9.3s
+     ⠹ 5044045cf6f2 Downloading  216.7kB/391.1kB                             9.3s
+     ⠹ d736e67e6ac3 Waiting                                                  9.3s
+     ⠹ 390c1c9a5ae4 Waiting                                                  9.3s
+     ⠹ c0e62f172284 Waiting                                                  9.3s
+     ⠹ ebcdc659c5bf Waiting                                                  9.3s
+     ⠹ 29be22cb3acc Waiting                                                  9.3s
+     ⠹ f63c47038e66 Waiting                                                  9.3s
+     ⠹ 77a0c198cde5 Waiting                                                  9.3s
+     ⠹ c8752d5b785c Waiting                                                  9.3s
+  ``̀`
 deprecated: false
 deprecated: false
 experimental: false
 experimental: false
 experimentalcli: false
 experimentalcli: false

+ 1 - 0
docs/reference/docker_compose_push.yaml

@@ -28,6 +28,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Push what it can and ignores images with push failures
   description: Push what it can and ignores images with push failures
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 12 - 1
docs/reference/docker_compose_restart.yaml

@@ -1,6 +1,16 @@
 command: docker compose restart
 command: docker compose restart
 short: Restart containers
 short: Restart containers
-long: Restart containers
+long: |-
+  Restarts all stopped and running services.
+
+  If you make changes to your `compose.yml` configuration, these changes are not reflected
+  after running this command. For example, changes to environment variables (which are added
+  after a container is built, but before the container's command is executed) are not updated
+  after restarting.
+
+  If you are looking to configure a service's restart policy, please refer to
+  [restart](https://github.com/compose-spec/compose-spec/blob/master/spec.md#restart)
+  or [restart_policy](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy).
 usage: docker compose restart
 usage: docker compose restart
 pname: docker compose
 pname: docker compose
 plink: docker_compose.yaml
 plink: docker_compose.yaml
@@ -11,6 +21,7 @@ options:
   default_value: "10"
   default_value: "10"
   description: Specify a shutdown timeout in seconds
   description: Specify a shutdown timeout in seconds
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 4 - 0
docs/reference/docker_compose_rm.yaml

@@ -26,6 +26,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Deprecated - no effect
   description: Deprecated - no effect
   deprecated: false
   deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -36,6 +37,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't ask to confirm removal
   description: Don't ask to confirm removal
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -46,6 +48,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Stop the containers, if required, before removing
   description: Stop the containers, if required, before removing
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -56,6 +59,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Remove any anonymous volumes attached to containers
   description: Remove any anonymous volumes attached to containers
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 49 - 4
docs/reference/docker_compose_run.yaml

@@ -65,6 +65,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Run container in background and print container ID
   description: Run container in background and print container ID
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -73,6 +74,7 @@ options:
   value_type: string
   value_type: string
   description: Override the entrypoint of the image
   description: Override the entrypoint of the image
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -83,16 +85,29 @@ options:
   default_value: '[]'
   default_value: '[]'
   description: Set environment variables
   description: Set environment variables
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
   swarm: false
   swarm: false
-- option: labels
+- option: interactive
+  shorthand: i
+  value_type: bool
+  default_value: "true"
+  description: Keep STDIN open even if not attached.
+  deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: label
   shorthand: l
   shorthand: l
   value_type: stringArray
   value_type: stringArray
   default_value: '[]'
   default_value: '[]'
   description: Add or override a label
   description: Add or override a label
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -101,6 +116,7 @@ options:
   value_type: string
   value_type: string
   description: Assign a name to the container
   description: Assign a name to the container
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -108,10 +124,10 @@ options:
 - option: no-TTY
 - option: no-TTY
   shorthand: T
   shorthand: T
   value_type: bool
   value_type: bool
-  default_value: "false"
-  description: |
-    Disable pseudo-noTty allocation. By default docker compose run allocates a TTY
+  default_value: "true"
+  description: 'Disable pseudo-TTY allocation (default: auto-detected).'
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -121,6 +137,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't start linked services.
   description: Don't start linked services.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -131,6 +148,17 @@ options:
   default_value: '[]'
   default_value: '[]'
   description: Publish a container's port(s) to the host.
   description: Publish a container's port(s) to the host.
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: quiet-pull
+  value_type: bool
+  default_value: "false"
+  description: Pull without printing progress information.
+  deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -140,6 +168,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Automatically remove the container when it exits
   description: Automatically remove the container when it exits
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -150,6 +179,18 @@ options:
   description: |
   description: |
     Run command with the service's ports enabled and mapped to the host.
     Run command with the service's ports enabled and mapped to the host.
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: tty
+  shorthand: t
+  value_type: bool
+  default_value: "true"
+  description: Allocate a pseudo-TTY.
+  deprecated: false
+  hidden: true
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -160,6 +201,7 @@ options:
   description: |
   description: |
     Use the service's network useAliases in the network(s) the container connects to.
     Use the service's network useAliases in the network(s) the container connects to.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -169,6 +211,7 @@ options:
   value_type: string
   value_type: string
   description: Run as specified username or uid
   description: Run as specified username or uid
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -179,6 +222,7 @@ options:
   default_value: '[]'
   default_value: '[]'
   description: Bind mount a volume.
   description: Bind mount a volume.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -188,6 +232,7 @@ options:
   value_type: string
   value_type: string
   description: Working directory inside the container
   description: Working directory inside the container
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 1 - 0
docs/reference/docker_compose_stop.yaml

@@ -12,6 +12,7 @@ options:
   default_value: "10"
   default_value: "10"
   description: Specify a shutdown timeout in seconds
   description: Specify a shutdown timeout in seconds
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 1 - 1
docs/reference/docker_compose_unpause.yaml

@@ -1,5 +1,5 @@
 command: docker compose unpause
 command: docker compose unpause
-short: unpause services
+short: Unpause services
 long: Unpauses paused containers of a service.
 long: Unpauses paused containers of a service.
 usage: docker compose unpause [SERVICE...]
 usage: docker compose unpause [SERVICE...]
 pname: docker compose
 pname: docker compose

+ 29 - 10
docs/reference/docker_compose_up.yaml

@@ -27,6 +27,7 @@ options:
   description: |
   description: |
     Stops all containers if any container was stopped. Incompatible with -d
     Stops all containers if any container was stopped. Incompatible with -d
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -36,6 +37,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Recreate dependent containers. Incompatible with --no-recreate.
   description: Recreate dependent containers. Incompatible with --no-recreate.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -45,6 +47,7 @@ options:
   default_value: '[]'
   default_value: '[]'
   description: Attach to service output.
   description: Attach to service output.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -54,6 +57,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Attach to dependent containers.
   description: Attach to dependent containers.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -63,6 +67,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Build images before starting containers.
   description: Build images before starting containers.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -73,16 +78,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: 'Detached mode: Run containers in the background'
   description: 'Detached mode: Run containers in the background'
   deprecated: false
   deprecated: false
-  experimental: false
-  experimentalcli: false
-  kubernetes: false
-  swarm: false
-- option: environment
-  shorthand: e
-  value_type: stringArray
-  default_value: '[]'
-  description: Environment variables
-  deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -92,6 +88,7 @@ options:
   description: |
   description: |
     Return the exit code of the selected service container. Implies --abort-on-container-exit
     Return the exit code of the selected service container. Implies --abort-on-container-exit
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -102,6 +99,7 @@ options:
   description: |
   description: |
     Recreate containers even if their configuration and image haven't changed.
     Recreate containers even if their configuration and image haven't changed.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -111,6 +109,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't build an image, even if it's missing.
   description: Don't build an image, even if it's missing.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -120,6 +119,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Produce monochrome output.
   description: Produce monochrome output.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -129,6 +129,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't start linked services.
   description: Don't start linked services.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -138,6 +139,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't print prefix in logs.
   description: Don't print prefix in logs.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -148,6 +150,7 @@ options:
   description: |
   description: |
     If containers already exist, don't recreate them. Incompatible with --force-recreate.
     If containers already exist, don't recreate them. Incompatible with --force-recreate.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -157,6 +160,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Don't start the services after creating them.
   description: Don't start the services after creating them.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -166,6 +170,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Pull without printing progress information.
   description: Pull without printing progress information.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -175,6 +180,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Remove containers for services not defined in the Compose file.
   description: Remove containers for services not defined in the Compose file.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -186,6 +192,7 @@ options:
   description: |
   description: |
     Recreate anonymous volumes instead of retrieving data from the previous containers.
     Recreate anonymous volumes instead of retrieving data from the previous containers.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -196,6 +203,7 @@ options:
   description: |
   description: |
     Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.
     Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -207,6 +215,17 @@ options:
   description: |
   description: |
     Use this timeout in seconds for container shutdown when attached or when containers are already running.
     Use this timeout in seconds for container shutdown when attached or when containers are already running.
   deprecated: false
   deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
+- option: wait
+  value_type: bool
+  default_value: "false"
+  description: Wait for services to be running|healthy. Implies detached mode.
+  deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 2 - 0
docs/reference/docker_compose_version.yaml

@@ -10,6 +10,7 @@ options:
   value_type: string
   value_type: string
   description: 'Format the output. Values: [pretty | json]. (Default: pretty)'
   description: 'Format the output. Values: [pretty | json]. (Default: pretty)'
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
@@ -19,6 +20,7 @@ options:
   default_value: "false"
   default_value: "false"
   description: Shows only Compose's version number.
   description: Shows only Compose's version number.
   deprecated: false
   deprecated: false
+  hidden: false
   experimental: false
   experimental: false
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false

+ 15 - 8
docs/yaml/main/generate.go

@@ -22,16 +22,23 @@ import (
 	"path/filepath"
 	"path/filepath"
 
 
 	clidocstool "github.com/docker/cli-docs-tool"
 	clidocstool "github.com/docker/cli-docs-tool"
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/cmd/compose"
 	"github.com/docker/compose/v2/cmd/compose"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-func generateCliYaml(opts *options) error {
-	cmd := &cobra.Command{Use: "docker"}
-	cmd.AddCommand(compose.RootCommand(nil))
+func generateDocs(opts *options) error {
+	dockerCLI, err := command.NewDockerCli()
+	if err != nil {
+		return err
+	}
+	cmd := &cobra.Command{
+		Use:               "docker",
+		DisableAutoGenTag: true,
+	}
+	cmd.AddCommand(compose.RootCommand(dockerCLI, nil))
 	disableFlagsInUseLine(cmd)
 	disableFlagsInUseLine(cmd)
 
 
-	cmd.DisableAutoGenTag = true
 	tool, err := clidocstool.New(clidocstool.Options{
 	tool, err := clidocstool.New(clidocstool.Options{
 		Root:      cmd,
 		Root:      cmd,
 		SourceDir: opts.source,
 		SourceDir: opts.source,
@@ -41,7 +48,7 @@ func generateCliYaml(opts *options) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return tool.GenYamlTree(cmd)
+	return tool.GenAllTree()
 }
 }
 
 
 func disableFlagsInUseLine(cmd *cobra.Command) {
 func disableFlagsInUseLine(cmd *cobra.Command) {
@@ -69,12 +76,12 @@ type options struct {
 func main() {
 func main() {
 	cwd, _ := os.Getwd()
 	cwd, _ := os.Getwd()
 	opts := &options{
 	opts := &options{
-		source: cwd,
+		source: filepath.Join(cwd, "docs", "reference"),
 		target: filepath.Join(cwd, "docs", "reference"),
 		target: filepath.Join(cwd, "docs", "reference"),
 	}
 	}
 	fmt.Printf("Project root: %s\n", opts.source)
 	fmt.Printf("Project root: %s\n", opts.source)
 	fmt.Printf("Generating yaml files into %s\n", opts.target)
 	fmt.Printf("Generating yaml files into %s\n", opts.target)
-	if err := generateCliYaml(opts); err != nil {
-		fmt.Fprintf(os.Stderr, "Failed to generate yaml files: %s\n", err.Error())
+	if err := generateDocs(opts); err != nil {
+		_, _ = fmt.Fprintf(os.Stderr, "Failed to generate documentation: %s\n", err.Error())
 	}
 	}
 }
 }

+ 75 - 66
go.mod

@@ -1,144 +1,153 @@
 module github.com/docker/compose/v2
 module github.com/docker/compose/v2
 
 
-go 1.17
+go 1.18
 
 
 require (
 require (
-	github.com/AlecAivazis/survey/v2 v2.3.2
+	github.com/AlecAivazis/survey/v2 v2.3.5
 	github.com/buger/goterm v1.0.4
 	github.com/buger/goterm v1.0.4
-	github.com/cnabio/cnab-to-oci v0.3.1-beta1
-	github.com/compose-spec/compose-go v1.1.0
+	github.com/cnabio/cnab-to-oci v0.3.4
+	github.com/compose-spec/compose-go v1.2.8
 	github.com/containerd/console v1.0.3
 	github.com/containerd/console v1.0.3
-	github.com/containerd/containerd v1.6.0
+	github.com/containerd/containerd v1.6.6
 	github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
 	github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
-	github.com/docker/buildx v0.7.1
-	github.com/docker/cli v20.10.12+incompatible
-	github.com/docker/cli-docs-tool v0.2.1
-	github.com/docker/docker v20.10.7+incompatible
+	github.com/docker/buildx v0.8.2 // when updating, also update the replace rules accordingly
+	github.com/docker/cli v20.10.17+incompatible
+	github.com/docker/cli-docs-tool v0.4.0
+	github.com/docker/docker v20.10.17+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/docker/go-connections v0.4.0
 	github.com/docker/go-units v0.4.0
 	github.com/docker/go-units v0.4.0
 	github.com/golang/mock v1.6.0
 	github.com/golang/mock v1.6.0
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/hashicorp/go-multierror v1.1.1
-	github.com/hashicorp/go-version v1.3.0
+	github.com/hashicorp/go-version v1.6.0
 	github.com/mattn/go-isatty v0.0.14
 	github.com/mattn/go-isatty v0.0.14
 	github.com/mattn/go-shellwords v1.0.12
 	github.com/mattn/go-shellwords v1.0.12
-	github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da
+	github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d
 	github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
 	github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
 	github.com/morikuni/aec v1.0.0
 	github.com/morikuni/aec v1.0.0
 	github.com/opencontainers/go-digest v1.0.0
 	github.com/opencontainers/go-digest v1.0.0
-	github.com/opencontainers/image-spec v1.0.2
+	github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
 	github.com/pkg/errors v0.9.1
 	github.com/pkg/errors v0.9.1
 	github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
 	github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
 	github.com/sirupsen/logrus v1.8.1
 	github.com/sirupsen/logrus v1.8.1
-	github.com/spf13/cobra v1.3.0
+	github.com/spf13/cobra v1.5.0
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/pflag v1.0.5
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
+	github.com/theupdateframework/notary v0.7.0
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	gotest.tools v2.2.0+incompatible
 	gotest.tools v2.2.0+incompatible
-	gotest.tools/v3 v3.1.0
+	gotest.tools/v3 v3.3.0
 )
 )
 
 
 require (
 require (
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Masterminds/semver v1.5.0 // indirect
 	github.com/Masterminds/semver v1.5.0 // indirect
-	github.com/Microsoft/go-winio v0.5.1 // indirect
-	github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
+	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
-	github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
+	github.com/cnabio/cnab-go v0.23.4 // indirect
 	github.com/containerd/continuity v0.2.2 // indirect
 	github.com/containerd/continuity v0.2.2 // indirect
+	github.com/containerd/ttrpc v1.1.0 // indirect
 	github.com/containerd/typeurl v1.0.2 // indirect
 	github.com/containerd/typeurl v1.0.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/docker/distribution v2.8.0+incompatible // indirect
+	github.com/docker/distribution v2.8.1+incompatible // indirect
 	github.com/docker/docker-credential-helpers v0.6.4 // indirect
 	github.com/docker/docker-credential-helpers v0.6.4 // indirect
 	github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
 	github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
 	github.com/docker/go-metrics v0.0.1 // indirect
 	github.com/docker/go-metrics v0.0.1 // indirect
 	github.com/felixge/httpsnoop v1.0.2 // indirect
 	github.com/felixge/httpsnoop v1.0.2 // indirect
-	github.com/fvbommel/sortorder v1.0.1 // indirect
+	github.com/fvbommel/sortorder v1.0.2 // indirect
 	github.com/go-logr/logr v1.2.2 // indirect
 	github.com/go-logr/logr v1.2.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/gofrs/flock v0.8.0 // indirect
 	github.com/gofrs/flock v0.8.0 // indirect
-	github.com/gogo/googleapis v1.4.0 // indirect
+	github.com/gogo/googleapis v1.4.1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/go-cmp v0.5.7 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/gorilla/mux v1.8.0 // indirect
 	github.com/gorilla/mux v1.8.0 // indirect
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
 	github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
 	github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
-	github.com/imdario/mergo v0.3.12 // indirect
+	github.com/imdario/mergo v0.3.13 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
-	github.com/klauspost/compress v1.13.5 // indirect
+	github.com/klauspost/compress v1.15.1 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
-	github.com/miekg/pkcs11 v1.0.3 // indirect
-	github.com/mitchellh/mapstructure v1.4.3 // indirect
+	github.com/miekg/pkcs11 v1.1.1 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/moby/locker v1.0.1 // indirect
 	github.com/moby/locker v1.0.1 // indirect
-	github.com/moby/sys/mount v0.2.0 // indirect
-	github.com/moby/sys/mountinfo v0.5.0 // indirect
 	github.com/moby/sys/signal v0.6.0 // indirect
 	github.com/moby/sys/signal v0.6.0 // indirect
 	github.com/moby/sys/symlink v0.2.0 // indirect
 	github.com/moby/sys/symlink v0.2.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/opencontainers/runc v1.1.0 // indirect
+	github.com/opencontainers/runc v1.1.2 // indirect
 	github.com/pelletier/go-toml v1.9.4 // indirect
 	github.com/pelletier/go-toml v1.9.4 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_golang v1.11.0 // indirect
+	github.com/prometheus/client_golang v1.12.1 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
-	github.com/prometheus/common v0.30.0 // indirect
+	github.com/prometheus/common v0.32.1 // indirect
 	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/prometheus/procfs v0.7.3 // indirect
-	github.com/qri-io/jsonpointer v0.1.0 // indirect
-	github.com/qri-io/jsonschema v0.1.1 // indirect
-	github.com/sergi/go-diff v1.1.0 // indirect
-	github.com/theupdateframework/notary v0.6.1 // indirect
-	github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect
+	github.com/qri-io/jsonpointer v0.1.1 // indirect
+	github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
+	github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
 	github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
 	github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
 	github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
 	github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
-	go.opentelemetry.io/contrib v0.21.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0 // indirect
-	go.opentelemetry.io/otel v1.3.0 // indirect
-	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect
-	go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect
-	go.opentelemetry.io/otel/metric v0.21.0 // indirect
-	go.opentelemetry.io/otel/sdk v1.3.0 // indirect
-	go.opentelemetry.io/otel/trace v1.3.0 // indirect
-	go.opentelemetry.io/proto/otlp v0.11.0 // indirect
-	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
-	golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
+	go.opentelemetry.io/otel v1.4.1 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
+	go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
+	go.opentelemetry.io/otel/metric v0.27.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.4.1 // indirect
+	go.opentelemetry.io/otel/trace v1.4.1 // indirect
+	go.opentelemetry.io/proto/otlp v0.12.0 // indirect
+	golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
+	golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
-	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
-	golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
+	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
+	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
+	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
-	google.golang.org/grpc v1.43.0 // indirect
+	google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
+	google.golang.org/grpc v1.45.0 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
-	k8s.io/apimachinery v0.22.5 // indirect
-	k8s.io/client-go v0.22.5 // indirect
-	k8s.io/klog/v2 v2.30.0 // indirect
-	k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
-	sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	k8s.io/apimachinery v0.24.1 // indirect; see replace for the actual version used
+	k8s.io/client-go v0.24.1 // indirect; see replace for the actual version used
+	k8s.io/klog/v2 v2.60.1 // indirect
+	k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
 	sigs.k8s.io/yaml v1.2.0 // indirect
 	sigs.k8s.io/yaml v1.2.0 // indirect
 )
 )
 
 
-// (for buildx)
+require (
+	github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
+	github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
+	github.com/zmap/zlint v1.1.0 // indirect
+)
+
 replace (
 replace (
-	github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible
-	github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible
-	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939
-	go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/httptrace/otelhttptrace v0.0.0-20210714055410-d010b05b4939
-	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/otelhttp v0.0.0-20210714055410-d010b05b4939
+	github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible
+	github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
+
+	github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
+
+	// For k8s dependencies, we use a replace directive, to prevent them being
+	// upgraded to the version specified in containerd, which is not relevant to the
+	// version needed.
+	// See https://github.com/docker/buildx/pull/948 for details.
+	// https://github.com/docker/buildx/blob/v0.8.1/go.mod#L62-L64
+	k8s.io/api => k8s.io/api v0.22.4
+	k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
+	k8s.io/client-go => k8s.io/client-go v0.22.4
 )
 )

文件差异内容过多而无法显示
+ 118 - 217
go.sum


+ 15 - 14
pkg/api/api.go

@@ -19,7 +19,6 @@ package api
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
-	"io"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -33,9 +32,9 @@ type Service interface {
 	// Push executes the equivalent ot a `compose push`
 	// Push executes the equivalent ot a `compose push`
 	Push(ctx context.Context, project *types.Project, options PushOptions) error
 	Push(ctx context.Context, project *types.Project, options PushOptions) error
 	// Pull executes the equivalent of a `compose pull`
 	// Pull executes the equivalent of a `compose pull`
-	Pull(ctx context.Context, project *types.Project, opts PullOptions) error
+	Pull(ctx context.Context, project *types.Project, options PullOptions) error
 	// Create executes the equivalent to a `compose create`
 	// Create executes the equivalent to a `compose create`
-	Create(ctx context.Context, project *types.Project, opts CreateOptions) error
+	Create(ctx context.Context, project *types.Project, options CreateOptions) error
 	// Start executes the equivalent to a `compose start`
 	// Start executes the equivalent to a `compose start`
 	Start(ctx context.Context, projectName string, options StartOptions) error
 	Start(ctx context.Context, projectName string, options StartOptions) error
 	// Restart restarts containers
 	// Restart restarts containers
@@ -55,25 +54,25 @@ type Service interface {
 	// Convert translate compose model into backend's native format
 	// Convert translate compose model into backend's native format
 	Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
 	Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
 	// Kill executes the equivalent to a `compose kill`
 	// Kill executes the equivalent to a `compose kill`
-	Kill(ctx context.Context, project *types.Project, options KillOptions) error
+	Kill(ctx context.Context, projectName string, options KillOptions) error
 	// RunOneOffContainer creates a service oneoff container and starts its dependencies
 	// RunOneOffContainer creates a service oneoff container and starts its dependencies
 	RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
 	RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
 	// Remove executes the equivalent to a `compose rm`
 	// Remove executes the equivalent to a `compose rm`
-	Remove(ctx context.Context, project *types.Project, options RemoveOptions) error
+	Remove(ctx context.Context, projectName string, options RemoveOptions) error
 	// Exec executes a command in a running service container
 	// Exec executes a command in a running service container
-	Exec(ctx context.Context, project string, opts RunOptions) (int, error)
+	Exec(ctx context.Context, projectName string, options RunOptions) (int, error)
 	// Copy copies a file/folder between a service container and the local filesystem
 	// Copy copies a file/folder between a service container and the local filesystem
-	Copy(ctx context.Context, project string, options CopyOptions) error
+	Copy(ctx context.Context, projectName string, options CopyOptions) error
 	// Pause executes the equivalent to a `compose pause`
 	// Pause executes the equivalent to a `compose pause`
-	Pause(ctx context.Context, project string, options PauseOptions) error
+	Pause(ctx context.Context, projectName string, options PauseOptions) error
 	// UnPause executes the equivalent to a `compose unpause`
 	// UnPause executes the equivalent to a `compose unpause`
-	UnPause(ctx context.Context, project string, options PauseOptions) error
+	UnPause(ctx context.Context, projectName string, options PauseOptions) error
 	// Top executes the equivalent to a `compose top`
 	// Top executes the equivalent to a `compose top`
 	Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
 	Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
 	// Events executes the equivalent to a `compose events`
 	// Events executes the equivalent to a `compose events`
-	Events(ctx context.Context, project string, options EventsOptions) error
+	Events(ctx context.Context, projectName string, options EventsOptions) error
 	// Port executes the equivalent to a `compose port`
 	// Port executes the equivalent to a `compose port`
-	Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error)
+	Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error)
 	// Images executes the equivalent of a `compose images`
 	// 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) ([]ImageSummary, error)
 }
 }
@@ -92,6 +91,8 @@ type BuildOptions struct {
 	Quiet bool
 	Quiet bool
 	// Services passed in the command line to be built
 	// Services passed in the command line to be built
 	Services []string
 	Services []string
+	// Ssh authentications passed in the command line
+	SSHs []types.SSHKey
 }
 }
 
 
 // CreateOptions group options of the Create API
 // CreateOptions group options of the Create API
@@ -116,6 +117,8 @@ type CreateOptions struct {
 
 
 // StartOptions group options of the Start API
 // StartOptions group options of the Start API
 type StartOptions struct {
 type StartOptions struct {
+	// Project is the compose project used to define this app. Might be nil if user ran `start` just with project name
+	Project *types.Project
 	// Attach to container and forward logs if not nil
 	// Attach to container and forward logs if not nil
 	Attach LogConsumer
 	Attach LogConsumer
 	// AttachTo set the services to attach to
 	// AttachTo set the services to attach to
@@ -216,10 +219,8 @@ type RunOptions struct {
 	Entrypoint        []string
 	Entrypoint        []string
 	Detach            bool
 	Detach            bool
 	AutoRemove        bool
 	AutoRemove        bool
-	Stdin             io.ReadCloser
-	Stdout            io.WriteCloser
-	Stderr            io.WriteCloser
 	Tty               bool
 	Tty               bool
+	Interactive       bool
 	WorkingDir        string
 	WorkingDir        string
 	User              string
 	User              string
 	Environment       []string
 	Environment       []string

+ 18 - 24
pkg/api/proxy.go

@@ -37,9 +37,9 @@ type ServiceProxy struct {
 	PsFn                 func(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error)
 	PsFn                 func(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error)
 	ListFn               func(ctx context.Context, options ListOptions) ([]Stack, error)
 	ListFn               func(ctx context.Context, options ListOptions) ([]Stack, error)
 	ConvertFn            func(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
 	ConvertFn            func(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
-	KillFn               func(ctx context.Context, project *types.Project, options KillOptions) error
+	KillFn               func(ctx context.Context, project string, options KillOptions) error
 	RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
 	RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
-	RemoveFn             func(ctx context.Context, project *types.Project, options RemoveOptions) error
+	RemoveFn             func(ctx context.Context, project string, options RemoveOptions) error
 	ExecFn               func(ctx context.Context, project string, opts RunOptions) (int, error)
 	ExecFn               func(ctx context.Context, project string, opts RunOptions) (int, error)
 	CopyFn               func(ctx context.Context, project string, options CopyOptions) error
 	CopyFn               func(ctx context.Context, project string, options CopyOptions) error
 	PauseFn              func(ctx context.Context, project string, options PauseOptions) error
 	PauseFn              func(ctx context.Context, project string, options PauseOptions) error
@@ -219,14 +219,11 @@ func (s *ServiceProxy) Convert(ctx context.Context, project *types.Project, opti
 }
 }
 
 
 // Kill implements Service interface
 // Kill implements Service interface
-func (s *ServiceProxy) Kill(ctx context.Context, project *types.Project, options KillOptions) error {
+func (s *ServiceProxy) Kill(ctx context.Context, projectName string, options KillOptions) error {
 	if s.KillFn == nil {
 	if s.KillFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	for _, i := range s.interceptors {
-		i(ctx, project)
-	}
-	return s.KillFn(ctx, project, options)
+	return s.KillFn(ctx, projectName, options)
 }
 }
 
 
 // RunOneOffContainer implements Service interface
 // RunOneOffContainer implements Service interface
@@ -241,46 +238,43 @@ func (s *ServiceProxy) RunOneOffContainer(ctx context.Context, project *types.Pr
 }
 }
 
 
 // Remove implements Service interface
 // Remove implements Service interface
-func (s *ServiceProxy) Remove(ctx context.Context, project *types.Project, options RemoveOptions) error {
+func (s *ServiceProxy) Remove(ctx context.Context, projectName string, options RemoveOptions) error {
 	if s.RemoveFn == nil {
 	if s.RemoveFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	for _, i := range s.interceptors {
-		i(ctx, project)
-	}
-	return s.RemoveFn(ctx, project, options)
+	return s.RemoveFn(ctx, projectName, options)
 }
 }
 
 
 // Exec implements Service interface
 // Exec implements Service interface
-func (s *ServiceProxy) Exec(ctx context.Context, project string, options RunOptions) (int, error) {
+func (s *ServiceProxy) Exec(ctx context.Context, projectName string, options RunOptions) (int, error) {
 	if s.ExecFn == nil {
 	if s.ExecFn == nil {
 		return 0, ErrNotImplemented
 		return 0, ErrNotImplemented
 	}
 	}
-	return s.ExecFn(ctx, project, options)
+	return s.ExecFn(ctx, projectName, options)
 }
 }
 
 
 // Copy implements Service interface
 // Copy implements Service interface
-func (s *ServiceProxy) Copy(ctx context.Context, project string, options CopyOptions) error {
+func (s *ServiceProxy) Copy(ctx context.Context, projectName string, options CopyOptions) error {
 	if s.CopyFn == nil {
 	if s.CopyFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	return s.CopyFn(ctx, project, options)
+	return s.CopyFn(ctx, projectName, options)
 }
 }
 
 
 // Pause implements Service interface
 // Pause implements Service interface
-func (s *ServiceProxy) Pause(ctx context.Context, project string, options PauseOptions) error {
+func (s *ServiceProxy) Pause(ctx context.Context, projectName string, options PauseOptions) error {
 	if s.PauseFn == nil {
 	if s.PauseFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	return s.PauseFn(ctx, project, options)
+	return s.PauseFn(ctx, projectName, options)
 }
 }
 
 
 // UnPause implements Service interface
 // UnPause implements Service interface
-func (s *ServiceProxy) UnPause(ctx context.Context, project string, options PauseOptions) error {
+func (s *ServiceProxy) UnPause(ctx context.Context, projectName string, options PauseOptions) error {
 	if s.UnPauseFn == nil {
 	if s.UnPauseFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	return s.UnPauseFn(ctx, project, options)
+	return s.UnPauseFn(ctx, projectName, options)
 }
 }
 
 
 // Top implements Service interface
 // Top implements Service interface
@@ -292,19 +286,19 @@ func (s *ServiceProxy) Top(ctx context.Context, project string, services []strin
 }
 }
 
 
 // Events implements Service interface
 // Events implements Service interface
-func (s *ServiceProxy) Events(ctx context.Context, project string, options EventsOptions) error {
+func (s *ServiceProxy) Events(ctx context.Context, projectName string, options EventsOptions) error {
 	if s.EventsFn == nil {
 	if s.EventsFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	return s.EventsFn(ctx, project, options)
+	return s.EventsFn(ctx, projectName, options)
 }
 }
 
 
 // Port implements Service interface
 // Port implements Service interface
-func (s *ServiceProxy) Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error) {
+func (s *ServiceProxy) Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error) {
 	if s.PortFn == nil {
 	if s.PortFn == nil {
 		return "", 0, ErrNotImplemented
 		return "", 0, ErrNotImplemented
 	}
 	}
-	return s.PortFn(ctx, project, service, port, options)
+	return s.PortFn(ctx, projectName, service, port, options)
 }
 }
 
 
 // Images implements Service interface
 // Images implements Service interface

+ 11 - 9
pkg/compose/attach.go

@@ -48,7 +48,7 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
 	fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
 	fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
 
 
 	for _, container := range containers {
 	for _, container := range containers {
-		err := s.attachContainer(ctx, container, listener, project)
+		err := s.attachContainer(ctx, container, listener)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -56,13 +56,9 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
 	return containers, err
 	return containers, err
 }
 }
 
 
-func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener, project *types.Project) error {
+func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener) error {
 	serviceName := container.Labels[api.ServiceLabel]
 	serviceName := container.Labels[api.ServiceLabel]
 	containerName := getContainerNameWithoutProject(container)
 	containerName := getContainerNameWithoutProject(container)
-	service, err := project.GetService(serviceName)
-	if err != nil {
-		return err
-	}
 
 
 	listener(api.ContainerEvent{
 	listener(api.ContainerEvent{
 		Type:      api.ContainerEventAttach,
 		Type:      api.ContainerEventAttach,
@@ -78,7 +74,13 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
 			Line:      line,
 			Line:      line,
 		})
 		})
 	})
 	})
-	_, _, err = s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w, w)
+
+	inspect, err := s.dockerCli.Client().ContainerInspect(ctx, container.ID)
+	if err != nil {
+		return err
+	}
+
+	_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, w, w)
 	return err
 	return err
 }
 }
 
 
@@ -137,7 +139,7 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s
 func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.WriteCloser, io.ReadCloser, error) {
 func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.WriteCloser, io.ReadCloser, error) {
 	var stdout io.ReadCloser
 	var stdout io.ReadCloser
 	var stdin io.WriteCloser
 	var stdin io.WriteCloser
-	cnx, err := s.apiClient.ContainerAttach(ctx, container, moby.ContainerAttachOptions{
+	cnx, err := s.apiClient().ContainerAttach(ctx, container, moby.ContainerAttachOptions{
 		Stream: true,
 		Stream: true,
 		Stdin:  true,
 		Stdin:  true,
 		Stdout: true,
 		Stdout: true,
@@ -151,7 +153,7 @@ func (s *composeService) getContainerStreams(ctx context.Context, container stri
 	}
 	}
 
 
 	// Fallback to logs API
 	// Fallback to logs API
-	logs, err := s.apiClient.ContainerLogs(ctx, container, moby.ContainerLogsOptions{
+	logs, err := s.apiClient().ContainerLogs(ctx, container, moby.ContainerLogsOptions{
 		ShowStdout: true,
 		ShowStdout: true,
 		ShowStderr: true,
 		ShowStderr: true,
 		Follow:     true,
 		Follow:     true,

+ 76 - 28
pkg/compose/build.go

@@ -19,7 +19,6 @@ package compose
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
-	"os"
 	"path/filepath"
 	"path/filepath"
 
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
@@ -28,11 +27,12 @@ import (
 	_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
 	_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
 	"github.com/docker/buildx/util/buildflags"
 	"github.com/docker/buildx/util/buildflags"
 	xprogress "github.com/docker/buildx/util/progress"
 	xprogress "github.com/docker/buildx/util/progress"
-	"github.com/docker/cli/cli/command"
 	"github.com/docker/docker/pkg/urlutil"
 	"github.com/docker/docker/pkg/urlutil"
 	bclient "github.com/moby/buildkit/client"
 	bclient "github.com/moby/buildkit/client"
 	"github.com/moby/buildkit/session"
 	"github.com/moby/buildkit/session"
 	"github.com/moby/buildkit/session/auth/authprovider"
 	"github.com/moby/buildkit/session/auth/authprovider"
+	"github.com/moby/buildkit/session/secrets/secretsprovider"
+	"github.com/moby/buildkit/session/sshforward/sshprovider"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
@@ -64,7 +64,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 		if service.Build != nil {
 		if service.Build != nil {
 			imageName := getImageName(service, project.Name)
 			imageName := getImageName(service, project.Name)
 			imagesToBuild = append(imagesToBuild, imageName)
 			imagesToBuild = append(imagesToBuild, imageName)
-			buildOptions, err := s.toBuildOptions(project, service, imageName)
+			buildOptions, err := s.toBuildOptions(project, service, imageName, options.SSHs)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
@@ -82,7 +82,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 					Attrs: map[string]string{"ref": image},
 					Attrs: map[string]string{"ref": image},
 				})
 				})
 			}
 			}
-
 			opts[imageName] = buildOptions
 			opts[imageName] = buildOptions
 		}
 		}
 	}
 	}
@@ -161,7 +160,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
 			if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
 			if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
 				continue
 				continue
 			}
 			}
-			opt, err := s.toBuildOptions(project, service, imageName)
+			opt, err := s.toBuildOptions(project, service, imageName, []types.SSHKey{})
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -189,37 +188,29 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
 	for name, info := range imgs {
 	for name, info := range imgs {
 		images[name] = info.ID
 		images[name] = info.ID
 	}
 	}
-	return images, nil
-}
 
 
-func (s *composeService) serverInfo(ctx context.Context) (command.ServerInfo, error) {
-	ping, err := s.apiClient.Ping(ctx)
-	if err != nil {
-		return command.ServerInfo{}, err
-	}
-	serverInfo := command.ServerInfo{
-		HasExperimental: ping.Experimental,
-		OSType:          ping.OSType,
-		BuildkitVersion: ping.BuilderVersion,
+	for _, s := range project.Services {
+		imgName := getImageName(s, project.Name)
+		digest, ok := images[imgName]
+		if ok {
+			s.CustomLabels[api.ImageDigestLabel] = digest
+		}
 	}
 	}
-	return serverInfo, err
+
+	return images, nil
 }
 }
 
 
 func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
 func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
 	if len(opts) == 0 {
 	if len(opts) == 0 {
 		return nil, nil
 		return nil, nil
 	}
 	}
-	serverInfo, err := s.serverInfo(ctx)
-	if err != nil {
-		return nil, err
-	}
-	if buildkitEnabled, err := command.BuildKitEnabled(serverInfo); err != nil || !buildkitEnabled {
-		return s.doBuildClassic(ctx, opts)
+	if buildkitEnabled, err := s.dockerCli.BuildKitEnabled(); err != nil || !buildkitEnabled {
+		return s.doBuildClassic(ctx, project, opts)
 	}
 	}
 	return s.doBuildBuildkit(ctx, project, opts, mode)
 	return s.doBuildBuildkit(ctx, project, opts, mode)
 }
 }
 
 
-func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string) (build.Options, error) {
+func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string, sshKeys []types.SSHKey) (build.Options, error) {
 	var tags []string
 	var tags []string
 	tags = append(tags, imageTag)
 	tags = append(tags, imageTag)
 
 
@@ -244,11 +235,59 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
 		plats = append(plats, p)
 		plats = append(plats, p)
 	}
 	}
 
 
+	cacheFrom, err := buildflags.ParseCacheEntry(service.Build.CacheFrom)
+	if err != nil {
+		return build.Options{}, err
+	}
+	cacheTo, err := buildflags.ParseCacheEntry(service.Build.CacheTo)
+	if err != nil {
+		return build.Options{}, err
+	}
+
+	sessionConfig := []session.Attachable{
+		authprovider.NewDockerAuthProvider(s.stderr()),
+	}
+	if len(sshKeys) > 0 || len(service.Build.SSH) > 0 {
+		sshAgentProvider, err := sshAgentProvider(append(service.Build.SSH, sshKeys...))
+		if err != nil {
+			return build.Options{}, err
+		}
+		sessionConfig = append(sessionConfig, sshAgentProvider)
+	}
+
+	if len(service.Build.Secrets) > 0 {
+		var sources []secretsprovider.Source
+		for _, secret := range service.Build.Secrets {
+			config := project.Secrets[secret.Source]
+			if config.File == "" {
+				return build.Options{}, fmt.Errorf("build.secrets only supports file-based secrets: %q", secret.Source)
+			}
+			sources = append(sources, secretsprovider.Source{
+				ID:       secret.Source,
+				FilePath: config.File,
+			})
+		}
+		store, err := secretsprovider.NewStore(sources)
+		if err != nil {
+			return build.Options{}, err
+		}
+		p := secretsprovider.NewSecretProvider(store)
+		sessionConfig = append(sessionConfig, p)
+	}
+
+	if len(service.Build.Tags) > 0 {
+		tags = append(tags, service.Build.Tags...)
+	}
+
 	return build.Options{
 	return build.Options{
 		Inputs: build.Inputs{
 		Inputs: build.Inputs{
 			ContextPath:    service.Build.Context,
 			ContextPath:    service.Build.Context,
 			DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
 			DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
 		},
 		},
+		CacheFrom:   cacheFrom,
+		CacheTo:     cacheTo,
+		NoCache:     service.Build.NoCache,
+		Pull:        service.Build.Pull,
 		BuildArgs:   buildArgs,
 		BuildArgs:   buildArgs,
 		Tags:        tags,
 		Tags:        tags,
 		Target:      service.Build.Target,
 		Target:      service.Build.Target,
@@ -256,10 +295,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
 		Platforms:   plats,
 		Platforms:   plats,
 		Labels:      service.Build.Labels,
 		Labels:      service.Build.Labels,
 		NetworkMode: service.Build.Network,
 		NetworkMode: service.Build.Network,
-		ExtraHosts:  service.Build.ExtraHosts,
-		Session: []session.Attachable{
-			authprovider.NewDockerAuthProvider(os.Stderr),
-		},
+		ExtraHosts:  service.Build.ExtraHosts.AsList(),
+		Session:     sessionConfig,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -293,3 +330,14 @@ func dockerFilePath(context string, dockerfile string) string {
 	}
 	}
 	return filepath.Join(context, dockerfile)
 	return filepath.Join(context, dockerfile)
 }
 }
+
+func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
+	sshConfig := make([]sshprovider.AgentConfig, 0, len(sshKeys))
+	for _, sshKey := range sshKeys {
+		sshConfig = append(sshConfig, sshprovider.AgentConfig{
+			ID:    sshKey.ID,
+			Paths: []string{sshKey.Path},
+		})
+	}
+	return sshprovider.NewSSHAgentProvider(sshConfig)
+}

+ 3 - 3
pkg/compose/build_buildkit.go

@@ -29,7 +29,7 @@ import (
 
 
 func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
 func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
 	const drivername = "default"
 	const drivername = "default"
-	d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, nil, nil, nil, project.WorkingDir)
+	d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient(), s.configFile(), nil, nil, nil, nil, nil, project.WorkingDir)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -45,10 +45,10 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
 	// build and will lock
 	// build and will lock
 	progressCtx, cancel := context.WithCancel(context.Background())
 	progressCtx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 	defer cancel()
-	w := xprogress.NewPrinter(progressCtx, os.Stdout, mode)
+	w := xprogress.NewPrinter(progressCtx, s.stdout(), os.Stdout, mode)
 
 
 	// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
 	// 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, filepath.Dir(s.configFile.Filename), w)
+	response, err := build.Build(ctx, driverInfo, opts, nil, filepath.Dir(s.configFile().Filename), w)
 	errW := w.Wait()
 	errW := w.Wait()
 	if err == nil {
 	if err == nil {
 		err = errW
 		err = errW

+ 22 - 22
pkg/compose/build_classic.go

@@ -21,12 +21,12 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"io/ioutil"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"runtime"
 	"runtime"
 	"strings"
 	"strings"
 
 
+	"github.com/compose-spec/compose-go/types"
 	buildx "github.com/docker/buildx/build"
 	buildx "github.com/docker/buildx/build"
 	"github.com/docker/cli/cli/command/image/build"
 	"github.com/docker/cli/cli/command/image/build"
 	dockertypes "github.com/docker/docker/api/types"
 	dockertypes "github.com/docker/docker/api/types"
@@ -41,15 +41,24 @@ import (
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
 
 
-func (s *composeService) doBuildClassic(ctx context.Context, opts map[string]buildx.Options) (map[string]string, error) {
+func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, opts map[string]buildx.Options) (map[string]string, error) {
 	var nameDigests = make(map[string]string)
 	var nameDigests = make(map[string]string)
 	var errs error
 	var errs error
-	for name, o := range opts {
+	err := project.WithServices(nil, func(service types.ServiceConfig) error {
+		imageName := getImageName(service, project.Name)
+		o, ok := opts[imageName]
+		if !ok {
+			return nil
+		}
 		digest, err := s.doBuildClassicSimpleImage(ctx, o)
 		digest, err := s.doBuildClassicSimpleImage(ctx, o)
 		if err != nil {
 		if err != nil {
 			errs = multierror.Append(errs, err).ErrorOrNil()
 			errs = multierror.Append(errs, err).ErrorOrNil()
 		}
 		}
-		nameDigests[name] = digest
+		nameDigests[imageName] = digest
+		return nil
+	})
+	if err != nil {
+		return nil, err
 	}
 	}
 
 
 	return nameDigests, errs
 	return nameDigests, errs
@@ -69,8 +78,8 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
 
 
 	dockerfileName := options.Inputs.DockerfilePath
 	dockerfileName := options.Inputs.DockerfilePath
 	specifiedContext := options.Inputs.ContextPath
 	specifiedContext := options.Inputs.ContextPath
-	progBuff := os.Stdout
-	buildBuff := os.Stdout
+	progBuff := s.stdout()
+	buildBuff := s.stdout()
 	if options.ImageIDFile != "" {
 	if options.ImageIDFile != "" {
 		// Avoid leaving a stale file if we eventually fail
 		// Avoid leaving a stale file if we eventually fail
 		if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
 		if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
@@ -143,19 +152,10 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
 		return "", err
 		return "", err
 	}
 	}
 
 
-	// if up to this point nothing has set the context then we must have another
-	// way for sending it(streaming) and set the context to the Dockerfile
-	if dockerfileCtx != nil && buildCtx == nil {
-		buildCtx = dockerfileCtx
-	}
-
 	progressOutput := streamformatter.NewProgressOutput(progBuff)
 	progressOutput := streamformatter.NewProgressOutput(progBuff)
-	var body io.Reader
-	if buildCtx != nil {
-		body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
-	}
+	body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
 
 
-	configFile := s.configFile
+	configFile := s.configFile()
 	creds, err := configFile.GetAllCredentials()
 	creds, err := configFile.GetAllCredentials()
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
@@ -171,7 +171,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
 
 
 	ctx, cancel := context.WithCancel(ctx)
 	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 	defer cancel()
-	response, err := s.apiClient.ImageBuild(ctx, body, buildOptions)
+	response, err := s.apiClient().ImageBuild(ctx, body, buildOptions)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -181,13 +181,13 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
 	aux := func(msg jsonmessage.JSONMessage) {
 	aux := func(msg jsonmessage.JSONMessage) {
 		var result dockertypes.BuildResult
 		var result dockertypes.BuildResult
 		if err := json.Unmarshal(*msg.Aux, &result); err != nil {
 		if err := json.Unmarshal(*msg.Aux, &result); err != nil {
-			fmt.Fprintf(os.Stderr, "Failed to parse aux message: %s", err)
+			fmt.Fprintf(s.stderr(), "Failed to parse aux message: %s", err)
 		} else {
 		} else {
 			imageID = result.ID
 			imageID = result.ID
 		}
 		}
 	}
 	}
 
 
-	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.Fd(), true, aux)
+	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.FD(), true, aux)
 	if err != nil {
 	if err != nil {
 		if jerr, ok := err.(*jsonmessage.JSONError); ok {
 		if jerr, ok := err.(*jsonmessage.JSONError); ok {
 			// If no error code is set, default to 1
 			// If no error code is set, default to 1
@@ -203,7 +203,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
 	// daemon isn't running Windows.
 	// daemon isn't running Windows.
 	if response.OSType != "windows" && runtime.GOOS == "windows" {
 	if response.OSType != "windows" && runtime.GOOS == "windows" {
 		// if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
 		// if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
-		fmt.Fprintln(os.Stdout, "SECURITY WARNING: You are building a Docker "+
+		fmt.Fprintln(s.stdout(), "SECURITY WARNING: You are building a Docker "+
 			"image from Windows against a non-Windows Docker host. All files and "+
 			"image from Windows against a non-Windows Docker host. All files and "+
 			"directories added to build context will have '-rwxr-xr-x' permissions. "+
 			"directories added to build context will have '-rwxr-xr-x' permissions. "+
 			"It is recommended to double check and reset permissions for sensitive "+
 			"It is recommended to double check and reset permissions for sensitive "+
@@ -214,7 +214,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
 		if imageID == "" {
 		if imageID == "" {
 			return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
 			return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
 		}
 		}
-		if err := ioutil.WriteFile(options.ImageIDFile, []byte(imageID), 0666); err != nil {
+		if err := os.WriteFile(options.ImageIDFile, []byte(imageID), 0666); err != nil {
 			return "", err
 			return "", err
 		}
 		}
 	}
 	}

+ 91 - 10
pkg/compose/compose.go

@@ -21,15 +21,18 @@ import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
+	"io"
 	"strings"
 	"strings"
 
 
-	"github.com/docker/compose/v2/pkg/api"
-	"github.com/pkg/errors"
-
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/config/configfile"
 	"github.com/docker/cli/cli/config/configfile"
+	"github.com/docker/cli/cli/streams"
+	"github.com/docker/compose/v2/pkg/api"
 	moby "github.com/docker/docker/api/types"
 	moby "github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/client"
+	"github.com/pkg/errors"
 	"github.com/sanathkr/go-yaml"
 	"github.com/sanathkr/go-yaml"
 )
 )
 
 
@@ -37,19 +40,41 @@ import (
 var Separator = "-"
 var Separator = "-"
 
 
 // NewComposeService create a local implementation of the compose.Service API
 // NewComposeService create a local implementation of the compose.Service API
-func NewComposeService(apiClient client.APIClient, configFile *configfile.ConfigFile) api.Service {
+func NewComposeService(dockerCli command.Cli) api.Service {
 	return &composeService{
 	return &composeService{
-		apiClient:  apiClient,
-		configFile: configFile,
+		dockerCli: dockerCli,
 	}
 	}
 }
 }
 
 
 type composeService struct {
 type composeService struct {
-	apiClient  client.APIClient
-	configFile *configfile.ConfigFile
+	dockerCli command.Cli
+}
+
+func (s *composeService) apiClient() client.APIClient {
+	return s.dockerCli.Client()
+}
+
+func (s *composeService) configFile() *configfile.ConfigFile {
+	return s.dockerCli.ConfigFile()
+}
+
+func (s *composeService) stdout() *streams.Out {
+	return s.dockerCli.Out()
+}
+
+func (s *composeService) stdin() *streams.In {
+	return s.dockerCli.In()
+}
+
+func (s *composeService) stderr() io.Writer {
+	return s.dockerCli.Err()
 }
 }
 
 
 func getCanonicalContainerName(c moby.Container) string {
 func getCanonicalContainerName(c moby.Container) string {
+	if len(c.Names) == 0 {
+		// corner case, sometime happens on removal. return short ID as a safeguard value
+		return c.ID[:12]
+	}
 	// Names return container canonical name /foo  + link aliases /linked_by/foo
 	// Names return container canonical name /foo  + link aliases /linked_by/foo
 	for _, name := range c.Names {
 	for _, name := range c.Names {
 		if strings.LastIndex(name, "/") == 0 {
 		if strings.LastIndex(name, "/") == 0 {
@@ -100,7 +125,7 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
 		Name: projectName,
 		Name: projectName,
 	}
 	}
 	if len(containers) == 0 {
 	if len(containers) == 0 {
-		return project, errors.New("no such project: " + projectName)
+		return project, errors.Wrap(api.ErrNotFound, fmt.Sprintf("no container found for project %q", projectName))
 	}
 	}
 	set := map[string]*types.ServiceConfig{}
 	set := map[string]*types.ServiceConfig{}
 	for _, c := range containers {
 	for _, c := range containers {
@@ -140,7 +165,7 @@ SERVICES:
 				continue SERVICES
 				continue SERVICES
 			}
 			}
 		}
 		}
-		return project, errors.New("no such service: " + qs)
+		return project, errors.Wrapf(api.ErrNotFound, "no such service: %q", qs)
 	}
 	}
 	err := project.ForServices(services)
 	err := project.ForServices(services)
 	if err != nil {
 	if err != nil {
@@ -149,3 +174,59 @@ SERVICES:
 
 
 	return project, nil
 	return project, nil
 }
 }
+
+// actualState list resources labelled by projectName to rebuild compose project model
+func (s *composeService) actualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) {
+	var containers Containers
+	// don't filter containers by options.Services so projectFromName can rebuild project with all existing resources
+	containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	project, err := s.projectFromName(containers, projectName, services...)
+	if err != nil && !api.IsNotFoundError(err) {
+		return nil, nil, err
+	}
+
+	if len(services) > 0 {
+		containers = containers.filter(isService(services...))
+	}
+	return containers, project, nil
+}
+
+func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
+	volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
+	if err != nil {
+		return nil, err
+	}
+
+	actual := types.Volumes{}
+	for _, vol := range volumes.Volumes {
+		actual[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
+			Name:   vol.Name,
+			Driver: vol.Driver,
+			Labels: vol.Labels,
+		}
+	}
+	return actual, nil
+}
+
+func (s *composeService) actualNetworks(ctx context.Context, projectName string) (types.Networks, error) {
+	networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
+		Filters: filters.NewArgs(projectFilter(projectName)),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	actual := types.Networks{}
+	for _, net := range networks {
+		actual[net.Labels[api.NetworkLabel]] = types.NetworkConfig{
+			Name:   net.Name,
+			Driver: net.Driver,
+			Labels: net.Labels,
+		}
+	}
+	return actual, nil
+}

+ 35 - 20
pkg/compose/containers.go

@@ -18,14 +18,13 @@ package compose
 
 
 import (
 import (
 	"context"
 	"context"
+	"fmt"
 	"sort"
 	"sort"
-	"strconv"
-
-	moby "github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/filters"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
+	moby "github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 )
 )
 
 
 // Containers is a set of moby Container
 // Containers is a set of moby Container
@@ -41,7 +40,22 @@ const (
 
 
 func (s *composeService) getContainers(ctx context.Context, project string, oneOff oneOff, stopped bool, selectedServices ...string) (Containers, error) {
 func (s *composeService) getContainers(ctx context.Context, project string, oneOff oneOff, stopped bool, selectedServices ...string) (Containers, error) {
 	var containers Containers
 	var containers Containers
-	f := []filters.KeyValuePair{projectFilter(project)}
+	f := getDefaultFilters(project, oneOff, selectedServices...)
+	containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
+		Filters: filters.NewArgs(f...),
+		All:     stopped,
+	})
+	if err != nil {
+		return nil, err
+	}
+	if len(selectedServices) > 1 {
+		containers = containers.filter(isService(selectedServices...))
+	}
+	return containers, nil
+}
+
+func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...string) []filters.KeyValuePair {
+	f := []filters.KeyValuePair{projectFilter(projectName)}
 	if len(selectedServices) == 1 {
 	if len(selectedServices) == 1 {
 		f = append(f, serviceFilter(selectedServices[0]))
 		f = append(f, serviceFilter(selectedServices[0]))
 	}
 	}
@@ -52,17 +66,26 @@ func (s *composeService) getContainers(ctx context.Context, project string, oneO
 		f = append(f, oneOffFilter(false))
 		f = append(f, oneOffFilter(false))
 	case oneOffInclude:
 	case oneOffInclude:
 	}
 	}
-	containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
-		Filters: filters.NewArgs(f...),
-		All:     stopped,
+	return f
+}
+
+func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName string, oneOff oneOff, stopped bool, serviceName string, containerIndex int) (moby.Container, error) {
+	defaultFilters := getDefaultFilters(projectName, oneOff, serviceName)
+	defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
+	containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
+		Filters: filters.NewArgs(
+			defaultFilters...,
+		),
+		All: stopped,
 	})
 	})
 	if err != nil {
 	if err != nil {
-		return nil, err
+		return moby.Container{}, err
 	}
 	}
-	if len(selectedServices) > 1 {
-		containers = containers.filter(isService(selectedServices...))
+	if len(containers) < 1 {
+		return moby.Container{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)
 	}
 	}
-	return containers, nil
+	container := containers[0]
+	return container, nil
 }
 }
 
 
 // containerPredicate define a predicate we want container to satisfy for filtering operations
 // containerPredicate define a predicate we want container to satisfy for filtering operations
@@ -87,14 +110,6 @@ func isNotOneOff(c moby.Container) bool {
 	return !ok || v == "False"
 	return !ok || v == "False"
 }
 }
 
 
-func indexed(index int) containerPredicate {
-	return func(c moby.Container) bool {
-		number := c.Labels[api.ContainerNumberLabel]
-		idx, err := strconv.Atoi(number)
-		return err == nil && index == idx
-	}
-}
-
 // filter return Containers with elements to match predicate
 // filter return Containers with elements to match predicate
 func (containers Containers) filter(predicate containerPredicate) Containers {
 func (containers Containers) filter(predicate containerPredicate) Containers {
 	var filtered Containers
 	var filtered Containers

+ 35 - 22
pkg/compose/convergence.go

@@ -180,26 +180,20 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
 			// Scale Down
 			// Scale Down
 			container := container
 			container := container
 			eg.Go(func() error {
 			eg.Go(func() error {
-				err := c.service.apiClient.ContainerStop(ctx, container.ID, timeout)
+				err := c.service.apiClient().ContainerStop(ctx, container.ID, timeout)
 				if err != nil {
 				if err != nil {
 					return err
 					return err
 				}
 				}
-				return c.service.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
+				return c.service.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
 			})
 			})
 			continue
 			continue
 		}
 		}
 
 
-		if recreate == api.RecreateNever {
-			continue
-		}
-		// Re-create diverged containers
-		configHash, err := ServiceHash(service)
+		mustRecreate, err := mustRecreate(service, container, recreate)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		name := getContainerProgressName(container)
-		diverged := container.Labels[api.ConfigHashLabel] != configHash
-		if diverged || recreate == api.RecreateForce || service.Extensions[extLifecycle] == forceRecreate {
+		if mustRecreate {
 			i, container := i, container
 			i, container := i, container
 			eg.Go(func() error {
 			eg.Go(func() error {
 				recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
 				recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
@@ -211,6 +205,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
 
 
 		// Enforce non-diverged containers are running
 		// Enforce non-diverged containers are running
 		w := progress.ContextWriter(ctx)
 		w := progress.ContextWriter(ctx)
+		name := getContainerProgressName(container)
 		switch container.State {
 		switch container.State {
 		case ContainerRunning:
 		case ContainerRunning:
 			w.Event(progress.RunningEvent(name))
 			w.Event(progress.RunningEvent(name))
@@ -249,6 +244,22 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
 	return err
 	return err
 }
 }
 
 
+func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
+	if policy == api.RecreateNever {
+		return false, nil
+	}
+	if policy == api.RecreateForce || expected.Extensions[extLifecycle] == forceRecreate {
+		return true, nil
+	}
+	configHash, err := ServiceHash(expected)
+	if err != nil {
+		return false, err
+	}
+	configChanged := actual.Labels[api.ConfigHashLabel] != configHash
+	imageUpdated := actual.Labels[api.ImageDigestLabel] != expected.CustomLabels[api.ImageDigestLabel]
+	return configChanged || imageUpdated, nil
+}
+
 func getContainerName(projectName string, service types.ServiceConfig, number int) string {
 func getContainerName(projectName string, service types.ServiceConfig, number int) string {
 	name := strings.Join([]string{projectName, service.Name, strconv.Itoa(number)}, Separator)
 	name := strings.Join([]string{projectName, service.Name, strconv.Itoa(number)}, Separator)
 	if service.ContainerName != "" {
 	if service.ContainerName != "" {
@@ -395,13 +406,13 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 	var created moby.Container
 	var created moby.Container
 	w := progress.ContextWriter(ctx)
 	w := progress.ContextWriter(ctx)
 	w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
 	w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
-	err := s.apiClient.ContainerStop(ctx, replaced.ID, timeout)
+	err := s.apiClient().ContainerStop(ctx, replaced.ID, timeout)
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
 	name := getCanonicalContainerName(replaced)
 	name := getCanonicalContainerName(replaced)
 	tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
 	tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
-	err = s.apiClient.ContainerRename(ctx, replaced.ID, tmpName)
+	err = s.apiClient().ContainerRename(ctx, replaced.ID, tmpName)
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
@@ -419,7 +430,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
-	err = s.apiClient.ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
+	err = s.apiClient().ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
@@ -444,7 +455,7 @@ func setDependentLifecycle(project *types.Project, service string, strategy stri
 func (s *composeService) startContainer(ctx context.Context, container moby.Container) error {
 func (s *composeService) startContainer(ctx context.Context, container moby.Container) error {
 	w := progress.ContextWriter(ctx)
 	w := progress.ContextWriter(ctx)
 	w.Event(progress.NewEvent(getContainerProgressName(container), progress.Working, "Restart"))
 	w.Event(progress.NewEvent(getContainerProgressName(container), progress.Working, "Restart"))
-	err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
+	err := s.apiClient().ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -468,11 +479,11 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
 		}
 		}
 		plat = &p
 		plat = &p
 	}
 	}
-	response, err := s.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
+	response, err := s.apiClient().ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
-	inspectedContainer, err := s.apiClient.ContainerInspect(ctx, response.ID)
+	inspectedContainer, err := s.apiClient().ContainerInspect(ctx, response.ID)
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
@@ -502,7 +513,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
 			if shortIDAliasExists(created.ID, val.Aliases...) {
 			if shortIDAliasExists(created.ID, val.Aliases...) {
 				continue
 				continue
 			}
 			}
-			err = s.apiClient.NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
+			err = s.apiClient().NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
 			if err != nil {
 			if err != nil {
 				return created, err
 				return created, err
 			}
 			}
@@ -512,6 +523,8 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
 			return created, err
 			return created, err
 		}
 		}
 	}
 	}
+
+	err = s.injectSecrets(ctx, project, service, created.ID)
 	return created, err
 	return created, err
 }
 }
 
 
@@ -596,7 +609,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
 			IPv6Address: ipv6Address,
 			IPv6Address: ipv6Address,
 		}
 		}
 	}
 	}
-	err := s.apiClient.NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
+	err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
 		Aliases:           aliases,
 		Aliases:           aliases,
 		IPAddress:         ipv4Address,
 		IPAddress:         ipv4Address,
 		GlobalIPv6Address: ipv6Address,
 		GlobalIPv6Address: ipv6Address,
@@ -619,7 +632,7 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
 		return false, nil
 		return false, nil
 	}
 	}
 	for _, c := range containers {
 	for _, c := range containers {
-		container, err := s.apiClient.ContainerInspect(ctx, c.ID)
+		container, err := s.apiClient().ContainerInspect(ctx, c.ID)
 		if err != nil {
 		if err != nil {
 			return false, err
 			return false, err
 		}
 		}
@@ -651,7 +664,7 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
 		return false, 0, err
 		return false, 0, err
 	}
 	}
 	for _, c := range containers {
 	for _, c := range containers {
-		container, err := s.apiClient.ContainerInspect(ctx, c.ID)
+		container, err := s.apiClient().ContainerInspect(ctx, c.ID)
 		if err != nil {
 		if err != nil {
 			return false, 0, err
 			return false, 0, err
 		}
 		}
@@ -671,7 +684,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+	containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
 		Filters: filters.NewArgs(
 		Filters: filters.NewArgs(
 			projectFilter(project.Name),
 			projectFilter(project.Name),
 			serviceFilter(service.Name),
 			serviceFilter(service.Name),
@@ -700,7 +713,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
 		eg.Go(func() error {
 		eg.Go(func() error {
 			eventName := getContainerProgressName(container)
 			eventName := getContainerProgressName(container)
 			w.Event(progress.StartingEvent(eventName))
 			w.Event(progress.StartingEvent(eventName))
-			err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
+			err := s.apiClient().ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
 			if err == nil {
 			if err == nil {
 				w.Event(progress.StartedEvent(eventName))
 				w.Event(progress.StartedEvent(eventName))
 			}
 			}

+ 21 - 7
pkg/compose/convergence_test.go

@@ -74,8 +74,11 @@ func TestServiceLinks(t *testing.T) {
 	t.Run("service links default", func(t *testing.T) {
 	t.Run("service links default", func(t *testing.T) {
 		mockCtrl := gomock.NewController(t)
 		mockCtrl := gomock.NewController(t)
 		defer mockCtrl.Finish()
 		defer mockCtrl.Finish()
+
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
-		tested.apiClient = apiClient
+		cli := mocks.NewMockCli(mockCtrl)
+		tested.dockerCli = cli
+		cli.EXPECT().Client().Return(apiClient).AnyTimes()
 
 
 		s.Links = []string{"db"}
 		s.Links = []string{"db"}
 
 
@@ -95,7 +98,9 @@ func TestServiceLinks(t *testing.T) {
 		mockCtrl := gomock.NewController(t)
 		mockCtrl := gomock.NewController(t)
 		defer mockCtrl.Finish()
 		defer mockCtrl.Finish()
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
-		tested.apiClient = apiClient
+		cli := mocks.NewMockCli(mockCtrl)
+		tested.dockerCli = cli
+		cli.EXPECT().Client().Return(apiClient).AnyTimes()
 
 
 		s.Links = []string{"db:db"}
 		s.Links = []string{"db:db"}
 
 
@@ -115,7 +120,9 @@ func TestServiceLinks(t *testing.T) {
 		mockCtrl := gomock.NewController(t)
 		mockCtrl := gomock.NewController(t)
 		defer mockCtrl.Finish()
 		defer mockCtrl.Finish()
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
-		tested.apiClient = apiClient
+		cli := mocks.NewMockCli(mockCtrl)
+		tested.dockerCli = cli
+		cli.EXPECT().Client().Return(apiClient).AnyTimes()
 
 
 		s.Links = []string{"db:dbname"}
 		s.Links = []string{"db:dbname"}
 
 
@@ -135,7 +142,9 @@ func TestServiceLinks(t *testing.T) {
 		mockCtrl := gomock.NewController(t)
 		mockCtrl := gomock.NewController(t)
 		defer mockCtrl.Finish()
 		defer mockCtrl.Finish()
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
-		tested.apiClient = apiClient
+		cli := mocks.NewMockCli(mockCtrl)
+		tested.dockerCli = cli
+		cli.EXPECT().Client().Return(apiClient).AnyTimes()
 
 
 		s.Links = []string{"db:dbname"}
 		s.Links = []string{"db:dbname"}
 		s.ExternalLinks = []string{"db1:db2"}
 		s.ExternalLinks = []string{"db1:db2"}
@@ -159,7 +168,9 @@ func TestServiceLinks(t *testing.T) {
 		mockCtrl := gomock.NewController(t)
 		mockCtrl := gomock.NewController(t)
 		defer mockCtrl.Finish()
 		defer mockCtrl.Finish()
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
 		apiClient := mocks.NewMockAPIClient(mockCtrl)
-		tested.apiClient = apiClient
+		cli := mocks.NewMockCli(mockCtrl)
+		tested.dockerCli = cli
+		cli.EXPECT().Client().Return(apiClient).AnyTimes()
 
 
 		s.Links = []string{}
 		s.Links = []string{}
 		s.ExternalLinks = []string{}
 		s.ExternalLinks = []string{}
@@ -189,8 +200,11 @@ func TestServiceLinks(t *testing.T) {
 func TestWaitDependencies(t *testing.T) {
 func TestWaitDependencies(t *testing.T) {
 	mockCtrl := gomock.NewController(t)
 	mockCtrl := gomock.NewController(t)
 	defer mockCtrl.Finish()
 	defer mockCtrl.Finish()
-	api := mocks.NewMockAPIClient(mockCtrl)
-	tested.apiClient = api
+
+	apiClient := mocks.NewMockAPIClient(mockCtrl)
+	cli := mocks.NewMockCli(mockCtrl)
+	tested.dockerCli = cli
+	cli.EXPECT().Client().Return(apiClient).AnyTimes()
 
 
 	t.Run("should skip dependencies with scale 0", func(t *testing.T) {
 	t.Run("should skip dependencies with scale 0", func(t *testing.T) {
 		dbService := types.ServiceConfig{Name: "db", Scale: 0}
 		dbService := types.ServiceConfig{Name: "db", Scale: 0}

+ 50 - 29
pkg/compose/cp.go

@@ -42,59 +42,80 @@ const (
 	acrossServices = fromService | toService
 	acrossServices = fromService | toService
 )
 )
 
 
-func (s *composeService) Copy(ctx context.Context, project string, opts api.CopyOptions) error {
-	srcService, srcPath := splitCpArg(opts.Source)
-	destService, dstPath := splitCpArg(opts.Destination)
+func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
+	projectName = strings.ToLower(projectName)
+	srcService, srcPath := splitCpArg(options.Source)
+	destService, dstPath := splitCpArg(options.Destination)
 
 
 	var direction copyDirection
 	var direction copyDirection
 	var serviceName string
 	var serviceName string
+	var copyFunc func(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error
 	if srcService != "" {
 	if srcService != "" {
 		direction |= fromService
 		direction |= fromService
 		serviceName = srcService
 		serviceName = srcService
+		copyFunc = s.copyFromContainer
 
 
 		// copying from multiple containers of a services doesn't make sense.
 		// copying from multiple containers of a services doesn't make sense.
-		if opts.All {
+		if options.All {
 			return errors.New("cannot use the --all flag when copying from a service")
 			return errors.New("cannot use the --all flag when copying from a service")
 		}
 		}
 	}
 	}
 	if destService != "" {
 	if destService != "" {
 		direction |= toService
 		direction |= toService
 		serviceName = destService
 		serviceName = destService
+		copyFunc = s.copyToContainer
 	}
 	}
-
-	containers, err := s.getContainers(ctx, project, oneOffExclude, true, serviceName)
-	if err != nil {
-		return err
+	if direction == acrossServices {
+		return errors.New("copying between services is not supported")
 	}
 	}
 
 
-	if len(containers) < 1 {
-		return fmt.Errorf("no container found for service %q", serviceName)
+	if direction == 0 {
+		return errors.New("unknown copy direction")
 	}
 	}
 
 
-	if !opts.All {
-		containers = containers.filter(indexed(opts.Index))
+	containers, err := s.listContainersTargetedForCopy(ctx, projectName, options.Index, direction, serviceName)
+	if err != nil {
+		return err
 	}
 	}
 
 
 	g := errgroup.Group{}
 	g := errgroup.Group{}
 	for _, container := range containers {
 	for _, container := range containers {
 		containerID := container.ID
 		containerID := container.ID
 		g.Go(func() error {
 		g.Go(func() error {
-			switch direction {
-			case fromService:
-				return s.copyFromContainer(ctx, containerID, srcPath, dstPath, opts)
-			case toService:
-				return s.copyToContainer(ctx, containerID, srcPath, dstPath, opts)
-			case acrossServices:
-				return errors.New("copying between services is not supported")
-			default:
-				return errors.New("unknown copy direction")
-			}
+			return copyFunc(ctx, containerID, srcPath, dstPath, options)
 		})
 		})
 	}
 	}
 
 
 	return g.Wait()
 	return g.Wait()
 }
 }
 
 
+func (s *composeService) listContainersTargetedForCopy(ctx context.Context, projectName string, index int, direction copyDirection, serviceName string) (Containers, error) {
+	var containers Containers
+	var err error
+	switch {
+	case index > 0:
+		container, err := s.getSpecifiedContainer(ctx, projectName, oneOffExclude, true, serviceName, index)
+		if err != nil {
+			return nil, err
+		}
+		return append(containers, container), nil
+	default:
+		containers, err = s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
+		if err != nil {
+			return nil, err
+		}
+
+		if len(containers) < 1 {
+			return nil, fmt.Errorf("no container found for service %q", serviceName)
+		}
+		if direction == fromService {
+			return containers[:1], err
+
+		}
+		return containers, err
+	}
+}
+
 func (s *composeService) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error {
 func (s *composeService) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error {
 	var err error
 	var err error
 	if srcPath != "-" {
 	if srcPath != "-" {
@@ -107,7 +128,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
 
 
 	// Prepare destination copy info by stat-ing the container path.
 	// Prepare destination copy info by stat-ing the container path.
 	dstInfo := archive.CopyInfo{Path: dstPath}
 	dstInfo := archive.CopyInfo{Path: dstPath}
-	dstStat, err := s.apiClient.ContainerStatPath(ctx, containerID, dstPath)
+	dstStat, err := s.apiClient().ContainerStatPath(ctx, containerID, dstPath)
 
 
 	// If the destination is a symbolic link, we should evaluate it.
 	// If the destination is a symbolic link, we should evaluate it.
 	if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
 	if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
@@ -119,7 +140,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
 		}
 		}
 
 
 		dstInfo.Path = linkTarget
 		dstInfo.Path = linkTarget
-		dstStat, err = s.apiClient.ContainerStatPath(ctx, containerID, linkTarget)
+		dstStat, err = s.apiClient().ContainerStatPath(ctx, containerID, linkTarget)
 	}
 	}
 
 
 	// Validate the destination path
 	// Validate the destination path
@@ -143,7 +164,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
 	)
 	)
 
 
 	if srcPath == "-" {
 	if srcPath == "-" {
-		content = os.Stdin
+		content = s.stdin()
 		resolvedDstPath = dstInfo.Path
 		resolvedDstPath = dstInfo.Path
 		if !dstInfo.IsDir {
 		if !dstInfo.IsDir {
 			return errors.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
 			return errors.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
@@ -187,7 +208,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
 		AllowOverwriteDirWithFile: false,
 		AllowOverwriteDirWithFile: false,
 		CopyUIDGID:                opts.CopyUIDGID,
 		CopyUIDGID:                opts.CopyUIDGID,
 	}
 	}
-	return s.apiClient.CopyToContainer(ctx, containerID, resolvedDstPath, content, options)
+	return s.apiClient().CopyToContainer(ctx, containerID, resolvedDstPath, content, options)
 }
 }
 
 
 func (s *composeService) copyFromContainer(ctx context.Context, containerID, srcPath, dstPath string, opts api.CopyOptions) error {
 func (s *composeService) copyFromContainer(ctx context.Context, containerID, srcPath, dstPath string, opts api.CopyOptions) error {
@@ -207,7 +228,7 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
 	// if client requests to follow symbol link, then must decide target file to be copied
 	// if client requests to follow symbol link, then must decide target file to be copied
 	var rebaseName string
 	var rebaseName string
 	if opts.FollowLink {
 	if opts.FollowLink {
-		srcStat, err := s.apiClient.ContainerStatPath(ctx, containerID, srcPath)
+		srcStat, err := s.apiClient().ContainerStatPath(ctx, containerID, srcPath)
 
 
 		// If the destination is a symbolic link, we should follow it.
 		// If the destination is a symbolic link, we should follow it.
 		if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
 		if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
@@ -223,14 +244,14 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
 		}
 		}
 	}
 	}
 
 
-	content, stat, err := s.apiClient.CopyFromContainer(ctx, containerID, srcPath)
+	content, stat, err := s.apiClient().CopyFromContainer(ctx, containerID, srcPath)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	defer content.Close() //nolint:errcheck
 	defer content.Close() //nolint:errcheck
 
 
 	if dstPath == "-" {
 	if dstPath == "-" {
-		_, err = io.Copy(os.Stdout, content)
+		_, err = io.Copy(s.stdout(), content)
 		return err
 		return err
 	}
 	}
 
 

+ 124 - 104
pkg/compose/create.go

@@ -21,7 +21,7 @@ import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"path"
 	"path"
 	"path/filepath"
 	"path/filepath"
 	"strconv"
 	"strconv"
@@ -31,6 +31,7 @@ import (
 	moby "github.com/docker/docker/api/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/blkiodev"
 	"github.com/docker/docker/api/types/blkiodev"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/mount"
 	"github.com/docker/docker/api/types/mount"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/docker/api/types/strslice"
@@ -173,13 +174,21 @@ func prepareServicesDependsOn(p *types.Project) error {
 			dependencies = append(dependencies, spec[0])
 			dependencies = append(dependencies, spec[0])
 		}
 		}
 
 
+		for _, link := range service.Links {
+			dependencies = append(dependencies, strings.Split(link, ":")[0])
+		}
+
 		if len(dependencies) == 0 {
 		if len(dependencies) == 0 {
 			continue
 			continue
 		}
 		}
 		if service.DependsOn == nil {
 		if service.DependsOn == nil {
 			service.DependsOn = make(types.DependsOnConfig)
 			service.DependsOn = make(types.DependsOnConfig)
 		}
 		}
-		deps, err := p.GetServices(dependencies...)
+
+		// Verify dependencies exist in the project, whether disabled or not
+		projAllServices := types.Project{}
+		projAllServices.Services = p.AllServices()
+		deps, err := projAllServices.GetServices(dependencies...)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -255,7 +264,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
 		return nil, nil, nil, err
 		return nil, nil, nil, err
 	}
 	}
 
 
-	proxyConfig := types.MappingWithEquals(s.configFile.ParseProxyConfig(s.apiClient.DaemonHost(), nil))
+	proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
 	env := proxyConfig.OverrideBy(service.Environment)
 	env := proxyConfig.OverrideBy(service.Environment)
 
 
 	containerConfig := container.Config{
 	containerConfig := container.Config{
@@ -347,6 +356,11 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
 		volumesFrom = append(volumesFrom, v[len("container:"):])
 		volumesFrom = append(volumesFrom, v[len("container:"):])
 	}
 	}
 
 
+	links, err := s.getLinks(ctx, p.Name, service, number)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
 	securityOpts, err := parseSecurityOpts(p, service.SecurityOpt)
 	securityOpts, err := parseSecurityOpts(p, service.SecurityOpt)
 	if err != nil {
 	if err != nil {
 		return nil, nil, nil, err
 		return nil, nil, nil, err
@@ -371,7 +385,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
 		DNS:            service.DNS,
 		DNS:            service.DNS,
 		DNSSearch:      service.DNSSearch,
 		DNSSearch:      service.DNSSearch,
 		DNSOptions:     service.DNSOpts,
 		DNSOptions:     service.DNSOpts,
-		ExtraHosts:     service.ExtraHosts,
+		ExtraHosts:     service.ExtraHosts.AsList(),
 		SecurityOpt:    securityOpts,
 		SecurityOpt:    securityOpts,
 		UsernsMode:     container.UsernsMode(service.UserNSMode),
 		UsernsMode:     container.UsernsMode(service.UserNSMode),
 		Privileged:     service.Privileged,
 		Privileged:     service.Privileged,
@@ -381,6 +395,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
 		Runtime:        service.Runtime,
 		Runtime:        service.Runtime,
 		LogConfig:      logConfig,
 		LogConfig:      logConfig,
 		GroupAdd:       service.GroupAdd,
 		GroupAdd:       service.GroupAdd,
+		Links:          links,
 	}
 	}
 
 
 	return &containerConfig, &hostConfig, networkConfig, nil
 	return &containerConfig, &hostConfig, networkConfig, nil
@@ -399,7 +414,7 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, error
 			}
 			}
 		}
 		}
 		if con[0] == "seccomp" && con[1] != "unconfined" {
 		if con[0] == "seccomp" && con[1] != "unconfined" {
-			f, err := ioutil.ReadFile(p.RelativePath(con[1]))
+			f, err := os.ReadFile(p.RelativePath(con[1]))
 			if err != nil {
 			if err != nil {
 				return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
 				return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
 			}
 			}
@@ -500,6 +515,7 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
 		CPUShares:          s.CPUShares,
 		CPUShares:          s.CPUShares,
 		CPUPercent:         int64(s.CPUS * 100),
 		CPUPercent:         int64(s.CPUS * 100),
 		CpusetCpus:         s.CPUSet,
 		CpusetCpus:         s.CPUSet,
+		DeviceCgroupRules:  s.DeviceCgroupRules,
 	}
 	}
 
 
 	if s.PidsLimit != 0 {
 	if s.PidsLimit != 0 {
@@ -579,8 +595,12 @@ func setLimits(limits *types.Resource, resources *container.Resources) {
 		resources.Memory = int64(limits.MemoryBytes)
 		resources.Memory = int64(limits.MemoryBytes)
 	}
 	}
 	if limits.NanoCPUs != "" {
 	if limits.NanoCPUs != "" {
-		i, _ := strconv.ParseInt(limits.NanoCPUs, 10, 64)
-		resources.NanoCPUs = i
+		if f, err := strconv.ParseFloat(limits.NanoCPUs, 64); err == nil {
+			resources.NanoCPUs = int64(f * 1e9)
+		}
+	}
+	if limits.PIds > 0 {
+		resources.PidsLimit = &limits.PIds
 	}
 	}
 }
 }
 
 
@@ -693,7 +713,7 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
 	var mounts = []mount.Mount{}
 	var mounts = []mount.Mount{}
 
 
 	image := getImageName(service, p.Name)
 	image := getImageName(service, p.Name)
-	imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
+	imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image)
 	if err != nil {
 	if err != nil {
 		return nil, nil, nil, err
 		return nil, nil, nil, err
 	}
 	}
@@ -708,12 +728,20 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
 MOUNTS:
 MOUNTS:
 	for _, m := range mountOptions {
 	for _, m := range mountOptions {
 		volumeMounts[m.Target] = struct{}{}
 		volumeMounts[m.Target] = struct{}{}
-		// `Bind` API is used when host path need to be created if missing, `Mount` is preferred otherwise
 		if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
 		if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
+			// `Mount` is preferred but does not offer option to created host path if missing
+			// so `Bind` API is used here with raw volume string
+			// see https://github.com/moby/moby/issues/43483
 			for _, v := range service.Volumes {
 			for _, v := range service.Volumes {
-				if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
-					binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, getBindMode(v.Bind, m.ReadOnly)))
-					continue MOUNTS
+				if v.Target == m.Target {
+					switch {
+					case string(m.Type) != v.Type:
+						v.Source = m.Source
+						fallthrough
+					case v.Bind != nil && v.Bind.CreateHostPath:
+						binds = append(binds, v.String())
+						continue MOUNTS
+					}
 				}
 				}
 			}
 			}
 		}
 		}
@@ -722,23 +750,6 @@ MOUNTS:
 	return volumeMounts, binds, mounts, nil
 	return volumeMounts, binds, mounts, nil
 }
 }
 
 
-func getBindMode(bind *types.ServiceVolumeBind, readOnly bool) string {
-	mode := "rw"
-
-	if readOnly {
-		mode = "ro"
-	}
-
-	switch bind.SELinux {
-	case types.SELinuxShared:
-		mode += ",z"
-	case types.SELinuxPrivate:
-		mode += ",Z"
-	}
-
-	return mode
-}
-
 func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
 func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
 	var mounts = map[string]mount.Mount{}
 	var mounts = map[string]mount.Mount{}
 	if inherit != nil {
 	if inherit != nil {
@@ -878,6 +889,10 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
 			return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
 			return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
 		}
 		}
 
 
+		if definedSecret.Environment != "" {
+			continue
+		}
+
 		mount, err := buildMount(p, types.ServiceVolumeConfig{
 		mount, err := buildMount(p, types.ServiceVolumeConfig{
 			Type:     types.VolumeTypeBind,
 			Type:     types.VolumeTypeBind,
 			Source:   definedSecret.File,
 			Source:   definedSecret.File,
@@ -921,10 +936,14 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
 		}
 		}
 	}
 	}
 
 
-	bind, vol, tmpfs := buildMountOptions(volume)
+	bind, vol, tmpfs := buildMountOptions(project, volume)
 
 
 	volume.Target = path.Clean(volume.Target)
 	volume.Target = path.Clean(volume.Target)
 
 
+	if bind != nil {
+		volume.Type = types.VolumeTypeBind
+	}
+
 	return mount.Mount{
 	return mount.Mount{
 		Type:          mount.Type(volume.Type),
 		Type:          mount.Type(volume.Type),
 		Source:        source,
 		Source:        source,
@@ -937,7 +956,7 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
 	}, nil
 	}, nil
 }
 }
 
 
-func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
+func buildMountOptions(project types.Project, volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
 	switch volume.Type {
 	switch volume.Type {
 	case "bind":
 	case "bind":
 		if volume.Volume != nil {
 		if volume.Volume != nil {
@@ -954,6 +973,11 @@ func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *m
 		if volume.Tmpfs != nil {
 		if volume.Tmpfs != nil {
 			logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
 			logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
 		}
 		}
+		if v, ok := project.Volumes[volume.Source]; ok && v.DriverOpts["o"] == types.VolumeTypeBind {
+			return buildBindOption(&types.ServiceVolumeBind{
+				CreateHostPath: true,
+			}), nil, nil
+		}
 		return nil, buildVolumeOptions(volume.Volume), nil
 		return nil, buildVolumeOptions(volume.Volume), nil
 	case "tmpfs":
 	case "tmpfs":
 		if volume.Bind != nil {
 		if volume.Bind != nil {
@@ -1007,92 +1031,88 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
 }
 }
 
 
 func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
 func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
-	_, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
+	// NetworkInspect will match on ID prefix, so NetworkList with a name
+	// filter is used to look for an exact match to prevent e.g. a network
+	// named `db` from getting erroneously matched to a network with an ID
+	// like `db9086999caf`
+	networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
+		Filters: filters.NewArgs(filters.Arg("name", n.Name)),
+	})
 	if err != nil {
 	if err != nil {
-		if errdefs.IsNotFound(err) {
-			if n.External.External {
-				if n.Driver == "overlay" {
-					// Swarm nodes do not register overlay networks that were
-					// created on a different node unless they're in use.
-					// Here we assume `driver` is relevant for a network we don't manage
-					// which is a non-sense, but this is our legacy ¯\(ツ)/¯
-					// networkAttach will later fail anyway if network actually doesn't exists
-					return nil
-				}
-				return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
+		return err
+	}
+	if len(networks) == 0 {
+		if n.External.External {
+			if n.Driver == "overlay" {
+				// Swarm nodes do not register overlay networks that were
+				// created on a different node unless they're in use.
+				// Here we assume `driver` is relevant for a network we don't manage
+				// which is a non-sense, but this is our legacy ¯\(ツ)/¯
+				// networkAttach will later fail anyway if network actually doesn't exists
+				return nil
 			}
 			}
-			var ipam *network.IPAM
-			if n.Ipam.Config != nil {
-				var config []network.IPAMConfig
-				for _, pool := range n.Ipam.Config {
-					config = append(config, network.IPAMConfig{
-						Subnet:     pool.Subnet,
-						IPRange:    pool.IPRange,
-						Gateway:    pool.Gateway,
-						AuxAddress: pool.AuxiliaryAddresses,
-					})
-				}
-				ipam = &network.IPAM{
-					Driver: n.Ipam.Driver,
-					Config: config,
-				}
+			return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
+		}
+		var ipam *network.IPAM
+		if n.Ipam.Config != nil {
+			var config []network.IPAMConfig
+			for _, pool := range n.Ipam.Config {
+				config = append(config, network.IPAMConfig{
+					Subnet:     pool.Subnet,
+					IPRange:    pool.IPRange,
+					Gateway:    pool.Gateway,
+					AuxAddress: pool.AuxiliaryAddresses,
+				})
 			}
 			}
-			createOpts := moby.NetworkCreate{
-				// TODO NameSpace Labels
-				Labels:     n.Labels,
-				Driver:     n.Driver,
-				Options:    n.DriverOpts,
-				Internal:   n.Internal,
-				Attachable: n.Attachable,
-				IPAM:       ipam,
-				EnableIPv6: n.EnableIPv6,
+			ipam = &network.IPAM{
+				Driver: n.Ipam.Driver,
+				Config: config,
 			}
 			}
+		}
+		createOpts := moby.NetworkCreate{
+			CheckDuplicate: true,
+			// TODO NameSpace Labels
+			Labels:     n.Labels,
+			Driver:     n.Driver,
+			Options:    n.DriverOpts,
+			Internal:   n.Internal,
+			Attachable: n.Attachable,
+			IPAM:       ipam,
+			EnableIPv6: n.EnableIPv6,
+		}
 
 
-			if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
-				createOpts.IPAM = &network.IPAM{}
-			}
+		if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
+			createOpts.IPAM = &network.IPAM{}
+		}
 
 
-			if n.Ipam.Driver != "" {
-				createOpts.IPAM.Driver = n.Ipam.Driver
-			}
+		if n.Ipam.Driver != "" {
+			createOpts.IPAM.Driver = n.Ipam.Driver
+		}
 
 
-			for _, ipamConfig := range n.Ipam.Config {
-				config := network.IPAMConfig{
-					Subnet: ipamConfig.Subnet,
-				}
-				createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
+		for _, ipamConfig := range n.Ipam.Config {
+			config := network.IPAMConfig{
+				Subnet:     ipamConfig.Subnet,
+				IPRange:    ipamConfig.IPRange,
+				Gateway:    ipamConfig.Gateway,
+				AuxAddress: ipamConfig.AuxiliaryAddresses,
 			}
 			}
-			networkEventName := fmt.Sprintf("Network %s", n.Name)
-			w := progress.ContextWriter(ctx)
-			w.Event(progress.CreatingEvent(networkEventName))
-			if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
-				w.Event(progress.ErrorEvent(networkEventName))
-				return errors.Wrapf(err, "failed to create network %s", n.Name)
-			}
-			w.Event(progress.CreatedEvent(networkEventName))
-			return nil
+			createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
 		}
 		}
-		return err
-	}
-	return nil
-}
-
-func (s *composeService) removeNetwork(ctx context.Context, networkID string, networkName string) error {
-	w := progress.ContextWriter(ctx)
-	eventName := fmt.Sprintf("Network %s", networkName)
-	w.Event(progress.RemovingEvent(eventName))
-
-	if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
-		w.Event(progress.ErrorEvent(eventName))
-		return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID))
+		networkEventName := fmt.Sprintf("Network %s", n.Name)
+		w := progress.ContextWriter(ctx)
+		w.Event(progress.CreatingEvent(networkEventName))
+		if _, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts); err != nil {
+			w.Event(progress.ErrorEvent(networkEventName))
+			return errors.Wrapf(err, "failed to create network %s", n.Name)
+		}
+		w.Event(progress.CreatedEvent(networkEventName))
+		return nil
 	}
 	}
-
-	w.Event(progress.RemovedEvent(eventName))
 	return nil
 	return nil
 }
 }
 
 
 func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
 func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
-	inspected, err := s.apiClient.VolumeInspect(ctx, volume.Name)
+	inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
 	if err != nil {
 	if err != nil {
 		if !errdefs.IsNotFound(err) {
 		if !errdefs.IsNotFound(err) {
 			return err
 			return err
@@ -1123,7 +1143,7 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
 	eventName := fmt.Sprintf("Volume %q", volume.Name)
 	eventName := fmt.Sprintf("Volume %q", volume.Name)
 	w := progress.ContextWriter(ctx)
 	w := progress.ContextWriter(ctx)
 	w.Event(progress.CreatingEvent(eventName))
 	w.Event(progress.CreatingEvent(eventName))
-	_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
+	_, err := s.apiClient().VolumeCreate(ctx, volume_api.VolumeCreateBody{
 		Labels:     volume.Labels,
 		Labels:     volume.Labels,
 		Name:       volume.Name,
 		Name:       volume.Name,
 		Driver:     volume.Driver,
 		Driver:     volume.Driver,

+ 0 - 9
pkg/compose/create_test.go

@@ -143,15 +143,6 @@ func TestBuildContainerMountOptions(t *testing.T) {
 	assert.Equal(t, mounts[1].Target, "/var/myvolume2")
 	assert.Equal(t, mounts[1].Target, "/var/myvolume2")
 }
 }
 
 
-func TestGetBindMode(t *testing.T) {
-	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, false), "rw")
-	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, true), "ro")
-	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, false), "rw,z")
-	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, false), "rw,Z")
-	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, true), "ro,z")
-	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, true), "ro,Z")
-}
-
 func TestGetDefaultNetworkMode(t *testing.T) {
 func TestGetDefaultNetworkMode(t *testing.T) {
 	t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
 	t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
 		service := composetypes.ServiceConfig{
 		service := composetypes.ServiceConfig{

+ 3 - 3
pkg/compose/dependencies.go

@@ -132,7 +132,7 @@ func getParents(v *Vertex) []*Vertex {
 	return v.GetParents()
 	return v.GetParents()
 }
 }
 
 
-// GetParents returns a slice with the parent vertexes of the a Vertex
+// GetParents returns a slice with the parent vertices of the a Vertex
 func (v *Vertex) GetParents() []*Vertex {
 func (v *Vertex) GetParents() []*Vertex {
 	var res []*Vertex
 	var res []*Vertex
 	for _, p := range v.Parents {
 	for _, p := range v.Parents {
@@ -145,7 +145,7 @@ func getChildren(v *Vertex) []*Vertex {
 	return v.GetChildren()
 	return v.GetChildren()
 }
 }
 
 
-// GetChildren returns a slice with the child vertexes of the a Vertex
+// GetChildren returns a slice with the child vertices of the a Vertex
 func (v *Vertex) GetChildren() []*Vertex {
 func (v *Vertex) GetChildren() []*Vertex {
 	var res []*Vertex
 	var res []*Vertex
 	for _, p := range v.Children {
 	for _, p := range v.Children {
@@ -194,7 +194,7 @@ func (g *Graph) AddVertex(key string, service string, initialStatus ServiceStatu
 	g.Vertices[key] = v
 	g.Vertices[key] = v
 }
 }
 
 
-// AddEdge adds a relationship of dependency between vertexes `source` and `destination`
+// AddEdge adds a relationship of dependency between vertices `source` and `destination`
 func (g *Graph) AddEdge(source string, destination string) error {
 func (g *Graph) AddEdge(source string, destination string) error {
 	g.lock.Lock()
 	g.lock.Lock()
 	defer g.lock.Unlock()
 	defer g.lock.Unlock()

+ 86 - 40
pkg/compose/down.go

@@ -26,6 +26,7 @@ import (
 	moby "github.com/docker/docker/api/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
+	"github.com/pkg/errors"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
@@ -41,7 +42,6 @@ func (s *composeService) Down(ctx context.Context, projectName string, options a
 }
 }
 
 
 func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
 func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
-	builtFromResources := options.Project == nil
 	w := progress.ContextWriter(ctx)
 	w := progress.ContextWriter(ctx)
 	resourceToRemove := false
 	resourceToRemove := false
 
 
@@ -51,8 +51,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
 		return err
 		return err
 	}
 	}
 
 
-	if builtFromResources {
-		options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName)
+	project := options.Project
+	if project == nil {
+		project, err = s.getProjectWithResources(ctx, containers, projectName)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -62,7 +63,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
 		resourceToRemove = true
 		resourceToRemove = true
 	}
 	}
 
 
-	err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service string) error {
+	err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
 		serviceContainers := containers.filter(isService(service))
 		serviceContainers := containers.filter(isService(service))
 		err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
 		err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
 		return err
 		return err
@@ -71,7 +72,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
 		return err
 		return err
 	}
 	}
 
 
-	orphans := containers.filter(isNotService(options.Project.ServiceNames()...))
+	orphans := containers.filter(isNotService(project.ServiceNames()...))
 	if options.RemoveOrphans && len(orphans) > 0 {
 	if options.RemoveOrphans && len(orphans) > 0 {
 		err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
 		err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
 		if err != nil {
 		if err != nil {
@@ -79,21 +80,18 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
 		}
 		}
 	}
 	}
 
 
-	ops, err := s.ensureNetworksDown(ctx, projectName)
-	if err != nil {
-		return err
-	}
+	ops := s.ensureNetworksDown(ctx, project, w)
 
 
 	if options.Images != "" {
 	if options.Images != "" {
-		ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...)
+		ops = append(ops, s.ensureImagesDown(ctx, project, options, w)...)
 	}
 	}
 
 
 	if options.Volumes {
 	if options.Volumes {
-		ops = append(ops, s.ensureVolumesDown(ctx, options.Project, w)...)
+		ops = append(ops, s.ensureVolumesDown(ctx, project, w)...)
 	}
 	}
 
 
 	if !resourceToRemove && len(ops) == 0 {
 	if !resourceToRemove && len(ops) == 0 {
-		w.Event(progress.NewEvent(projectName, progress.Done, "Warning: No resource found to remove"))
+		fmt.Fprintf(s.stderr(), "Warning: No resource found to remove for project %q.\n", projectName)
 	}
 	}
 
 
 	eg, _ := errgroup.WithContext(ctx)
 	eg, _ := errgroup.WithContext(ctx)
@@ -106,6 +104,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
 func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
 func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
 	var ops []downOp
 	var ops []downOp
 	for _, vol := range project.Volumes {
 	for _, vol := range project.Volumes {
+		if vol.External.External {
+			continue
+		}
 		volumeName := vol.Name
 		volumeName := vol.Name
 		ops = append(ops, func() error {
 		ops = append(ops, func() error {
 			return s.removeVolume(ctx, volumeName, w)
 			return s.removeVolume(ctx, volumeName, w)
@@ -114,9 +115,9 @@ func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.P
 	return ops
 	return ops
 }
 }
 
 
-func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options api.DownOptions, w progress.Writer) []downOp {
+func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) []downOp {
 	var ops []downOp
 	var ops []downOp
-	for image := range s.getServiceImages(options, projectName) {
+	for image := range s.getServiceImages(options, project) {
 		image := image
 		image := image
 		ops = append(ops, func() error {
 		ops = append(ops, func() error {
 			return s.removeImage(ctx, image, w)
 			return s.removeImage(ctx, image, w)
@@ -125,31 +126,74 @@ func (s *composeService) ensureImagesDown(ctx context.Context, projectName strin
 	return ops
 	return ops
 }
 }
 
 
-func (s *composeService) ensureNetworksDown(ctx context.Context, projectName string) ([]downOp, error) {
+func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
 	var ops []downOp
 	var ops []downOp
-	networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))})
-	if err != nil {
-		return ops, err
-	}
-	for _, n := range networks {
-		networkID := n.ID
+	for _, n := range project.Networks {
+		if n.External.External {
+			continue
+		}
+		// loop capture variable for op closure
 		networkName := n.Name
 		networkName := n.Name
 		ops = append(ops, func() error {
 		ops = append(ops, func() error {
-			return s.removeNetwork(ctx, networkID, networkName)
+			return s.removeNetwork(ctx, networkName, w)
 		})
 		})
 	}
 	}
-	return ops, nil
+	return ops
+}
+
+func (s *composeService) removeNetwork(ctx context.Context, name string, w progress.Writer) error {
+	// networks are guaranteed to have unique IDs but NOT names, so it's
+	// possible to get into a situation where a compose down will fail with
+	// an error along the lines of:
+	// 	failed to remove network test: Error response from daemon: network test is ambiguous (2 matches found based on name)
+	// as a workaround here, the delete is done by ID after doing a list using
+	// the name as a filter (99.9% of the time this will return a single result)
+	networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
+		Filters: filters.NewArgs(filters.Arg("name", name)),
+	})
+	if err != nil {
+		return errors.Wrapf(err, fmt.Sprintf("failed to inspect network %s", name))
+	}
+	if len(networks) == 0 {
+		return nil
+	}
+
+	eventName := fmt.Sprintf("Network %s", name)
+	w.Event(progress.RemovingEvent(eventName))
+
+	var removed int
+	for _, net := range networks {
+		if err := s.apiClient().NetworkRemove(ctx, net.ID); err != nil {
+			if errdefs.IsNotFound(err) {
+				continue
+			}
+			w.Event(progress.ErrorEvent(eventName))
+			return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", name))
+		}
+		removed++
+	}
+
+	if removed == 0 {
+		// in practice, it's extremely unlikely for this to ever occur, as it'd
+		// mean the network was present when we queried at the start of this
+		// method but was then deleted by something else in the interim
+		w.Event(progress.NewEvent(eventName, progress.Done, "Warning: No resource found to remove"))
+		return nil
+	}
+
+	w.Event(progress.RemovedEvent(eventName))
+	return nil
 }
 }
 
 
-func (s *composeService) getServiceImages(options api.DownOptions, projectName string) map[string]struct{} {
+func (s *composeService) getServiceImages(options api.DownOptions, project *types.Project) map[string]struct{} {
 	images := map[string]struct{}{}
 	images := map[string]struct{}{}
-	for _, service := range options.Project.Services {
+	for _, service := range project.Services {
 		image := service.Image
 		image := service.Image
 		if options.Images == "local" && image != "" {
 		if options.Images == "local" && image != "" {
 			continue
 			continue
 		}
 		}
 		if image == "" {
 		if image == "" {
-			image = getImageName(service, projectName)
+			image = getImageName(service, project.Name)
 		}
 		}
 		images[image] = struct{}{}
 		images[image] = struct{}{}
 	}
 	}
@@ -159,7 +203,7 @@ func (s *composeService) getServiceImages(options api.DownOptions, projectName s
 func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
 func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
 	id := fmt.Sprintf("Image %s", image)
 	id := fmt.Sprintf("Image %s", image)
 	w.Event(progress.NewEvent(id, progress.Working, "Removing"))
 	w.Event(progress.NewEvent(id, progress.Working, "Removing"))
-	_, err := s.apiClient.ImageRemove(ctx, image, moby.ImageRemoveOptions{})
+	_, err := s.apiClient().ImageRemove(ctx, image, moby.ImageRemoveOptions{})
 	if err == nil {
 	if err == nil {
 		w.Event(progress.NewEvent(id, progress.Done, "Removed"))
 		w.Event(progress.NewEvent(id, progress.Done, "Removed"))
 		return nil
 		return nil
@@ -174,7 +218,7 @@ func (s *composeService) removeImage(ctx context.Context, image string, w progre
 func (s *composeService) removeVolume(ctx context.Context, id string, w progress.Writer) error {
 func (s *composeService) removeVolume(ctx context.Context, id string, w progress.Writer) error {
 	resource := fmt.Sprintf("Volume %s", id)
 	resource := fmt.Sprintf("Volume %s", id)
 	w.Event(progress.NewEvent(resource, progress.Working, "Removing"))
 	w.Event(progress.NewEvent(resource, progress.Working, "Removing"))
-	err := s.apiClient.VolumeRemove(ctx, id, true)
+	err := s.apiClient().VolumeRemove(ctx, id, true)
 	if err == nil {
 	if err == nil {
 		w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
 		w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
 		return nil
 		return nil
@@ -193,7 +237,7 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
 		eg.Go(func() error {
 		eg.Go(func() error {
 			eventName := getContainerProgressName(container)
 			eventName := getContainerProgressName(container)
 			w.Event(progress.StoppingEvent(eventName))
 			w.Event(progress.StoppingEvent(eventName))
-			err := s.apiClient.ContainerStop(ctx, container.ID, timeout)
+			err := s.apiClient().ContainerStop(ctx, container.ID, timeout)
 			if err != nil {
 			if err != nil {
 				w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
 				w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
 				return err
 				return err
@@ -218,7 +262,7 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
 				return err
 				return err
 			}
 			}
 			w.Event(progress.RemovingEvent(eventName))
 			w.Event(progress.RemovingEvent(eventName))
-			err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
+			err = s.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
 				Force:         true,
 				Force:         true,
 				RemoveVolumes: volumes,
 				RemoveVolumes: volumes,
 			})
 			})
@@ -233,21 +277,23 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
 	return eg.Wait()
 	return eg.Wait()
 }
 }
 
 
-func (s *composeService) getProjectWithVolumes(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
+func (s *composeService) getProjectWithResources(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
 	containers = containers.filter(isNotOneOff)
 	containers = containers.filter(isNotOneOff)
-	project, _ := s.projectFromName(containers, projectName)
-	volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
+	project, err := s.projectFromName(containers, projectName)
+	if err != nil && !api.IsNotFoundError(err) {
+		return nil, err
+	}
+
+	volumes, err := s.actualVolumes(ctx, projectName)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	project.Volumes = volumes
 
 
-	project.Volumes = types.Volumes{}
-	for _, vol := range volumes.Volumes {
-		project.Volumes[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
-			Name:   vol.Name,
-			Driver: vol.Driver,
-			Labels: vol.Labels,
-		}
+	networks, err := s.actualNetworks(ctx, projectName)
+	if err != nil {
+		return nil, err
 	}
 	}
+	project.Networks = networks
 	return project, nil
 	return project, nil
 }
 }

+ 39 - 16
pkg/compose/down_test.go

@@ -21,21 +21,24 @@ import (
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 
 
-	compose "github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/mocks"
-
 	moby "github.com/docker/docker/api/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/volume"
 	"github.com/docker/docker/api/types/volume"
 	"github.com/golang/mock/gomock"
 	"github.com/golang/mock/gomock"
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/assert"
+
+	compose "github.com/docker/compose/v2/pkg/api"
+	"github.com/docker/compose/v2/pkg/mocks"
 )
 )
 
 
 func TestDown(t *testing.T) {
 func TestDown(t *testing.T) {
 	mockCtrl := gomock.NewController(t)
 	mockCtrl := gomock.NewController(t)
 	defer mockCtrl.Finish()
 	defer mockCtrl.Finish()
+
 	api := mocks.NewMockAPIClient(mockCtrl)
 	api := mocks.NewMockAPIClient(mockCtrl)
-	tested.apiClient = api
+	cli := mocks.NewMockCli(mockCtrl)
+	tested.dockerCli = cli
+	cli.EXPECT().Client().Return(api).AnyTimes()
 
 
 	api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
 	api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
 		[]moby.Container{
 		[]moby.Container{
@@ -47,6 +50,14 @@ func TestDown(t *testing.T) {
 	api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
 	api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
 		Return(volume.VolumeListOKBody{}, nil)
 		Return(volume.VolumeListOKBody{}, nil)
 
 
+	// network names are not guaranteed to be unique, ensure Compose handles
+	// cleanup properly if duplicates are inadvertently created
+	api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
+		Return([]moby.NetworkResource{
+			{ID: "abc123", Name: "myProject_default"},
+			{ID: "def456", Name: "myProject_default"},
+		}, nil)
+
 	api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
@@ -55,10 +66,14 @@ func TestDown(t *testing.T) {
 	api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil)
 	api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil)
 	api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
 	api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
 
 
-	api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return([]moby.NetworkResource{{ID: "myProject_default"}},
-		nil)
-
-	api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
+	api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
+		Filters: filters.NewArgs(filters.Arg("name", "myProject_default")),
+	}).Return([]moby.NetworkResource{
+		{ID: "abc123", Name: "myProject_default"},
+		{ID: "def456", Name: "myProject_default"},
+	}, nil)
+	api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
+	api.EXPECT().NetworkRemove(gomock.Any(), "def456").Return(nil)
 
 
 	err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{})
 	err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{})
 	assert.NilError(t, err)
 	assert.NilError(t, err)
@@ -67,8 +82,11 @@ func TestDown(t *testing.T) {
 func TestDownRemoveOrphans(t *testing.T) {
 func TestDownRemoveOrphans(t *testing.T) {
 	mockCtrl := gomock.NewController(t)
 	mockCtrl := gomock.NewController(t)
 	defer mockCtrl.Finish()
 	defer mockCtrl.Finish()
+
 	api := mocks.NewMockAPIClient(mockCtrl)
 	api := mocks.NewMockAPIClient(mockCtrl)
-	tested.apiClient = api
+	cli := mocks.NewMockCli(mockCtrl)
+	tested.dockerCli = cli
+	cli.EXPECT().Client().Return(api).AnyTimes()
 
 
 	api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
 	api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
 		[]moby.Container{
 		[]moby.Container{
@@ -78,6 +96,8 @@ func TestDownRemoveOrphans(t *testing.T) {
 		}, nil)
 		}, nil)
 	api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
 	api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
 		Return(volume.VolumeListOKBody{}, nil)
 		Return(volume.VolumeListOKBody{}, nil)
+	api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
+		Return([]moby.NetworkResource{{Name: "myProject_default"}}, nil)
 
 
 	api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
@@ -87,10 +107,10 @@ func TestDownRemoveOrphans(t *testing.T) {
 	api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
 	api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
 	api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil)
 	api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil)
 
 
-	api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return([]moby.NetworkResource{{ID: "myProject_default"}},
-		nil)
-
-	api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
+	api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
+		Filters: filters.NewArgs(filters.Arg("name", "myProject_default")),
+	}).Return([]moby.NetworkResource{{ID: "abc123", Name: "myProject_default"}}, nil)
+	api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
 
 
 	err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
 	err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
 	assert.NilError(t, err)
 	assert.NilError(t, err)
@@ -99,8 +119,11 @@ func TestDownRemoveOrphans(t *testing.T) {
 func TestDownRemoveVolumes(t *testing.T) {
 func TestDownRemoveVolumes(t *testing.T) {
 	mockCtrl := gomock.NewController(t)
 	mockCtrl := gomock.NewController(t)
 	defer mockCtrl.Finish()
 	defer mockCtrl.Finish()
+
 	api := mocks.NewMockAPIClient(mockCtrl)
 	api := mocks.NewMockAPIClient(mockCtrl)
-	tested.apiClient = api
+	cli := mocks.NewMockCli(mockCtrl)
+	tested.dockerCli = cli
+	cli.EXPECT().Client().Return(api).AnyTimes()
 
 
 	api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
 	api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
 		[]moby.Container{testContainer("service1", "123", false)}, nil)
 		[]moby.Container{testContainer("service1", "123", false)}, nil)
@@ -108,12 +131,12 @@ func TestDownRemoveVolumes(t *testing.T) {
 		Return(volume.VolumeListOKBody{
 		Return(volume.VolumeListOKBody{
 			Volumes: []*moby.Volume{{Name: "myProject_volume"}},
 			Volumes: []*moby.Volume{{Name: "myProject_volume"}},
 		}, nil)
 		}, nil)
+	api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
+		Return(nil, nil)
 
 
 	api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
 	api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
 	api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
 
 
-	api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return(nil, nil)
-
 	api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
 	api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
 
 
 	err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})
 	err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})

+ 4 - 3
pkg/compose/events.go

@@ -29,9 +29,10 @@ import (
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
 )
 )
 
 
-func (s *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error {
-	events, errors := s.apiClient.Events(ctx, moby.EventsOptions{
-		Filters: filters.NewArgs(projectFilter(project)),
+func (s *composeService) Events(ctx context.Context, projectName string, options api.EventsOptions) error {
+	projectName = strings.ToLower(projectName)
+	events, errors := s.apiClient().Events(ctx, moby.EventsOptions{
+		Filters: filters.NewArgs(projectFilter(projectName)),
 	})
 	})
 	for {
 	for {
 		select {
 		select {

+ 23 - 128
pkg/compose/exec.go

@@ -18,149 +18,44 @@ package compose
 
 
 import (
 import (
 	"context"
 	"context"
-	"fmt"
-	"io"
-
-	"github.com/docker/cli/cli/streams"
-	moby "github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/filters"
-	"github.com/docker/docker/pkg/stdcopy"
-	"github.com/moby/term"
+	"strings"
 
 
+	"github.com/docker/cli/cli"
+	"github.com/docker/cli/cli/command/container"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
+	moby "github.com/docker/docker/api/types"
 )
 )
 
 
-func (s *composeService) Exec(ctx context.Context, project string, opts api.RunOptions) (int, error) {
-	container, err := s.getExecTarget(ctx, project, opts)
-	if err != nil {
-		return 0, err
-	}
-
-	exec, err := s.apiClient.ContainerExecCreate(ctx, container.ID, moby.ExecConfig{
-		Cmd:        opts.Command,
-		Env:        opts.Environment,
-		User:       opts.User,
-		Privileged: opts.Privileged,
-		Tty:        opts.Tty,
-		Detach:     opts.Detach,
-		WorkingDir: opts.WorkingDir,
-
-		AttachStdin:  true,
-		AttachStdout: true,
-		AttachStderr: true,
-	})
-	if err != nil {
-		return 0, err
-	}
-
-	if opts.Detach {
-		return 0, s.apiClient.ContainerExecStart(ctx, exec.ID, moby.ExecStartCheck{
-			Detach: true,
-			Tty:    opts.Tty,
-		})
-	}
-
-	resp, err := s.apiClient.ContainerExecAttach(ctx, exec.ID, moby.ExecStartCheck{
-		Tty: opts.Tty,
-	})
+func (s *composeService) Exec(ctx context.Context, projectName string, options api.RunOptions) (int, error) {
+	projectName = strings.ToLower(projectName)
+	target, err := s.getExecTarget(ctx, projectName, options)
 	if err != nil {
 	if err != nil {
 		return 0, err
 		return 0, err
 	}
 	}
-	defer resp.Close() //nolint:errcheck
 
 
-	if opts.Tty {
-		s.monitorTTySize(ctx, exec.ID, s.apiClient.ContainerExecResize)
+	exec := container.NewExecOptions()
+	exec.Interactive = options.Interactive
+	exec.TTY = options.Tty
+	exec.Detach = options.Detach
+	exec.User = options.User
+	exec.Privileged = options.Privileged
+	exec.Workdir = options.WorkingDir
+	exec.Container = target.ID
+	exec.Command = options.Command
+	for _, v := range options.Environment {
+		err := exec.Env.Set(v)
 		if err != nil {
 		if err != nil {
 			return 0, err
 			return 0, err
 		}
 		}
 	}
 	}
 
 
-	err = s.interactiveExec(ctx, opts, resp)
-	if err != nil {
-		return 0, err
-	}
-
-	return s.getExecExitStatus(ctx, exec.ID)
-}
-
-// inspired by https://github.com/docker/cli/blob/master/cli/command/container/exec.go#L116
-func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOptions, resp moby.HijackedResponse) error {
-	outputDone := make(chan error)
-	inputDone := make(chan error)
-
-	stdout := ContainerStdout{HijackedResponse: resp}
-	stdin := ContainerStdin{HijackedResponse: resp}
-	r, err := s.getEscapeKeyProxy(opts.Stdin, opts.Tty)
-	if err != nil {
-		return err
-	}
-
-	in := streams.NewIn(opts.Stdin)
-	if in.IsTerminal() && opts.Tty {
-		state, err := term.SetRawTerminal(in.FD())
-		if err != nil {
-			return err
-		}
-		defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck
-	}
-
-	go func() {
-		if opts.Tty {
-			_, err := io.Copy(opts.Stdout, stdout)
-			outputDone <- err
-		} else {
-			_, err := stdcopy.StdCopy(opts.Stdout, opts.Stderr, stdout)
-			outputDone <- err
-		}
-		stdout.Close() //nolint:errcheck
-	}()
-
-	go func() {
-		_, err := io.Copy(stdin, r)
-		inputDone <- err
-		stdin.Close() //nolint:errcheck
-	}()
-
-	for {
-		select {
-		case err := <-outputDone:
-			return err
-		case err := <-inputDone:
-			if _, ok := err.(term.EscapeError); ok {
-				return nil
-			}
-			if err != nil {
-				return err
-			}
-			// Wait for output to complete streaming
-		case <-ctx.Done():
-			return ctx.Err()
-		}
+	err = container.RunExec(s.dockerCli, exec)
+	if sterr, ok := err.(cli.StatusError); ok {
+		return sterr.StatusCode, nil
 	}
 	}
+	return 0, err
 }
 }
 
 
 func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
 func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
-	containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
-		Filters: filters.NewArgs(
-			projectFilter(projectName),
-			serviceFilter(opts.Service),
-			containerNumberFilter(opts.Index),
-		),
-	})
-	if err != nil {
-		return moby.Container{}, err
-	}
-	if len(containers) < 1 {
-		return moby.Container{}, fmt.Errorf("service %q is not running container #%d", opts.Service, opts.Index)
-	}
-	container := containers[0]
-	return container, nil
-}
-
-func (s *composeService) getExecExitStatus(ctx context.Context, execID string) (int, error) {
-	resp, err := s.apiClient.ContainerExecInspect(ctx, execID)
-	if err != nil {
-		return 0, err
-	}
-	return resp.ExitCode, nil
+	return s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, opts.Service, opts.Index)
 }
 }

+ 3 - 3
pkg/compose/images.go

@@ -32,7 +32,8 @@ import (
 )
 )
 
 
 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) ([]api.ImageSummary, error) {
-	allContainers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+	projectName = strings.ToLower(projectName)
+	allContainers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
 		All:     true,
 		All:     true,
 		Filters: filters.NewArgs(projectFilter(projectName)),
 		Filters: filters.NewArgs(projectFilter(projectName)),
 	})
 	})
@@ -83,7 +84,7 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
 	for _, img := range images {
 	for _, img := range images {
 		img := img
 		img := img
 		eg.Go(func() error {
 		eg.Go(func() error {
-			inspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, img)
+			inspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, img)
 			if err != nil {
 			if err != nil {
 				if errdefs.IsNotFound(err) {
 				if errdefs.IsNotFound(err) {
 					return nil
 					return nil
@@ -93,7 +94,6 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
 			tag := ""
 			tag := ""
 			repository := ""
 			repository := ""
 			if len(inspect.RepoTags) > 0 {
 			if len(inspect.RepoTags) > 0 {
-
 				repotag := strings.Split(inspect.RepoTags[0], ":")
 				repotag := strings.Split(inspect.RepoTags[0], ":")
 				repository = repotag[0]
 				repository = repotag[0]
 				if len(repotag) > 1 {
 				if len(repotag) > 1 {

部分文件因为文件数量过多而无法显示