-
-
Notifications
You must be signed in to change notification settings - Fork 591
fix: setup ExposeHostPorts forward on container start hook #3200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: setup ExposeHostPorts forward on container start hook #3200
Conversation
Fixes testcontainers#2811 Previously ExposedHostPorts would start an SSHD container prior to starting the testcontainer and inject a PostReadies lifecycle hook into the testcontainer in order to set up remote port forwarding from the host to the SSHD container so the testcontainer can talk to the host via the SSHD container This would be an issue if the testcontainer depends on accessing the host port on startup ( e.g., a proxy server ) as the forwarding for the host access isn't set up until all the WiatFor strategies on the testcontainer have completed. The fix is to move the forwarding setup to the PreCreates hook on the testcontainer. Since remote forwarding doesn't establish a connection to the host port until a connection is made to the remote port, this should not be an issue even if the host isn't listening yet and ensures the remote port is available to the testcontainer immediately.
* main: (236 commits) feat(kafka,redpanda): support for waiting for mapped ports without external checks (testcontainers#3165) chore: bump ryuk to 0.12.0 (testcontainers#3195) feat!: add options when creating RawCommand (testcontainers#3168) chore(deps)!: bump github.com/docker/docker from 28.1.1+incompatible to 28.2.2+incompatible (testcontainers#3194) feat(couchbase): adding auth to couchbase initCluster functions to support container reuse (testcontainers#3048) chore(deps): bump github.com/containerd/containerd/v2 (testcontainers#3167) docs(options): refactor options layout in modules (testcontainers#3163) fix(ci): do not run sonar for Testcontainers Cloud (testcontainers#3166) chore(ci): do not fail fast in the Testcontainers Cloud run (testcontainers#3164) feat: support adding wait strategies as functional option (testcontainers#3161) fix(etcd): expose ports for the etcd nodes (testcontainers#3162) fix(wait): no port to wait for (testcontainers#3158) feat: add more functional options for customising containers (testcontainers#3156) docs(redpanda): update sasl authentication option to use scram sha 256 (testcontainers#3126) chore(deps): bump mkdocs-include-markdown-plugin from 6.2.2 to 7.1.5 (testcontainers#3137) chore(deps): bump github.com/shirou/gopsutil/v4 from 4.25.1 to 4.25.4 (testcontainers#3133) chore(deps): bump github.com/docker/docker from 28.0.1+incompatible to 28.1.1+incompatible (testcontainers#3152) feat(memcached): add memcached module (testcontainers#3132) fix(etcd): single node etcd cluster access (testcontainers#3149) feat(valkey): add TLS support for Valkey (testcontainers#3131) ...
✅ Deploy Preview for testcontainers-go ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
* main: (30 commits) fix: preserve unix socket schema in testcontainers host from properties (testcontainers#3213) feat(registry): add helper functions to pull and tag images (testcontainers#3275) fix(reaper): remove termSignal override (testcontainers#3261) chore(deps): bump ryuk to v0.13.0, which uses scratch as base image (testcontainers#3274) chore(release): refine release script to update inter-module dependencies (testcontainers#3273) fix(registry): update `WithHtpasswd` to use `os.CreateTemp` instead of `os.Create` with `filepath.Join`. (testcontainers#3272) chore(deps): bump github.com/docker/docker from 28.2.2+incompatible to 28.3.3+incompatible (testcontainers#3270) chore(postgres): use require.NotNil instead of assert.NotNil (testcontainers#3252) fix(nats): use wait for listening port instead of wait for log (testcontainers#3256) chore(deps): bump github.com/go-viper/mapstructure/v2 (testcontainers#3267) fix(postgres): snapshot restore (testcontainers#3264) fix(dockermcpgateway): use duckduckgo instead of brave (testcontainers#3247) feat: add Solace pubsub+ module (testcontainers#3230) feat(options): add WithProvider (testcontainers#3241) chore(deps): bump github/codeql-action from 3.29.2 to 3.29.3 (testcontainers#3237) chore(deps): bump golang.org/x/oauth2 in /modules/weaviate (testcontainers#3240) chore(deps): bump mkdocs-include-markdown-plugin from 7.1.5 to 7.1.6 (testcontainers#3239) chore(deps): bump requests from 2.32.0 to 2.32.4 (testcontainers#3204) feat(mcpgateway): add MCP gateway module (testcontainers#3232) chore(deps): bump golang.org/x/oauth2 in /modules/pulsar (testcontainers#3236) ...
|
When is this going to be merged @mdelapenya @stevenh ? |
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughSSH-based port forwarding is moved earlier: the tunnel is created in PreCreates via a ContainerRequestHook using Changes
Sequence Diagram(s)sequenceDiagram
actor Test
participant PreCreate as PreCreates hook
participant SSH as SSH helper
participant Container as Target container
participant Host as Host service
rect rgb(230,245,230)
Note over PreCreate: New flow — tunnel created before container start
Test->>PreCreate: execute PreCreates hooks (ContainerRequest)
PreCreate->>SSH: exposeHostPort(req.HostAccessPorts...)
SSH->>Host: establish remote forwarding
PreCreate->>Container: create/start container (forwarding available)
Container->>Host: startup health checks / waits use forwarded ports
end
rect rgb(245,230,230)
Note over Container: Old flow — tunnel after readiness (shown for contrast)
Test->>Container: create/start container
Container->>Container: perform startup checks (no forwarding)
Container->>SSH: PostReadies -> exposeHostPort(...) (runs later)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (1)📓 Common learnings🔇 Additional comments (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@kberezin-nshl my bad, this PR was piled in the list. Thanks for the ping 🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
port_forwarding.go(1 hunks)port_forwarding_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-29T13:57:14.636Z
Learnt from: mdelapenya
Repo: testcontainers/testcontainers-go PR: 3319
File: modules/arangodb/arangodb.go:46-57
Timestamp: 2025-09-29T13:57:14.636Z
Learning: In testcontainers-go ArangoDB module, the wait strategy combines port listening check with HTTP readiness check using wait.ForAll - both strategies are required and complementary, not redundant.
Applied to files:
port_forwarding_test.go
📚 Learning: 2025-09-29T15:08:18.694Z
Learnt from: mdelapenya
Repo: testcontainers/testcontainers-go PR: 3320
File: modules/artemis/artemis.go:98-103
Timestamp: 2025-09-29T15:08:18.694Z
Learning: In testcontainers-go, nat.Port is a type alias for string, so untyped string constants can be passed directly to functions expecting nat.Port (like wait.ForListeningPort) without explicit type conversion - the Go compiler handles the implicit conversion automatically.
Applied to files:
port_forwarding_test.go
🧬 Code graph analysis (2)
port_forwarding.go (2)
lifecycle.go (2)
ContainerLifecycleHooks(43-55)ContainerRequestHook(24-24)container.go (1)
ContainerRequest(131-171)
port_forwarding_test.go (3)
wait/wait.go (1)
Strategy(17-19)wait/exec.go (1)
ForExec(72-74)port_forwarding.go (1)
HostInternal(28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: lint (modules/cassandra) / lint: modules/cassandra
- GitHub Check: lint (modules/artemis) / lint: modules/artemis
- GitHub Check: lint (modules/grafana-lgtm) / lint: modules/grafana-lgtm
- GitHub Check: lint (modules/aerospike) / lint: modules/aerospike
- GitHub Check: lint (modules/solace) / lint: modules/solace
- GitHub Check: lint (modules/mockserver) / lint: modules/mockserver
- GitHub Check: lint / lint:
- GitHub Check: lint (modules/meilisearch) / lint: modules/meilisearch
- GitHub Check: lint (modules/mongodb) / lint: modules/mongodb
- GitHub Check: lint (modules/surrealdb) / lint: modules/surrealdb
- GitHub Check: lint (modules/dynamodb) / lint: modules/dynamodb
- GitHub Check: lint (modules/couchbase) / lint: modules/couchbase
- GitHub Check: lint (modules/azurite) / lint: modules/azurite
- GitHub Check: lint (modules/mssql) / lint: modules/mssql
- GitHub Check: lint (modules/k3s) / lint: modules/k3s
- GitHub Check: lint (modules/elasticsearch) / lint: modules/elasticsearch
- GitHub Check: lint (modules/gcloud) / lint: modules/gcloud
- GitHub Check: lint (modules/postgres) / lint: modules/postgres
- GitHub Check: lint (modules/cockroachdb) / lint: modules/cockroachdb
- GitHub Check: Analyze (go)
🔇 Additional comments (2)
port_forwarding.go (1)
163-173: LGTM! Lifecycle hook change correctly fixes the issue.Moving SSH tunnel setup from
PostReadiestoPreCreatesensures host ports are accessible during container startup and wait strategies. The hook signature change fromContainerHooktoContainerRequestHookis correct, andreq.HostAccessPortsis available at this lifecycle stage. Since SSH remote port forwarding (line 316) only establishes the listener and doesn't require an active host connection until traffic arrives, this earlier setup aligns with the PR objectives.port_forwarding_test.go (1)
42-51: Wait strategies are never used—test doesn't verify the fix.The wait strategies verify that host ports are accessible during container startup (the core behavior this PR fixes), but they are never applied to any container request. Without these wait strategies, the test cannot confirm that ports are reachable before the container becomes ready.
Add the wait strategies to the container request. For example, in
testExposeHostPortsor pass them as a parameter and combine them withwait.ForAll:func testExposeHostPorts(t *testing.T, hostPorts []int, waitStrats []wait.Strategy, hasNetwork, hasHostAccess bool) { // ... existing code ... opts := []testcontainers.ContainerCustomizer{ testcontainers.WithHostPortAccess(hostAccessPorts...), testcontainers.WithCmd("top"), } if hasHostAccess && len(waitStrats) > 0 { opts = append(opts, testcontainers.WithWaitStrategy(wait.ForAll(waitStrats...))) } // ... rest of function }Then update the test calls to pass the wait strategies, e.g.:
testExposeHostPorts(t, singlePort, waitStrategies[0:1], false, false)⛔ Skipped due to learnings
Learnt from: mdelapenya Repo: testcontainers/testcontainers-go PR: 3319 File: modules/arangodb/arangodb.go:46-57 Timestamp: 2025-09-29T13:57:14.636Z Learning: In testcontainers-go ArangoDB module, the wait strategy combines port listening check with HTTP readiness check using wait.ForAll - both strategies are required and complementary, not redundant.Learnt from: mdelapenya Repo: testcontainers/testcontainers-go PR: 3320 File: modules/artemis/artemis.go:98-103 Timestamp: 2025-09-29T15:08:18.694Z Learning: In testcontainers-go, nat.Port is a type alias for string, so untyped string constants can be passed directly to functions expecting nat.Port (like wait.ForListeningPort) without explicit type conversion - the Go compiler handles the implicit conversion automatically.
* main: fix(kafka): strip architecture suffix from Kafka image tags for semver parsing (testcontainers#3276) chore(deps): bump github.com/dvsekhvalnov/jose2go in /modules/pulsar (testcontainers#3491)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
port_forwarding_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-29T15:08:18.694Z
Learnt from: mdelapenya
Repo: testcontainers/testcontainers-go PR: 3320
File: modules/artemis/artemis.go:98-103
Timestamp: 2025-09-29T15:08:18.694Z
Learning: In testcontainers-go, nat.Port is a type alias for string, so untyped string constants can be passed directly to functions expecting nat.Port (like wait.ForListeningPort) without explicit type conversion - the Go compiler handles the implicit conversion automatically.
Applied to files:
port_forwarding_test.go
📚 Learning: 2025-09-29T13:57:14.636Z
Learnt from: mdelapenya
Repo: testcontainers/testcontainers-go PR: 3319
File: modules/arangodb/arangodb.go:46-57
Timestamp: 2025-09-29T13:57:14.636Z
Learning: In testcontainers-go ArangoDB module, the wait strategy combines port listening check with HTTP readiness check using wait.ForAll - both strategies are required and complementary, not redundant.
Applied to files:
port_forwarding_test.go
🧬 Code graph analysis (1)
port_forwarding_test.go (3)
wait/wait.go (1)
Strategy(17-19)wait/exec.go (1)
ForExec(72-74)port_forwarding.go (1)
HostInternal(28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: Redirect rules - testcontainers-go
- GitHub Check: Header rules - testcontainers-go
- GitHub Check: Pages changed - testcontainers-go
- GitHub Check: lint (modules/registry) / lint: modules/registry
- GitHub Check: lint (modules/meilisearch) / lint: modules/meilisearch
- GitHub Check: lint (modules/dockermodelrunner) / lint: modules/dockermodelrunner
- GitHub Check: lint (modules/azurite) / lint: modules/azurite
- GitHub Check: lint (modules/dynamodb) / lint: modules/dynamodb
- GitHub Check: lint (modules/qdrant) / lint: modules/qdrant
- GitHub Check: lint (modules/mssql) / lint: modules/mssql
- GitHub Check: lint (modules/nats) / lint: modules/nats
- GitHub Check: lint (modules/mongodb) / lint: modules/mongodb
- GitHub Check: lint (modules/postgres) / lint: modules/postgres
- GitHub Check: Analyze (go)
| "github.com/testcontainers/testcontainers-go/wait" | ||
| ) | ||
|
|
||
| const ( | ||
| expectedResponse = "Hello, World!" | ||
| ) | ||
|
|
||
| func TestExposeHostPorts(t *testing.T) { | ||
| hostPorts := make([]int, 3) | ||
| const numberOfPorts = 3 | ||
|
|
||
| servers := make([]*httptest.Server, numberOfPorts) | ||
| hostPorts := make([]int, numberOfPorts) | ||
| waitStrategies := make([]wait.Strategy, numberOfPorts) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New waitStrategies are never used; clarify intent or remove to avoid dead code
The waitStrategies slice is fully populated with wait.ForExec(...).WithExitCodeMatcher(...).WithResponseMatcher(...), but it’s never read or passed into testExposeHostPorts / container options. As written, the new wait logic has no effect, and the tests still only validate host access via the explicit containerHasHostAccess / containerHasNoHostAccess calls after the container is started.
Similarly, the servers slice is only written to and never read, so it’s functionally redundant right now.
Depending on your intent:
- If these strategies are meant to assert that host ports are reachable during container startup (per the PR description), you likely want to plumb them into the container’s waiting strategy (e.g., via a
WaitingFor/ wait customization) and possibly thread them intotestExposeHostPorts. - If they’re not required, consider removing
waitStrategies(and thewaitimport) and possibly the unusedserversslice to keep the test simple and avoid confusion.
Also applies to: 37-49
🤖 Prompt for AI Agents
In port_forwarding_test.go around lines 18 to 31 (and similarly 37 to 49), the
slices waitStrategies and servers are built but never used; either wire them
into the container waiting logic or remove them — to fix quickly, delete the
wait import, the waitStrategies and servers declarations and all code that
populates them (including the per-port wait.ForExec(...) setup), and remove any
related unused variables so the test only keeps hostPorts and the actual
assertions; if you prefer to use them instead, pass the corresponding
wait.Strategy into the container creation (e.g., via
WaitingFor/WithStartupTimeout or by extending testExposeHostPorts to accept and
apply a per-port wait strategy) and ensure servers is read where its
httptest.Servers are needed.
Fixes #2811
Previously ExposedHostPorts would start an SSHD container prior to
starting the testcontainer and inject a PostReadies lifecycle hook into
the testcontainer in order to set up remote port forwarding from the
host to the SSHD container so the testcontainer can talk to the host via
the SSHD container
This would be an issue if the testcontainer depends on accessing the
host port on startup ( e.g., a proxy server ) as the forwarding for the
host access isn't set up until all the WiatFor strategies on the
testcontainer have completed.
The fix is to move the forwarding setup to the PreCreates hook on the
testcontainer. Since remote forwarding doesn't establish a connection to
the host port until a connection is made to the remote port, this should
not be an issue even if the host isn't listening yet and ensures the
remote port is available to the testcontainer immediately.
What does this PR do?
Changes the lifecycle hook for the ExposedHostPorts forwarding to happen on PreCreates of the testcontainer instead of PostReadies. Additionally updates the port forwarding tests to ensure the host ports are accessible on startup and that there's no issues even if the host isn't listening until PostCreates.
Why is it important?
Previously ExposedHostPorts would start an SSHD container prior to starting the testcontainer and inject a PostReadies lifecycle hook into the testcontainer in order to set up remote port forwarding from the host to the SSHD container so the testcontainer can talk to the host via the SSHD container
This would be an issue if the testcontainer depends on accessing the host port on startup ( e.g., a proxy server ) as the forwarding for the host access isn't set up until all the WiatFor strategies on the testcontainer have completed.
Related issues
How to test this PR
go test port_forwarding_test.goAdditionally, I've provided a minimal example of my use case where I ran into the issue trying to test my Caddy configuration as an API gateway using testcontainers.