From 41da408cafa20c7f1b90af7b28ad4f1b6f88bf35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Fri, 20 Mar 2026 17:03:42 -0600 Subject: [PATCH] Replace links usage with networks Links has been deprecated. This commit make changes to attach compose networks to ambassadador container. Fixes #5351 --- .../containers/ComposeDelegate.java | 40 ++++++++++++----- .../junit/ComposeWithMultipleNetworkTest.java | 45 +++++++++++++++++++ ...v2-compose-test-with-multiple-networks.yml | 14 ++++++ 3 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 core/src/test/java/org/testcontainers/junit/ComposeWithMultipleNetworkTest.java create mode 100644 core/src/test/resources/v2-compose-test-with-multiple-networks.yml diff --git a/core/src/main/java/org/testcontainers/containers/ComposeDelegate.java b/core/src/main/java/org/testcontainers/containers/ComposeDelegate.java index 3365a0b0681..cb4f82a909d 100644 --- a/core/src/main/java/org/testcontainers/containers/ComposeDelegate.java +++ b/core/src/main/java/org/testcontainers/containers/ComposeDelegate.java @@ -261,6 +261,26 @@ List listChildContainers() { void startAmbassadorContainer() { if (!this.ambassadorPortMappings.isEmpty()) { this.ambassadorContainer.start(); + connectAmbassadorToComposeNetworks(); + } + } + + private void connectAmbassadorToComposeNetworks() { + Set composeNetworkIds = listChildContainers() + .stream() + .filter(container -> { + return container.getNetworkSettings() != null && container.getNetworkSettings().getNetworks() != null; + }) + .flatMap(container -> container.getNetworkSettings().getNetworks().entrySet().stream()) + .filter(entry -> !Arrays.asList("bridge", "host", "none").contains(entry.getKey())) + .map(entry -> entry.getValue().getNetworkID()) + .collect(Collectors.toSet()); + + for (String composeNetworkId : composeNetworkIds) { + this.dockerClient.connectToNetworkCmd() + .withContainerId(this.ambassadorContainer.getContainerId()) + .withNetworkId(composeNetworkId) + .exec(); } } @@ -282,15 +302,14 @@ public void withExposedService(String serviceName, int servicePort, @NonNull Wai /* * For every service/port pair that needs to be exposed, we register a target on an 'ambassador container'. * - * The ambassador container's role is to link (within the Docker network) to one of the - * compose services, and proxy TCP network I/O out to a port that the ambassador container - * exposes. + * The ambassador container's role is to proxy TCP network I/O from one of the compose + * services out to a port that the ambassador container exposes. + * + * After Docker Compose starts, the ambassador container is connected to the compose + * network so it can resolve service names via Docker's built-in DNS. * * This avoids the need for the docker compose file to explicitly expose ports on all the * services. - * - * {@link GenericContainer} should ensure that the ambassador container is on the same network - * as the rest of the compose environment. */ // Ambassador container will be started together after docker compose has started @@ -298,11 +317,10 @@ public void withExposedService(String serviceName, int servicePort, @NonNull Wai ambassadorPortMappings .computeIfAbsent(serviceInstanceName, __ -> new ConcurrentHashMap<>()) .put(servicePort, ambassadorPort); - ambassadorContainer.withTarget(ambassadorPort, serviceInstanceName, servicePort); - ambassadorContainer.addLink( - new FutureContainer(this.project + this.composeSeparator + serviceInstanceName), - serviceInstanceName - ); + // Use the full container name as the socat target so Docker DNS can resolve it + // on the compose network (e.g., "project-redis-1" instead of just "redis-1") + String containerName = this.project + this.composeSeparator + serviceInstanceName; + ambassadorContainer.withTarget(ambassadorPort, containerName, servicePort); addWaitStrategy(serviceInstanceName, waitStrategy); } diff --git a/core/src/test/java/org/testcontainers/junit/ComposeWithMultipleNetworkTest.java b/core/src/test/java/org/testcontainers/junit/ComposeWithMultipleNetworkTest.java new file mode 100644 index 00000000000..5afe282fa82 --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/ComposeWithMultipleNetworkTest.java @@ -0,0 +1,45 @@ +package org.testcontainers.junit; + +import org.junit.jupiter.api.AutoClose; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.ComposeContainer; +import org.testcontainers.utility.DockerImageName; +import redis.clients.jedis.Jedis; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +class ComposeWithMultipleNetworkTest extends BaseComposeTest { + + @AutoClose + public ComposeContainer environment = new ComposeContainer( + DockerImageName.parse("docker:25.0.5"), + new File("src/test/resources/v2-compose-test-with-multiple-networks.yml") + ) + .withExposedService("redis-1", REDIS_PORT) + .withExposedService("another-redis-1", REDIS_PORT); + + ComposeWithMultipleNetworkTest() { + environment.start(); + } + + @Override + protected ComposeContainer getEnvironment() { + return environment; + } + + @Test + void redisInstanceInDifferentNetworkCanBeUsed() { + Jedis jedis = new Jedis( + getEnvironment().getServiceHost("another-redis-1", REDIS_PORT), + getEnvironment().getServicePort("another-redis-1", REDIS_PORT) + ); + + jedis.incr("test"); + jedis.incr("test"); + jedis.incr("test"); + + assertThat(jedis.get("test")).as("A redis instance defined in compose can be used in isolation").isEqualTo("3"); + } +} diff --git a/core/src/test/resources/v2-compose-test-with-multiple-networks.yml b/core/src/test/resources/v2-compose-test-with-multiple-networks.yml new file mode 100644 index 00000000000..56fb6799838 --- /dev/null +++ b/core/src/test/resources/v2-compose-test-with-multiple-networks.yml @@ -0,0 +1,14 @@ +version: '2.1' +services: + redis: + image: redis + networks: + - redis-net + another-redis: + image: redis + networks: + - another-redis-net + +networks: + redis-net: + another-redis-net: