In Testcontainers, how do two containers reach each other, and which port do they use?
answer
- One user-defined network for both containers
- Docker's embedded DNS resolves the name
- Give the target a stable alias
- Mapped ports are for the host only
- Peer connects on the real listening port
basics
~20 sPut both containers on one Testcontainers Network and give the target a network alias; the other container connects to that alias on the target's own internal port. Mapped ports are host-side only and must not be used between containers.
solid answer
~40 sCreate a network with `Network.newNetwork()` (or use the shared `Network.SHARED`), attach each container with `withNetwork(network)`, and label the target with `withNetworkAliases("db")`. Docker's embedded DNS on a user-defined network resolves that alias to the target container's address, so the client container connects to `db:5432` — the port the server actually listens on inside the container. `getMappedPort()` is wrong here: port publishing exists so the *host* can reach the container, and container-to-container traffic never traverses it. The test JVM is on the host, so it keeps using `getHost()` and `getMappedPort()` for its own connections; the same target therefore has two different addresses depending on who is calling. For the reverse direction — a container calling a service running on the host — Testcontainers offers `Testcontainers.exposeHostPorts(port)`, reachable inside containers as `host.testcontainers.internal`.
code
java · 17 linesNetwork network = Network.newNetwork();
PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16-alpine")
.withNetwork(network)
.withNetworkAliases("db");
GenericContainer<?> app = new GenericContainer<>("my/app:latest")
.withNetwork(network)
.withExposedPorts(8080)
// internal port of the database container, NOT getMappedPort(5432)
.withEnv("DB_URL", "jdbc:postgresql://db:5432/test");
db.start();
app.start();
// the test JVM lives on the host, so it uses the host-side coordinates
String appUrl = "http://" + app.getHost() + ":" + app.getMappedPort(8080);go deeper
Know the shape: Network.newNetwork(), withNetwork on both containers, withNetworkAliases on the one being called, and the peer connects to that alias on the normal service port.
Explain why the mapped port is wrong between containers — publishing is a host-side mapping — and that Docker's embedded DNS on a user-defined network is what resolves the alias.
Handle the full topology: the same container addressed two ways at once, ordering and readiness between dependent containers, and the container-to-host direction via exposeHostPorts and host.testcontainers.internal.
Decide how far a test suite should reproduce production topology in Docker versus stubbing the dependency, and what that costs in runtime, CI resources and diagnosis difficulty when it breaks.
## Three directions of traffic A container-backed test has up to three distinct paths, and each has its own addressing rule: 1. **Test JVM (host) to container** — use `getHost()` and `getMappedPort(containerPort)`. 2. **Container to container** — use a network alias and the target's *internal* port. 3. **Container to a service on the host** — expose the host port explicitly and address `host.testcontainers.internal`. Most confusion in this area is a case of applying rule 1 where rule 2 belongs. ## Setting up a shared network `Network.newNetwork()` creates a fresh user-defined Docker bridge network with a generated name; it implements `AutoCloseable` and Testcontainers cleans it up with the rest of the test resources. Every container that should see the others gets `withNetwork(network)`. On a user-defined bridge network — as opposed to Docker's legacy default bridge — Docker runs an embedded DNS server that resolves container names and network aliases to container IPs. `withNetworkAliases("db")` gives the target a stable, predictable name on that network, which matters because container names are otherwise generated. A container may carry several aliases. `Network.SHARED` is a convenience constant: a single network instance shared across the JVM, useful when many containers in a suite need to see each other without threading a `Network` reference through the code. ## Why the internal port, not the mapped port Port publishing (`-p`) creates a mapping on the *host's* network stack: traffic arriving at the host's ephemeral port is forwarded into the container. Two containers on the same Docker network talk directly over that network; their packets never reach the host mapping. So the client must address the port the server process actually binds — 5432 for Postgres, 9092 for a broker, 8080 for a web service — not the number `getMappedPort()` reports. A useful mental check: if the connection string is being consumed *inside* a container, no accessor from the Testcontainers API should appear in it except the alias you chose yourself. ## Consequences for configuration When a container under test needs the address of a dependency, you pass it as environment configuration built from the alias, for example an environment variable holding `jdbc:postgresql://db:5432/test`. When the *test* needs the same database, it builds a different string from `getHost()` and `getMappedPort(5432)`. Both are correct simultaneously; they are two views of one container. This is also why exposing a port is still worth doing on a container that is only consumed by another container: you expose it so the test can look inside — assert on data, inspect state — not because the peer container needs it. ## Startup ordering Attaching to a network does not order startup. If the client container must not start before the dependency is ready, start the dependency first (or declare a dependency with `dependsOn`) *and* give it a wait strategy that reflects real readiness. On a shared network, a client that starts too early sees DNS resolve but the connection refused. ## Reaching the host from a container The reverse direction comes up when the system under test runs in the JVM (a Spring Boot app on a random port) and a container must call back into it — a webhook receiver, a callback URL, a mock consumer. `org.testcontainers.Testcontainers.exposeHostPorts(int...)` makes the given host ports reachable from containers at the special host name `host.testcontainers.internal`. Call it before starting the containers that need it. ## Cleanup Networks, like containers, are labelled by Testcontainers and removed by the Ryuk resource reaper when the JVM exits, so a crashed run does not leave orphan networks behind. A `Network` created per test class is inexpensive; sharing one across a suite is mostly a convenience choice. ## What interviewers listen for The distinction between host-side and network-side addressing, stated without hedging, plus the observation that the same container legitimately has two addresses. Candidates who have only used single-container tests usually have not met this; candidates who have wired an app container to a database container answer it immediately.
- Can a container reach a service running in the test JVM on the host?Yes — call `Testcontainers.exposeHostPorts(port)` before starting the containers, and inside them the service is reachable at the host name `host.testcontainers.internal` on that same port. It is the usual way to let a container deliver a webhook back to the application under test.
- What is Network.SHARED for?It is a single shared network instance for the JVM, so containers across a suite can see each other without passing a `Network` object around. Use it when nearly everything needs one network; prefer an explicit `Network.newNetwork()` when you want isolation between test classes.
- Does putting two containers on one network guarantee the dependency is up first?No. Network membership says nothing about ordering or readiness. Start the dependency first and give it a wait strategy that reflects genuine readiness; otherwise the client container can start, resolve the alias by DNS, and still be refused on connect.
saying these in an interview costs you the question
- Passes getMappedPort() into another container's config
- Uses localhost between two containers
- Assumes the default bridge network resolves names
- Thinks network membership orders container startup
- Expects the container to reach the host as localhost