You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
[Bug]: reaper reuse path resolves the Docker host through reaper_default even when bridge exists; another session's Ryuk then removes it (no route to host) #3941
v0.44.0 (the code is unchanged on main at 9d2885d)
Using the latest Testcontainers version?
Yes
Host OS
Linux (Ubuntu 24.04 for the reproduction below; first seen on Linux self-hosted CI runners)
Host arch
x86_64 for the reproduction; arm64 on the CI runners
Go version
1.26.4 (golang:1.26 image) for the reproduction; 1.26.6 on CI
Docker version
Docker Engine 29.5.3, API 1.54
Docker info
Server Version: 29.5.3
Operating System: Ubuntu 24.04.4 LTS
OSType: linux
Architecture: x86_64
Storage Driver: overlayfs
Cgroup Driver: systemd
What happened?
When tests run inside a container (Docker-out-of-Docker, /var/run/docker.sock mounted) and one go test runs more than one package, the later test processes of a session resolve the reaper's address through a network named reaper_default, although the default bridge network exists.
reaper_default is created with the creating session's labels (org.testcontainers.sessionId, org.testcontainers.reap=true). When that session ends, its Ryuk removes the network. Any other session on the same Docker daemon that reached its reaper through reaper_default's gateway then fails to create containers:
create container: reaper: connect: dial reaper 172.19.0.1:45201: dial tcp 172.19.0.1:45201: connect: no route to host
On CI, where several runner containers share one Docker daemon, this showed up as intermittent failures. A container creation failed on the reaper connect ("no route to host" or a timeout), or the connect retries used up the test's deadline.
Why
The first process of a session creates Ryuk through a provider built with WithDefaultBridgeNetwork(Bridge) (provider.go#L114). Inside a container, DaemonHost() then resolves to the bridge gateway.
A later process of the same session reuses that Ryuk: reuseOrCreate → lookupContainer (reaper.go#L158). lookupContainer builds its own provider with NewDockerProvider()withoutWithDefaultBridgeNetwork (reaper.go#L165).
fromContainer computes the endpoint with that provider (reaper.go#L357): Host() → daemonHostLocked() → InAContainer() (docker.go#L1636) → ensureDefaultNetworkLocked() (docker.go#L1773).
When the creating session ends, its Ryuk prunes reaper_default. A concurrent session whose reused reaper resolved to that network's gateway can no longer reach its own Ryuk.
Minimal reproduction
The module depends only on github.com/testcontainers/testcontainers-go v0.44.0. Packages a, b and c each contain this test:
Package d starts one such container, sleeps 30 s, then starts another.
docker network create tcrepro
RUN="docker run --rm --network tcrepro -v /var/run/docker.sock:/var/run/docker.sock -v $PWD:/src -w /src golang:1.26"$RUN go test -count=1 -p 1 ./a ./b # session A$RUN go test -count=1 -p 1 -v ./c ./d # session B, started right after A ends
What I see:
During session A. When b logs 🔥 Reaper obtained from Docker for this test session, reaper_default appears (172.19.0.0/16, gateway 172.19.0.1), labelled org.testcontainers.sessionId=<session A> and org.testcontainers.reap=true. It is removed about 10 s after A ends.
During session B.d reuses B's Ryuk while A's reaper_default still exists, so its reaper endpoint becomes 172.19.0.1:<port>. After A's Ryuk removes the network, d's second container fails with the error above.
With the workaround. Setting TESTCONTAINERS_HOST_OVERRIDE to the bridge gateway (172.17.0.1 here) for session B makes the same sequence pass.
-p 1 only makes the timing deterministic. The reuse path is taken whenever a later process of the session finds that session's Ryuk already running.
Relevant log output
# session B (-v), trimmed
Test SessionID: 54c8c31a0bc4679563710b6f7ae11b120b8fb368ce41c1f79c4c0473bcdfccef
--- PASS: TestStartsOneContainer (1.74s)
Test SessionID: 54c8c31a0bc4679563710b6f7ae11b120b8fb368ce41c1f79c4c0473bcdfccef
2026/10/07 05:45:16 🔥 Reaper obtained from Docker for this test session 73c48704
d_test.go:32: create container: reaper: connect: dial reaper 172.19.0.1:45201: dial tcp 172.19.0.1:45201: connect: no route to host
--- FAIL: TestStartsTwoContainersApart (41.55s)
Additional information
Suggested fix. Build the provider in lookupContainer with WithDefaultBridgeNetwork(Bridge), or reuse the provider passed into reuseOrCreate, so that the create path and the reuse path resolve the same host. Separately, a network that every session on the daemon may resolve through probably should not carry one session's reap labels.
Workaround. When running inside a container, set TESTCONTAINERS_HOST_OVERRIDE to the default bridge gateway before the first container is created.
Testcontainers version
v0.44.0 (the code is unchanged on
mainat 9d2885d)Using the latest Testcontainers version?
Yes
Host OS
Linux (Ubuntu 24.04 for the reproduction below; first seen on Linux self-hosted CI runners)
Host arch
x86_64 for the reproduction; arm64 on the CI runners
Go version
1.26.4 (
golang:1.26image) for the reproduction; 1.26.6 on CIDocker version
Docker info
What happened?
When tests run inside a container (Docker-out-of-Docker,
/var/run/docker.sockmounted) and onego testruns more than one package, the later test processes of a session resolve the reaper's address through a network namedreaper_default, although the defaultbridgenetwork exists.reaper_defaultis created with the creating session's labels (org.testcontainers.sessionId,org.testcontainers.reap=true). When that session ends, its Ryuk removes the network. Any other session on the same Docker daemon that reached its reaper throughreaper_default's gateway then fails to create containers:On CI, where several runner containers share one Docker daemon, this showed up as intermittent failures. A container creation failed on the reaper connect ("no route to host" or a timeout), or the connect retries used up the test's deadline.
Why
WithDefaultBridgeNetwork(Bridge)(provider.go#L114). Inside a container,DaemonHost()then resolves to thebridgegateway.reuseOrCreate→lookupContainer(reaper.go#L158).lookupContainerbuilds its own provider withNewDockerProvider()withoutWithDefaultBridgeNetwork(reaper.go#L165).fromContainercomputes the endpoint with that provider (reaper.go#L357):Host()→daemonHostLocked()→InAContainer()(docker.go#L1636) →ensureDefaultNetworkLocked()(docker.go#L1773).defaultBridgeNetworkName,case p.defaultBridgeNetworkName(docker.go#L1789) never matchesbridge. So the provider uses an existingreaper_default, or creates one (docker.go#L1802), withGenericLabels()(docker.go#L1805): the creating session's ID andreap=true. As far as I can tell, Issue #243: Introducing the default network in case "bridge" is disabled #244 introducedreaper_defaultfor environments wherebridgeis unavailable, not for this case.reaper_default. A concurrent session whose reused reaper resolved to that network's gateway can no longer reach its own Ryuk.Minimal reproduction
The module depends only on
github.com/testcontainers/testcontainers-go v0.44.0. Packagesa,bandceach contain this test:Package
dstarts one such container, sleeps 30 s, then starts another.What I see:
blogs🔥 Reaper obtained from Docker for this test session,reaper_defaultappears (172.19.0.0/16, gateway172.19.0.1), labelledorg.testcontainers.sessionId=<session A>andorg.testcontainers.reap=true. It is removed about 10 s after A ends.dreuses B's Ryuk while A'sreaper_defaultstill exists, so its reaper endpoint becomes172.19.0.1:<port>. After A's Ryuk removes the network,d's second container fails with the error above.TESTCONTAINERS_HOST_OVERRIDEto thebridgegateway (172.17.0.1here) for session B makes the same sequence pass.-p 1only makes the timing deterministic. The reuse path is taken whenever a later process of the session finds that session's Ryuk already running.Relevant log output
Additional information
Suggested fix. Build the provider in
lookupContainerwithWithDefaultBridgeNetwork(Bridge), or reuse the provider passed intoreuseOrCreate, so that the create path and the reuse path resolve the same host. Separately, a network that every session on the daemon may resolve through probably should not carry one session's reap labels.Workaround. When running inside a container, set
TESTCONTAINERS_HOST_OVERRIDEto the defaultbridgegateway before the first container is created.Related:
newReaperpath, not the reuse path;NewDockerProvider()inlookupContainerunchanged;reaper_default.