Skip to content

[Bug]: Compose !override tag is treated like !reset, so the wrong images are pre-pulled #12094

Description

@srivathsav01

Module

Core

Testcontainers version

2.0.5

Using the latest Testcontainers version?

Yes

Host OS

Windows 10

Host Arch

x86_64

Docker version

N/A. The problem is in how Testcontainers parses the compose files before anything is sent to Docker, so it reproduces without a Docker daemon (see the reproduction below).

What happened?

Before running docker compose up, Testcontainers reads the compose files itself, collects every image the services use (including base images of Dockerfiles they build), and pulls them with its own Docker client (ComposeDelegate#pullImages). According to the comment in pullImages, this is so that images from registries that need credential helpers can be pulled even when Compose can't use those credentials: originally because of docker/compose#5854 (docker-compose build ignored credential helpers), and still when Compose itself runs in a container. When Compose then starts, the images are already present.

Support for the !override tag was added in #11490 (for #11489), but it is handled exactly like !reset. The tagged value is replaced with null:

if (node.getTag().equals(new Tag("!reset")) || node.getTag().equals(new Tag("!override"))) {
    return null;
}

In Compose, !reset removes a value, but !override means "use this value instead of the one from the previous files" (https://docs.docker.com/reference/compose-file/merge/#replace-value). Because the overriding value is dropped, the image from the base file is pre-pulled instead of the one Compose will actually run.

Overriding an Image

Image

Compose runs postgres:16, but Testcontainers pre-pulls postgres:15.

Overriding a whole scenario

Image

Compose runs postgres:16 and redis:7, but Testcontainers pre-pulls postgres:15 and redis:6, neither of which is used.

Here db becomes null, and ParsedDockerComposeFile#parseAndValidate then stops processing all remaining services, because the loop uses break instead of continue when a service definition is not a map:

if (!(serviceDefinition instanceof Map)) {
    log.debug("Compose file {} has an unknown format: service '{}' is not Map", composeFileName, serviceName);
    break;
}

Impact : The images that are actually needed are not pre-pulled, so Compose has to pull them itself. With a local Compose this usually just works. When Compose runs in a container (e.g. new ComposeContainer(DockerImageName.parse("docker:25.0.5"), ...)) and the overriding image is in a private registry, it can fail, because the containerised Compose has no access to the host's credential helpers, even though the user is logged in.

Relevant log output

Reproduction using the public `DockerComposeFiles#getDependencyImages()` (the set `pullImages()` iterates over) against testcontainers 2.0.5:

1) image replaced with !override
   pre-pulled: [postgres:15]            expected: [postgres:16]
2) whole service replaced with !override
   pre-pulled: [postgres:15]            expected: [postgres:16]
3) services after a !override service
   pre-pulled: [redis:6, postgres:15]   expected: [postgres:16, redis:7]

Additional Information

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions