From 3b1004c4d9d5e2d6102dc3134fe7ed83eaabcfa6 Mon Sep 17 00:00:00 2001 From: jarek Date: Tue, 31 Mar 2026 15:04:46 +0200 Subject: [PATCH] fix: gate extra EndpointsConfig behind API >= 1.44 Before API 1.44 (Docker Engine 25.0), ContainerCreate only accepted a single EndpointsConfig entry. Passing multiple entries silently drops all but one network, leaving containers under-connected on older daemons such as Docker 20.10 or Synology DSM 7.1/7.2. Add apiVersion144 constant and wrap the secondary-networks loop in defaultNetworkSettings so that only the primary network is included in the ContainerCreate call when the negotiated API version is below 1.44. Signed-off-by: jarek --- pkg/compose/api_versions.go | 10 ++++++++++ pkg/compose/create.go | 16 ++++++++++------ pkg/compose/create_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/pkg/compose/api_versions.go b/pkg/compose/api_versions.go index bccb4ad27..d39aef8aa 100644 --- a/pkg/compose/api_versions.go +++ b/pkg/compose/api_versions.go @@ -19,6 +19,16 @@ package compose // Docker Engine API version constants. // These versions correspond to specific Docker Engine releases and their features. const ( + // apiVersion144 represents Docker Engine API version 1.44 (Engine v25.0). + // + // New features in this version: + // - ContainerCreate API accepts multiple EndpointsConfig entries + // + // Before this version: + // - Only a single EndpointsConfig entry was accepted in ContainerCreate + // - Extra networks must be connected individually after container creation via NetworkConnect + apiVersion144 = "1.44" + // apiVersion148 represents Docker Engine API version 1.48 (Engine v28.0). // // New features in this version: diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 78db3d0fd..cc0b4afbc 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -563,13 +563,17 @@ func defaultNetworkSettings(project *types.Project, // so we can pass all the extra networks we want the container to be connected to // in the network configuration instead of connecting the container to each extra // network individually after creation. - for _, networkKey := range serviceNetworks { - epSettings, err := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases) - if err != nil { - return "", nil, err + // For older API versions, extra networks are connected via NetworkConnect after + // container creation (see createMobyContainer in convergence.go). + if !versions.LessThan(version, apiVersion144) { + for _, networkKey := range serviceNetworks { + epSettings, err := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases) + if err != nil { + return "", nil, err + } + mobyNetworkName := project.Networks[networkKey].Name + endpointsConfig[mobyNetworkName] = epSettings } - mobyNetworkName := project.Networks[networkKey].Name - endpointsConfig[mobyNetworkName] = epSettings } networkConfig := &network.NetworkingConfig{ diff --git a/pkg/compose/create_test.go b/pkg/compose/create_test.go index 2ac5c392b..e08e4227d 100644 --- a/pkg/compose/create_test.go +++ b/pkg/compose/create_test.go @@ -273,6 +273,30 @@ func TestDefaultNetworkSettings(t *testing.T) { assert.Check(t, cmp.Nil(networkConfig)) }) + t.Run("returns only primary network in EndpointsConfig for API < 1.44", func(t *testing.T) { + service := composetypes.ServiceConfig{ + Name: "myService", + Networks: map[string]*composetypes.ServiceNetworkConfig{ + "myNetwork1": {Priority: 10}, + "myNetwork2": {Priority: 1000}, + }, + } + project := composetypes.Project{ + Name: "myProject", + Services: composetypes.Services{"myService": service}, + Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{ + "myNetwork1": {Name: "myProject_myNetwork1"}, + "myNetwork2": {Name: "myProject_myNetwork2"}, + }), + } + + networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + assert.NilError(t, err) + assert.Equal(t, string(networkMode), "myProject_myNetwork2") + assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1)) + assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_myNetwork2")) + }) + t.Run("returns defined network mode if explicitly set", func(t *testing.T) { service := composetypes.ServiceConfig{ Name: "myService",