diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go
index 10b53e53f85..2be3ab15738 100644
--- a/cmd/compose/compose.go
+++ b/cmd/compose/compose.go
@@ -636,6 +636,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
publishCommand(&opts, dockerCli, backend),
alphaCommand(&opts, dockerCli, backend),
bridgeCommand(&opts, dockerCli),
+ volumesCommand(&opts, dockerCli, backend),
)
c.Flags().SetInterspersed(false)
diff --git a/cmd/compose/volumes.go b/cmd/compose/volumes.go
new file mode 100644
index 00000000000..d98f71db326
--- /dev/null
+++ b/cmd/compose/volumes.go
@@ -0,0 +1,95 @@
+/*
+ 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"
+ "fmt"
+ "slices"
+
+ "github.com/docker/cli/cli/command"
+ "github.com/docker/cli/cli/command/formatter"
+ "github.com/docker/cli/cli/flags"
+ "github.com/docker/compose/v2/pkg/api"
+ "github.com/spf13/cobra"
+)
+
+type volumesOptions struct {
+ *ProjectOptions
+ Quiet bool
+ Format string
+}
+
+func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
+ options := volumesOptions{
+ ProjectOptions: p,
+ }
+
+ cmd := &cobra.Command{
+ Use: "volumes [OPTIONS] [SERVICE...]",
+ Short: "List volumes",
+ RunE: Adapt(func(ctx context.Context, args []string) error {
+ return runVol(ctx, dockerCli, backend, args, options)
+ }),
+ ValidArgsFunction: completeServiceNames(dockerCli, p),
+ }
+
+ cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", false, "Only display volume names")
+ cmd.Flags().StringVar(&options.Format, "format", "table", flags.FormatHelp)
+
+ return cmd
+}
+
+func runVol(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, options volumesOptions) error {
+ project, _, err := options.projectOrName(ctx, dockerCli, services...)
+ if err != nil {
+ return err
+ }
+
+ names := project.ServiceNames()
+
+ if len(services) == 0 {
+ services = names
+ }
+
+ for _, service := range services {
+ if !slices.Contains(names, service) {
+ return fmt.Errorf("no such service: %s", service)
+ }
+ }
+
+ volumes, err := backend.Volumes(ctx, project, api.VolumesOptions{
+ Services: services,
+ })
+ if err != nil {
+ return err
+ }
+
+ if options.Quiet {
+ for _, v := range volumes {
+ _, _ = fmt.Fprintln(dockerCli.Out(), v.Name)
+ }
+ return nil
+ }
+
+ volumeCtx := formatter.Context{
+ Output: dockerCli.Out(),
+ Format: formatter.NewVolumeFormat(options.Format, options.Quiet),
+ }
+
+ return formatter.VolumeWrite(volumeCtx, volumes)
+}
diff --git a/docs/reference/compose.md b/docs/reference/compose.md
index 391bf1a9705..74d129d832f 100644
--- a/docs/reference/compose.md
+++ b/docs/reference/compose.md
@@ -43,6 +43,7 @@ Define and run multi-container applications with Docker
| [`unpause`](compose_unpause.md) | Unpause services |
| [`up`](compose_up.md) | Create and start containers |
| [`version`](compose_version.md) | Show the Docker Compose version information |
+| [`volumes`](compose_volumes.md) | List volumes |
| [`wait`](compose_wait.md) | Block until containers of all (or specified) services stop. |
| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |
diff --git a/docs/reference/compose_volumes.md b/docs/reference/compose_volumes.md
new file mode 100644
index 00000000000..6bad874f187
--- /dev/null
+++ b/docs/reference/compose_volumes.md
@@ -0,0 +1,16 @@
+# docker compose volumes
+
+
+List volumes
+
+### Options
+
+| Name | Type | Default | Description |
+|:----------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `--dry-run` | `bool` | | Execute command in dry run mode |
+| `--format` | `string` | `table` | Format output using a custom template:
'table': Print output in table format with column headers (default)
'table TEMPLATE': Print output in table format using the given Go template
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
+| `-q`, `--quiet` | `bool` | | Only display volume names |
+
+
+
+
diff --git a/docs/reference/docker_compose.yaml b/docs/reference/docker_compose.yaml
index 93332702cda..02a39d93232 100644
--- a/docs/reference/docker_compose.yaml
+++ b/docs/reference/docker_compose.yaml
@@ -37,6 +37,7 @@ cname:
- docker compose unpause
- docker compose up
- docker compose version
+ - docker compose volumes
- docker compose wait
- docker compose watch
clink:
@@ -72,6 +73,7 @@ clink:
- docker_compose_unpause.yaml
- docker_compose_up.yaml
- docker_compose_version.yaml
+ - docker_compose_volumes.yaml
- docker_compose_wait.yaml
- docker_compose_watch.yaml
options:
diff --git a/docs/reference/docker_compose_volumes.yaml b/docs/reference/docker_compose_volumes.yaml
new file mode 100644
index 00000000000..20516db7f13
--- /dev/null
+++ b/docs/reference/docker_compose_volumes.yaml
@@ -0,0 +1,52 @@
+command: docker compose volumes
+short: List volumes
+long: List volumes
+usage: docker compose volumes [OPTIONS] [SERVICE...]
+pname: docker compose
+plink: docker_compose.yaml
+options:
+ - option: format
+ value_type: string
+ default_value: table
+ description: |-
+ Format output using a custom template:
+ 'table': Print output in table format with column headers (default)
+ 'table TEMPLATE': Print output in table format using the given Go template
+ 'json': Print in JSON format
+ 'TEMPLATE': Print output using the given Go template.
+ Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates
+ deprecated: false
+ hidden: false
+ experimental: false
+ experimentalcli: false
+ kubernetes: false
+ swarm: false
+ - option: quiet
+ shorthand: q
+ value_type: bool
+ default_value: "false"
+ description: Only display volume names
+ deprecated: false
+ hidden: false
+ experimental: false
+ experimentalcli: false
+ kubernetes: false
+ swarm: false
+inherited_options:
+ - option: dry-run
+ value_type: bool
+ default_value: "false"
+ description: Execute command in dry run mode
+ deprecated: false
+ hidden: false
+ experimental: false
+ experimentalcli: false
+ kubernetes: false
+ swarm: false
+deprecated: false
+hidden: false
+experimental: false
+experimentalcli: false
+kubernetes: false
+swarm: false
+
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 1be883899c6..d3e8f0e9fe4 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -26,6 +26,7 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/platforms"
"github.com/docker/cli/opts"
+ "github.com/docker/docker/api/types/volume"
)
// Service manages a compose project
@@ -98,8 +99,16 @@ type Service interface {
Commit(ctx context.Context, projectName string, options CommitOptions) error
// Generate generates a Compose Project from existing containers
Generate(ctx context.Context, options GenerateOptions) (*types.Project, error)
+ // Volumes executes the equivalent to a `docker volume ls`
+ Volumes(ctx context.Context, project *types.Project, options VolumesOptions) ([]VolumesSummary, error)
}
+type VolumesOptions struct {
+ Services []string
+}
+
+type VolumesSummary = *volume.Volume
+
type ScaleOptions struct {
Services []string
}
diff --git a/pkg/compose/volumes.go b/pkg/compose/volumes.go
new file mode 100644
index 00000000000..8c7bbed2f6f
--- /dev/null
+++ b/pkg/compose/volumes.go
@@ -0,0 +1,85 @@
+/*
+ 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"
+ "slices"
+
+ "github.com/compose-spec/compose-go/v2/types"
+ "github.com/docker/compose/v2/pkg/api"
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/api/types/volume"
+)
+
+func (s *composeService) Volumes(ctx context.Context, project *types.Project, options api.VolumesOptions) ([]api.VolumesSummary, error) {
+ projectName := project.Name
+
+ allContainers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
+ Filters: filters.NewArgs(projectFilter(projectName)),
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ var containers []container.Summary
+
+ if len(options.Services) > 0 {
+ // filter service containers
+ for _, c := range allContainers {
+ if slices.Contains(options.Services, c.Labels[api.ServiceLabel]) {
+ containers = append(containers, c)
+ }
+ }
+ } else {
+ containers = allContainers
+ }
+
+ volumesResponse, err := s.apiClient().VolumeList(ctx, volume.ListOptions{
+ Filters: filters.NewArgs(projectFilter(projectName)),
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ projectVolumes := volumesResponse.Volumes
+
+ if len(options.Services) == 0 {
+ return projectVolumes, nil
+ }
+
+ var volumes []api.VolumesSummary
+
+ // create a name lookup of volumes used by containers
+ serviceVolumes := make(map[string]bool)
+
+ for _, container := range containers {
+ for _, mount := range container.Mounts {
+ serviceVolumes[mount.Name] = true
+ }
+ }
+
+ // append if volumes in this project are in serviceVolumes
+ for _, v := range projectVolumes {
+ if serviceVolumes[v.Name] {
+ volumes = append(volumes, v)
+ }
+ }
+
+ return volumes, nil
+}
diff --git a/pkg/compose/volumes_test.go b/pkg/compose/volumes_test.go
new file mode 100644
index 00000000000..8e149159b23
--- /dev/null
+++ b/pkg/compose/volumes_test.go
@@ -0,0 +1,89 @@
+/*
+ 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"
+ "testing"
+
+ "github.com/compose-spec/compose-go/v2/types"
+ "github.com/docker/compose/v2/pkg/api"
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/api/types/volume"
+ "go.uber.org/mock/gomock"
+ "gotest.tools/v3/assert"
+)
+
+func TestVolumes(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ mockApi, mockCli := prepareMocks(mockCtrl)
+ tested := composeService{
+ dockerCli: mockCli,
+ }
+
+ // Create test volumes
+ vol1 := &volume.Volume{Name: testProject + "_vol1"}
+ vol2 := &volume.Volume{Name: testProject + "_vol2"}
+ vol3 := &volume.Volume{Name: testProject + "_vol3"}
+
+ // Create test containers with volume mounts
+ c1 := container.Summary{
+ Labels: map[string]string{api.ServiceLabel: "service1"},
+ Mounts: []container.MountPoint{
+ {Name: testProject + "_vol1"},
+ {Name: testProject + "_vol2"},
+ },
+ }
+ c2 := container.Summary{
+ Labels: map[string]string{api.ServiceLabel: "service2"},
+ Mounts: []container.MountPoint{
+ {Name: testProject + "_vol3"},
+ },
+ }
+
+ ctx := context.Background()
+ project := &types.Project{Name: testProject}
+ args := filters.NewArgs(projectFilter(testProject))
+ listOpts := container.ListOptions{Filters: args}
+ volumeListArgs := filters.NewArgs(projectFilter(testProject))
+ volumeListOpts := volume.ListOptions{Filters: volumeListArgs}
+ volumeReturn := volume.ListResponse{
+ Volumes: []*volume.Volume{vol1, vol2, vol3},
+ }
+ containerReturn := []container.Summary{c1, c2}
+
+ // Mock API calls
+ mockApi.EXPECT().ContainerList(ctx, listOpts).Times(2).Return(containerReturn, nil)
+ mockApi.EXPECT().VolumeList(ctx, volumeListOpts).Times(2).Return(volumeReturn, nil)
+
+ // Test without service filter - should return all project volumes
+ volumeOptions := api.VolumesOptions{}
+ volumes, err := tested.Volumes(ctx, project, volumeOptions)
+ expected := []api.VolumesSummary{vol1, vol2, vol3}
+ assert.NilError(t, err)
+ assert.DeepEqual(t, volumes, expected)
+
+ // Test with service filter - should only return volumes used by service1
+ volumeOptions = api.VolumesOptions{Services: []string{"service1"}}
+ volumes, err = tested.Volumes(ctx, project, volumeOptions)
+ expected = []api.VolumesSummary{vol1, vol2}
+ assert.NilError(t, err)
+ assert.DeepEqual(t, volumes, expected)
+}
diff --git a/pkg/mocks/mock_docker_compose_api.go b/pkg/mocks/mock_docker_compose_api.go
index 811187ea721..6bb065f342e 100644
--- a/pkg/mocks/mock_docker_compose_api.go
+++ b/pkg/mocks/mock_docker_compose_api.go
@@ -497,6 +497,21 @@ func (mr *MockServiceMockRecorder) Viz(ctx, project, options any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Viz", reflect.TypeOf((*MockService)(nil).Viz), ctx, project, options)
}
+// Volumes mocks base method.
+func (m *MockService) Volumes(ctx context.Context, project *types.Project, options api.VolumesOptions) ([]api.VolumesSummary, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Volumes", ctx, project, options)
+ ret0, _ := ret[0].([]api.VolumesSummary)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Volumes indicates an expected call of Volumes.
+func (mr *MockServiceMockRecorder) Volumes(ctx, project, options any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Volumes", reflect.TypeOf((*MockService)(nil).Volumes), ctx, project, options)
+}
+
// Wait mocks base method.
func (m *MockService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
m.ctrl.T.Helper()