Networking and communicating with containers

Exposing container ports to the host

It is common to want to connect to a container from your test process, running on the test 'host' machine. For example, you may be testing a class that needs to connect to a backend or data store container.

Generally, each required port needs to be explicitly exposed. For example, we can specify one or more ports as follows:

public GenericContainer container = new GenericContainer("orientdb:3.0.13")
    .withExposedPorts(2424, 2480)
    .withLogConsumer(new Slf4jLogConsumer(log));

Note that this exposed port number is from the perspective of the container.

From the host's perspective Testcontainers actually exposes this on a random free port. This is by design, to avoid port collisions that may arise with locally running software or in between parallel test runs.

Because there is this layer of indirection, it is necessary to ask Testcontainers for the actual mapped port at runtime. This can be done using the getMappedPort method, which takes the original (container) port as an argument:

Integer firstMappedPort = container.getMappedPort(2424);
Integer secondMappedPort = container.getMappedPort(2480);

Warning

Because the randomised port mapping happens during container startup, the container must be running at the time getMappedPort is called. You may need to ensure that the startup order of components in your tests caters for this.

There is also a getFirstMappedPort method for convenience, for the fairly common scenario of a container that only exposes one port:

Integer firstMappedPort = container.getFirstMappedPort();

Getting the container IP address

When running with a local Docker daemon, exposed ports will usually be reachable on localhost. However, in some CI environments they may instead be reachable on a different host.

As such, Testcontainers provides a convenience method to obtain an IP address on which the container should be reachable from the host machine.

String ipAddress = container.getContainerIpAddress();

It is normally advisable to use getContainerIpAddress and getMappedPort together when constructing addresses - for example:

String address =
    container.getContainerIpAddress() + ":" + container.getMappedPort(2424);

Exposing host ports to the container

In some cases it is necessary to make a network connection from a container to a socket that is listening on the host machine. Natively, Docker has limited support for this model across platforms. Testcontainers, however, makes this possible.

In this example, assume that localServerPort is a port on our test host machine where a server (e.g. a web application) is running.

We need to tell Testcontainers to prepare to expose this port to containers:

Testcontainers.exposeHostPorts(localServerPort);

Warning

Note that the above command should be invoked before containers are started.

Having done so, we can now access this port from any containers that are launched. From a container's perspective, the hostname will be host.testcontainers.internal and the port will be the same value as localServerPort.

For example, here we construct an HTTP URL for our local web application and tell a Selenium container to get a page from it:

final String rootUrl =
    String.format("http://host.testcontainers.internal:%d/", localServerPort);

final RemoteWebDriver webDriver = browser.getWebDriver();
webDriver.get(rootUrl);

Advanced networking

Docker provides the ability for you to create custom networks and place containers on one or more networks. Then, communication can occur between networked containers without the need of exposing ports through the host. With Testcontainers, you can do this as well.

Warning

Note that Testcontainers currently only allows a container to be on a single network.

try (
        Network network = Network.newNetwork();

        GenericContainer foo = new GenericContainer()
                .withNetwork(network)
                .withNetworkAliases("foo")
                .withCommand("/bin/sh", "-c", "while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done");

        GenericContainer bar = new GenericContainer()
                .withNetwork(network)
                .withCommand("top")
) {
    foo.start();
    bar.start();

    String response = bar.execInContainer("wget", "-O", "-", "http://foo:8080").getStdout();
    assertEquals("received response", "yay", response);
}