Image name substitution

Testcontainers supports automatic substitution of Docker image names.

This allows replacement of an image name specified in test code with an alternative name - for example, to replace the name of a Docker Hub image dependency with an alternative hosted on a private image registry.

This is advisable to avoid Docker Hub rate limiting, and some companies will prefer this for policy reasons.

This page describes four approaches for image name substitution:

It is assumed that you have already set up a private registry hosting all the Docker images your build requires.

Manual substitution

Consider this if:

  • You use only a few images and updating code is not a chore
  • All developers and CI machines in your organisation have access to a common registry server
  • You also use one of the automated mechanisms to substitute the images that Testcontainers itself requires

This approach simply entails modifying test code manually, e.g. changing:

For example, you may have a test that uses the mysql container image from Docker Hub:

// Referring directly to an image on Docker Hub (mysql:8.0.24)
final MySQLContainer<?> mysql = new MySQLContainer<>(
    DockerImageName.parse("mysql:8.0.24")
)

// start the container and use it for testing

to:

// Referring directly to an image on a private registry - image name will vary
final MySQLContainer<?> mysql = new MySQLContainer<>(
    DockerImageName.parse("registry.mycompany.com/mirror/mysql:8.0.24")
                   .asCompatibleSubstituteFor("mysql")
)

// start the container and use it for testing

Automatically modifying Docker Hub image names

Testcontainers can be configured to modify Docker Hub image names on the fly to apply a prefix string.

Consider this if:

  • Developers and CI machines need to use different image names. For example, developers are able to pull images from Docker Hub, but CI machines need to pull from a private registry
  • Your private registry has copies of images from Docker Hub where the names are predictable, and just adding a prefix is enough. For example, registry.mycompany.com/mirror/mysql:8.0.24 can be derived from the original Docker Hub image name (mysql:8.0.24) with a consistent prefix string: registry.mycompany.com/mirror/

In this case, image name references in code are unchanged. i.e. you would leave as-is:

// Referring directly to an image on Docker Hub (mysql:8.0.24)
final MySQLContainer<?> mysql = new MySQLContainer<>(
    DockerImageName.parse("mysql:8.0.24")
)

// start the container and use it for testing

You can then configure Testcontainers to apply the prefix registry.mycompany.com/mirror/ to every image that it tries to pull from Docker Hub. This can be done in one of two ways:

  • Setting environment variables TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX=registry.mycompany.com/mirror/
  • Via config file, setting hub.image.name.prefix in either:
    • the ~/.testcontainers.properties file in your user home directory, or
    • a file named testcontainers.properties on the classpath

Testcontainers will automatically apply the prefix to every image that it pulls from Docker Hub - please verify that all the required images exist in your registry.

Testcontainers will not apply the prefix to:

  • non-Hub image names (e.g. where another registry is set)
  • Docker Hub image names where the hub registry is explicitly part of the name (i.e. anything with a docker.io or registry.hub.docker.com host part)

Developing a custom function for transforming image names on the fly

Consider this if:

  • You have complex rules about which private registry images should be used as substitutes, e.g.:
    • non-deterministic mapping of names meaning that a name prefix cannot be used
    • rules depending upon developer identity or location
  • or you wish to add audit logging of images used in the build
  • or you wish to prevent accidental usage of images that are not on an approved list

In this case, image name references in code are unchanged. i.e. you would leave as-is:

// Referring directly to an image on Docker Hub (mysql:8.0.24)
final MySQLContainer<?> mysql = new MySQLContainer<>(
    DockerImageName.parse("mysql:8.0.24")
)

// start the container and use it for testing

You can implement a custom image name substitutor by:

  • subclassing org.testcontainers.utility.ImageNameSubstitutor
  • configuring Testcontainers to use your custom implementation

The following is an example image substitutor implementation:

public class ExampleImageNameSubstitutor extends ImageNameSubstitutor {

    @Override
    public DockerImageName apply(DockerImageName original) {
        // convert the original name to something appropriate for
        // our build environment
        return DockerImageName.parse(
            // your code goes here - silly example of capitalising
            // the original name is shown
            original.asCanonicalNameString().toUpperCase()
        );
    }

    @Override
    protected String getDescription() {
        // used in logs
        return "example image name substitutor";
    }
}

Testcontainers can be configured to find it at runtime via configuration. To do this, create or modify a file on the classpath named testcontainers.properties.

For example:

image.substitutor=com.mycompany.testcontainers.ExampleImageNameSubstitutor

Note that it is also possible to provide this same configuration property:

  • in a testcontainers.properties file at the root of a library JAR file (useful if you wish to distribute a drop-in image substitutor JAR within an organization)
  • in a properties file in the user's home directory (~/.testcontainers.properties; note the leading .)
  • or as an environment variable (e.g. TESTCONTAINERS_IMAGE_SUBSTITUTOR=com.mycompany.testcontainers.ExampleImageNameSubstitutor).

Please see the documentation on configuration mechanisms for more information.

Overriding image names individually in configuration

Note

This approach is discouraged and deprecated, but is documented for completeness. Please consider one of the other approaches outlined in this page instead. Overriding individual image names via configuration may be removed in 2021.

Consider this if:

  • You have many references to image names in code and changing them is impractical, and
  • None of the other options are practical for you

In this case, image name references in code are left unchanged. i.e. you would leave as-is:

// Referring directly to an image on Docker Hub (mysql:8.0.24)
final MySQLContainer<?> mysql = new MySQLContainer<>(
    DockerImageName.parse("mysql:8.0.24")
)

// start the container and use it for testing

You can force Testcontainers to substitute in a different image using a configuration file, which allows some (but not all) container names to be substituted.