Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bbd3a3c

Browse filesBrowse files
committed
introduce volume.type=image
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent 482b622 commit bbd3a3c
Copy full SHA for bbd3a3c

File tree

Expand file treeCollapse file tree

7 files changed

+99
-40
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+99
-40
lines changed

‎go.mod

Copy file name to clipboardExpand all lines: go.mod
+1-3Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/Microsoft/go-winio v0.6.2
99
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
1010
github.com/buger/goterm v1.0.4
11-
github.com/compose-spec/compose-go/v2 v2.5.0
11+
github.com/compose-spec/compose-go/v2 v2.5.1-0.20250409070949-8e1a035095ca
1212
github.com/containerd/containerd/v2 v2.0.4
1313
github.com/containerd/platforms v1.0.0-rc.1
1414
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
@@ -206,5 +206,3 @@ require (
206206
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
207207
sigs.k8s.io/yaml v1.4.0 // indirect
208208
)
209-
210-
replace github.com/compose-spec/compose-go/v2 => github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535

‎go.sum

Copy file name to clipboardExpand all lines: go.sum
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
8383
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
8484
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
8585
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
86+
github.com/compose-spec/compose-go/v2 v2.5.1-0.20250409070949-8e1a035095ca h1:4dH4DudeDunWTYetcJxQE65osreQKvaLtFLdl9CcqME=
87+
github.com/compose-spec/compose-go/v2 v2.5.1-0.20250409070949-8e1a035095ca/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
8688
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
8789
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
8890
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
@@ -167,8 +169,6 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ
167169
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
168170
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
169171
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
170-
github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535 h1:S/P6v3QxsMpkKn+2OSMPNkfSkadSjSHoMGAc/eBZgMU=
171-
github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
172172
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
173173
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
174174
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=

‎pkg/compose/build.go

Copy file name to clipboardExpand all lines: pkg/compose/build.go
+7-4Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,14 +318,17 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
318318
}
319319

320320
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]api.ImageSummary, error) {
321-
var imageNames []string
321+
imageNames := utils.Set[string]{}
322322
for _, s := range project.Services {
323323
imgName := api.GetImageNameOrDefault(s, project.Name)
324-
if !utils.StringContains(imageNames, imgName) {
325-
imageNames = append(imageNames, imgName)
324+
imageNames.Add(imgName)
325+
for _, volume := range s.Volumes {
326+
if volume.Type == types.VolumeTypeImage {
327+
imageNames.Add(volume.Source)
328+
}
326329
}
327330
}
328-
imgs, err := s.getImageSummaries(ctx, imageNames)
331+
imgs, err := s.getImageSummaries(ctx, imageNames.Elements())
329332
if err != nil {
330333
return nil, err
331334
}

‎pkg/compose/create.go

Copy file name to clipboardExpand all lines: pkg/compose/create.go
+41-25Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,15 @@ MOUNTS:
871871
}
872872
}
873873
}
874+
if m.Type == mount.TypeImage {
875+
version, err := s.RuntimeVersion(ctx)
876+
if err != nil {
877+
return nil, nil, err
878+
}
879+
if versions.LessThan(version, "1.48") {
880+
return nil, nil, fmt.Errorf("volume with type=image require Docker Engine v28 or later")
881+
}
882+
}
874883
mounts = append(mounts, m)
875884
}
876885
return binds, mounts, nil
@@ -1125,7 +1134,7 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
11251134
}
11261135
}
11271136

1128-
bind, vol, tmpfs := buildMountOptions(volume)
1137+
bind, vol, tmpfs, img := buildMountOptions(volume)
11291138

11301139
if bind != nil {
11311140
volume.Type = types.VolumeTypeBind
@@ -1140,37 +1149,35 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
11401149
BindOptions: bind,
11411150
VolumeOptions: vol,
11421151
TmpfsOptions: tmpfs,
1152+
ImageOptions: img,
11431153
}, nil
11441154
}
11451155

1146-
func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
1156+
func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions, *mount.ImageOptions) {
1157+
if volume.Type != types.VolumeTypeBind && volume.Bind != nil {
1158+
logrus.Warnf("mount of type `%s` should not define `bind` option", volume.Type)
1159+
}
1160+
if volume.Type != types.VolumeTypeVolume && volume.Volume != nil {
1161+
logrus.Warnf("mount of type `%s` should not define `volume` option", volume.Type)
1162+
}
1163+
if volume.Type != types.VolumeTypeTmpfs && volume.Tmpfs != nil {
1164+
logrus.Warnf("mount of type `%s` should not define `tmpfs` option", volume.Type)
1165+
}
1166+
if volume.Type != types.VolumeTypeImage && volume.Image != nil {
1167+
logrus.Warnf("mount of type `%s` should not define `image` option", volume.Type)
1168+
}
1169+
11471170
switch volume.Type {
11481171
case "bind":
1149-
if volume.Volume != nil {
1150-
logrus.Warnf("mount of type `bind` should not define `volume` option")
1151-
}
1152-
if volume.Tmpfs != nil {
1153-
logrus.Warnf("mount of type `bind` should not define `tmpfs` option")
1154-
}
1155-
return buildBindOption(volume.Bind), nil, nil
1172+
return buildBindOption(volume.Bind), nil, nil, nil
11561173
case "volume":
1157-
if volume.Bind != nil {
1158-
logrus.Warnf("mount of type `volume` should not define `bind` option")
1159-
}
1160-
if volume.Tmpfs != nil {
1161-
logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
1162-
}
1163-
return nil, buildVolumeOptions(volume.Volume), nil
1174+
return nil, buildVolumeOptions(volume.Volume), nil, nil
11641175
case "tmpfs":
1165-
if volume.Bind != nil {
1166-
logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
1167-
}
1168-
if volume.Volume != nil {
1169-
logrus.Warnf("mount of type `tmpfs` should not define `volume` option")
1170-
}
1171-
return nil, nil, buildTmpfsOptions(volume.Tmpfs)
1176+
return nil, nil, buildTmpfsOptions(volume.Tmpfs), nil
1177+
case "image":
1178+
return nil, nil, nil, buildImageOptions(volume.Image)
11721179
}
1173-
return nil, nil, nil
1180+
return nil, nil, nil, nil
11741181
}
11751182

11761183
func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
@@ -1199,7 +1206,7 @@ func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
11991206
return &mount.VolumeOptions{
12001207
NoCopy: vol.NoCopy,
12011208
Subpath: vol.Subpath,
1202-
// Labels: , // FIXME missing from model ?
1209+
Labels: vol.Labels,
12031210
// DriverConfig: , // FIXME missing from model ?
12041211
}
12051212
}
@@ -1214,6 +1221,15 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
12141221
}
12151222
}
12161223

1224+
func buildImageOptions(image *types.ServiceVolumeImage) *mount.ImageOptions {
1225+
if image == nil {
1226+
return nil
1227+
}
1228+
return &mount.ImageOptions{
1229+
Subpath: image.SubPath,
1230+
}
1231+
}
1232+
12171233
func (s *composeService) ensureNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) {
12181234
if n.External {
12191235
return s.resolveExternalNetwork(ctx, n)

‎pkg/compose/pull.go

Copy file name to clipboardExpand all lines: pkg/compose/pull.go
+19-6Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,15 +290,28 @@ func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
290290
}
291291

292292
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]api.ImageSummary, quietPull bool) error {
293-
var needPull []types.ServiceConfig
294-
for _, service := range project.Services {
293+
needPull := map[string]types.ServiceConfig{}
294+
for name, service := range project.Services {
295295
pull, err := mustPull(service, images)
296296
if err != nil {
297297
return err
298298
}
299299
if pull {
300-
needPull = append(needPull, service)
300+
needPull[name] = service
301301
}
302+
for i, vol := range service.Volumes {
303+
if vol.Type == types.VolumeTypeImage {
304+
if _, ok := images[vol.Source]; !ok {
305+
// Hack: create a fake ServiceConfig so we pull missing volume image
306+
n := fmt.Sprintf("%s:volume %d", name, i)
307+
needPull[n] = types.ServiceConfig{
308+
Name: n,
309+
Image: vol.Source,
310+
}
311+
}
312+
}
313+
}
314+
302315
}
303316
if len(needPull) == 0 {
304317
return nil
@@ -308,11 +321,11 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
308321
w := progress.ContextWriter(ctx)
309322
eg, ctx := errgroup.WithContext(ctx)
310323
eg.SetLimit(s.maxConcurrency)
311-
pulledImages := make([]api.ImageSummary, len(needPull))
312-
for i, service := range needPull {
324+
pulledImages := map[string]api.ImageSummary{}
325+
for name, service := range needPull {
313326
eg.Go(func() error {
314327
id, err := s.pullServiceImage(ctx, service, s.configFile(), w, quietPull, project.Environment["DOCKER_DEFAULT_PLATFORM"])
315-
pulledImages[i] = api.ImageSummary{
328+
pulledImages[name] = api.ImageSummary{
316329
ID: id,
317330
Repository: service.Image,
318331
LastTagTime: time.Now(),

‎pkg/e2e/fixtures/volumes/compose.yaml

Copy file name to clipboard
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
with_image:
3+
image: alpine
4+
command: "ls -al /mnt/image"
5+
volumes:
6+
- type: image
7+
source: nginx:alpine
8+
target: /mnt/image
9+
image:
10+
subpath: usr/share/nginx/html/

‎pkg/e2e/volumes_test.go

Copy file name to clipboardExpand all lines: pkg/e2e/volumes_test.go
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,22 @@ func TestUpRecreateVolumes_IgnoreBinds(t *testing.T) {
174174
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/recreate-volumes/bind.yaml", "--project-name", projectName, "up", "-d")
175175
assert.Check(t, !strings.Contains(res.Combined(), "Recreated"))
176176
}
177+
178+
func TestImageVolume(t *testing.T) {
179+
c := NewCLI(t)
180+
const projectName = "compose-e2e-image-volume"
181+
t.Cleanup(func() {
182+
c.cleanupWithDown(t, projectName)
183+
})
184+
185+
version := c.RunDockerCmd(t, "version", "-f", "{{.Server.Version}}")
186+
major, _, found := strings.Cut(version.Combined(), ".")
187+
assert.Assert(t, found)
188+
if major == "26" || major == "27" {
189+
t.Skip("Skipping test due to docker version < 28")
190+
}
191+
192+
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/volumes/compose.yaml", "--project-name", projectName, "up", "with_image")
193+
out := res.Combined()
194+
assert.Check(t, strings.Contains(out, "index.html"))
195+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.