TestContainers with Custom Docker Image in JDBC URL

Mark Peñaranda
Mark Penaranda
Published in
2 min readMay 18, 2023

--

When it comes to integration testing, having a reliable and consistent behavior of your database is crucial. TestContainer is a powerful tool that provides a simple and effective solution for this purpose. With its JDBC Support, you can effortlessly set up a database container of your choice by adding tc: to your JDBC URL

datasource:
default:
url: jdbc:tc:postgres:15.2://db

With this support it removes us from thinking how to manage this test containers and properly set our datasources so its really a big help.

Custom Docker Image

All is fine when we’re using official database containers. As JDBC supports using the database image name and its tag out of the box.

url: jdbc:tc:${image-name}:${tag}://${database-name}

This wont work though if we want to use a custom flavor of a certain database. We can illustrate this with an example where we aim to employ a version of Postgres integrated with pgvector. The official container for pgvector can be found at the following link: https://hub.docker.com/r/ankane/pgvector/tags.

Initially, we believed that the following configuration would work:

url: jdbc:tc:ankane/pgvector:v0.4.2://${database-name}

However, it turns out that this approach is not viable. Testcontainer only supports the official database images.

Solution

Leverage the capability of Image Name substitution, which conveniently provided by Testcontainer.

According to testcontainer documentation

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.

https://www.testcontainers.org/features/image_name_substitution/

  1. Create ContainerImageNameSubstitutor

Place it under your test src. and put it in com.example.yourproject.config.ContainerImageNameSubstitutor

class ContainerImageNameSubstitutor : ImageNameSubstitutor() {

override fun apply(original: DockerImageName): DockerImageName {

if (original.asCanonicalNameString().contains("postgres")) {
// TODO: Do replacement here
}

return original
}
}

2. Specify the docker image that you want to use

In this example we want to create a DockerImageName for ankane/pgvector . Make sure that you mark the DockerImageName as compatible with postgres

private val pgVectorDockerImage = DockerImageName
.parse("ankane/pgvector")
.asCompatibleSubstituteFor("postgres")

3. Apply the DockerImageName in the substitutor

class ContainerImageNameSubstitutor : ImageNameSubstitutor() {

private val pgVectorDockerImage = DockerImageName
.parse("ankane/pgvector")
.asCompatibleSubstituteFor("postgres")

override fun apply(original: DockerImageName): DockerImageName {

if (original.asCanonicalNameString().contains("postgres")) {
return pgVectorDockerImage
}

return original
}
}

4. Lastly, tell testcontainer to use your custom ImageNameSubstitutor

To do this we have to create testcontainers.properties . You can put it in your classpath. Me I like to put it in src/test/resources . Identify the location of your custom image name substitutor.

image.substitutor=com.example.yourproject.config.ContainerImageNameSubstitutor

Now TestContainer replaces the used image for postgres with ankane/pgvector 🎉 🎉

--

--

Mark Peñaranda
Mark Penaranda

Senior Software Engineer. I write to collect my thoughts and organize my thinking. A journal to keep my ideas and learning.