diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index f899632ef6..ca42ff911c 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -7,7 +7,6 @@ import ( "io" "os" "path/filepath" - "sort" "strconv" "strings" "sync" @@ -321,17 +320,16 @@ func (d *DockerCompose) Up(ctx context.Context, opts ...StackUpOption) (err erro } if len(upOptions.Services) != len(d.project.Services) { - sort.Strings(upOptions.Services) - - filteredServices := types.Services{} - - for _, srv := range upOptions.Services { - if srvConfig, ok := d.project.Services[srv]; ok { - filteredServices[srv] = srvConfig - } + // Disable (rather than delete) the non-selected services so a selected + // service's `service:` additional build-contexts stay resolvable at build + // time; only the selected services are created and started. + // IgnoreDependencies excludes their runtime depends_on from the selection. + d.project, err = d.project.WithSelectedServices(upOptions.Services, types.IgnoreDependencies) + if err != nil { + return err } - - d.project.Services = filteredServices + // WithSelectedServices returns a new project; point the up options at it too. + upOptions.Project = d.project } err = d.composeService.Up(ctx, d.project, api.UpOptions{ diff --git a/modules/compose/compose_api_test.go b/modules/compose/compose_api_test.go index 124552aeb2..e426571bf9 100644 --- a/modules/compose/compose_api_test.go +++ b/modules/compose/compose_api_test.go @@ -107,6 +107,37 @@ func TestDockerComposeAPIWithRunServices(t *testing.T) { assert.Contains(t, serviceNames, "api-nginx") } +func TestDockerComposeAPIWithRunServicesAndServiceBuildContext(t *testing.T) { + // "app" builds with a `service:build-dep` additional build context, so the + // build needs build-dep to remain in the project even though RunServices + // selects only "app" and build-dep is never started as a container. + // + // Regression test: Up used to shrink d.project down to the selected + // services, which dropped the build-only build-dep service and made the + // build fail with "service ... declares unknown service ... as additional + // contexts" — even though `docker compose up app` on the same project works. + path := filepath.Join(testdataPackage, "docker-compose-service-build-context.yml") + compose, err := NewDockerCompose(path) + require.NoError(t, err, "NewDockerCompose()") + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WaitForService("app", wait.ForHTTP("/env").WithPort("8080/tcp")). + Up(ctx, Wait(true), RunServices("app")) + cleanup(t, compose) + require.NoError(t, err, "compose.Up()") + + // Only the selected runtime service is started; the build-only context is not. + serviceNames := compose.Services() + require.Len(t, serviceNames, 1) + assert.Contains(t, serviceNames, "app") + + _, err = compose.ServiceContainer(context.Background(), "build-dep") + require.Error(t, err, "build-dep is a build-only context and must not be started") +} + func TestDockerComposeAPIWithProfiles(t *testing.T) { path := RenderComposeProfiles(t) diff --git a/modules/compose/testdata/docker-compose-service-build-context.yml b/modules/compose/testdata/docker-compose-service-build-context.yml new file mode 100644 index 0000000000..bc3eace8e6 --- /dev/null +++ b/modules/compose/testdata/docker-compose-service-build-context.yml @@ -0,0 +1,16 @@ +services: + # build-only dependency: never started, only used as a build context by "app". + build-dep: + build: + context: . + dockerfile: service-build-context-dep.Dockerfile + image: testcontainers/compose-service-build-dep:latest + app: + build: + context: . + dockerfile: service-build-context-app.Dockerfile + additional_contexts: + build-dep: service:build-dep + image: testcontainers/compose-service-build-app:latest + ports: + - "8080/tcp" diff --git a/modules/compose/testdata/service-build-context-app.Dockerfile b/modules/compose/testdata/service-build-context-app.Dockerfile new file mode 100644 index 0000000000..83934193f7 --- /dev/null +++ b/modules/compose/testdata/service-build-context-app.Dockerfile @@ -0,0 +1,6 @@ +FROM golang:1.24-alpine@sha256:fc2cff6625f3c1c92e6c85938ac5bd09034ad0d4bc2dfb08278020b68540dbb5 +WORKDIR /app +# Consumes the `service:build-dep` additional build context. +COPY --from=build-dep /artifact /artifact +COPY echoserver.go . +CMD go run echoserver.go diff --git a/modules/compose/testdata/service-build-context-dep.Dockerfile b/modules/compose/testdata/service-build-context-dep.Dockerfile new file mode 100644 index 0000000000..c44c311989 --- /dev/null +++ b/modules/compose/testdata/service-build-context-dep.Dockerfile @@ -0,0 +1,2 @@ +FROM golang:1.24-alpine@sha256:fc2cff6625f3c1c92e6c85938ac5bd09034ad0d4bc2dfb08278020b68540dbb5 +RUN echo "service-build-context-dep" > /artifact