Class LoopbackPortPublisher
- All Implemented Interfaces:
org.testcontainers.core.CreateContainerCmdModifier
Docker's default publish is the wildcard address, and that coexists with a process
already holding 127.0.0.1:<port>: the more specific bind wins, so a client resolving
localhost to the IPv4 loopback reaches the stranger while the container sits there
healthy. Asking for a loopback address without a port makes the kernel pick a port that is free
on that address, so the collision has nowhere to form.
This is registered through META-INF/services rather than applied per harness because
GenericContainer loads the SPI per container
instance, which reaches containers no harness in this repository constructs —
testcontainers' own Ryuk reaper above all. Ryuk is contacted before any harness runs, so a
collision on its port fails a whole module's integration tests with a message naming no connector
at all. No per-harness change can reach that.
What this does not cost. curl http://localhost:<port> still works. #1021
expected it to break, because curl prefers ::1 and nothing is bound there any
more — measured 2026-08-22 with curl 8.7.1 on macOS, it tries ::1, is refused
immediately, falls back to 127.0.0.1 and connects. Prefer curl
http://127.0.0.1:<port> anyway: the fallback rests on that refusal being instant, and if
something is listening on ::1:<port> curl reaches it rather than the container —
the same shadowing hazard, moved to IPv6, and now reachable only by a hand-run curl
rather than by the JVM.
Only a loopback Docker host, and the guard is an address test. Against a daemon reached
at a routable address — DOCKER_HOST=tcp://…, Testcontainers Cloud — a loopback address is
the daemon's, which the test JVM cannot reach, so the wildcard publish is left alone.
What that test cannot see is a daemon reached at a loopback address but running
elsewhere: an SSH tunnel on tcp://127.0.0.1:2375, or a VM whose port forwarder only
carries wildcard-bound guest ports. There the modifier engages and binds inside the daemon, and
the connection is refused. Nothing distinguishes that from a local daemon — a local one exposed
over tcp://127.0.0.1 is a setup this should serve — so the escape is explicit
rather than inferred: pass -Dflink.gcp.tests.loopback-publish=false. Maven forwards a
command-line -D into the surefire fork, and .mvn/maven.config or
MAVEN_OPTS carries it for a shell that sets no flags per run.
That restores testcontainers' own wildcard publish. The property is read per container rather than once, so clearing it mid-run re-engages the rewrite for later containers. Docker Desktop for macOS — itself VM-backed — is measured working without it; colima and Rancher Desktop are not measured, which is what the escape is for.
ADR-0132 carries the evidence, the declined alternatives, and the dependency-bump tripwire that this class's survival rests on.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptioncom.github.dockerjava.api.command.CreateContainerCmdmodify(com.github.dockerjava.api.command.CreateContainerCmd createContainerCmd)
-
Constructor Details
-
LoopbackPortPublisher
public LoopbackPortPublisher()Required byServiceLoader, which is the only intended caller.
-
-
Method Details
-
modify
public com.github.dockerjava.api.command.CreateContainerCmd modify(com.github.dockerjava.api.command.CreateContainerCmd createContainerCmd) - Specified by:
modifyin interfaceorg.testcontainers.core.CreateContainerCmdModifier
-