Skip to content

Fail wait strategies as soon as the container exits - #1512

Open
cristianrgreco wants to merge 5 commits into
mainfrom
claude/wait-abort-on-exit-9ee0a7
Open

cristianrgreco wants to merge 5 commits into
mainfrom
claude/wait-abort-on-exit-9ee0a7

Conversation

@cristianrgreco

@cristianrgreco cristianrgreco commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Wait.forHttp() kept polling until the startup timeout after the container had exited, so a service that crashed during startup held start() for the whole timeout and failed with URL /health not accessible after 60000ms. The other wait strategies did fail quickly, but only because the Docker call they make fails once the container is gone, so each reported the exit differently, and not always as an exit.

Exit detection now lives in AbstractWaitStrategy and applies to every wait strategy:

  • retryUntil (protected) wraps the polling loop. After an attempt that is not ready, or that throws, it inspects the container and fails with Container exited with code <code> if the container has exited. Otherwise it keeps polling, or rethrows the attempt's error.
  • withAbortOnContainerExit(false) (public) opts out. Wait.forAll([...]).withAbortOnContainerExit(false) applies it to every child strategy. Wait.forHttp(path, port, { abortOnContainerExit: false }) keeps working.
  • Wait.forListeningPorts(), Wait.forLogMessage() and Wait.forHealthCheck() now return their concrete classes, so the method can be chained on them.

Per strategy, before and after, for a container that exits during the wait:

Strategy Before After
HTTP waited the full startup timeout Container exited with code <code>
Listening ports (default) (HTTP code 409) container stopped/paused - container <id> is not running Container exited with code <code>
Shell command the same raw 409 Container exited with code <code>
Health check Health check failed: unhealthy (Docker marks a stopped container unhealthy) Container exited with code <code>
Log message Log stream ended and message "..." was not received Container exited with code <code>
One shot / StartupCheckStrategy expects the exit unchanged
  • A container that is restarting under a restart policy is still waited on: Docker reports restarting, and only reports exited once the policy gives up.
  • A container that logs the awaited message and then exits still passes the log wait.
  • The HTTP exit error no longer embeds the last 50 log lines. Include container logs in the error when a container fails to start #1510 appends the container logs to every failed-start error, for every strategy.
  • Docs: a general opt-out example replaces the HTTP-only one. Also fixes the insecure TLS example, which called .insecureTls(); the method is .allowInsecure().

Other implementations

This PR does what .NET does, but in the base class rather than around the strategies, so containers that are expected to exit (one-shot, or "log a message then exit") keep working without an opt-out.

Verification

npm run format
npm run lint
npm run knip
npm run check-compiles
npx vitest run packages/testcontainers/src
npx vitest run packages/modules/influxdb/src packages/modules/minio/src packages/modules/chromadb/src packages/modules/weaviate/src
  • format, lint, knip, check-compiles: clean.
  • packages/testcontainers/src: 452 tests, 1 failure: should not pull existing image without pull policy ("Failed to build image"). It starts no container, and its file passed on its own (10/10).
  • Modules that wait on HTTP or logs: 19 tests passed.

Red-green

Each new test was run on its own against the code before this change, then after it:

Test Before After
Shell should fail when the container exits (HTTP code 409) container stopped/paused ... is not running passes
Health check should fail when the container exits Health check failed: unhealthy passes
Log should fail when the container exits before logging the message Log stream ended and message "Ready" was not received passes
HTTP should fail when the container exits before the wait succeeds URL /hello-world not accessible after 60000ms (60.3s) passes in under 1s
Composite should apply abortOnContainerExit to its wait strategies method did not exist passes
AbstractWaitStrategy unit tests (exit, failed attempt, restarting, opt-out) method did not exist pass
  • should wait for a log message from a container that then exits passes before and after.
  • should keep waiting after the container exits when abortOnContainerExit is false (HTTP) passes before and after.

Not breaking

  • No signature changes: one public method is added, and three Wait factories return narrower types.
  • A wait that succeeds today still succeeds: exit is only checked after an attempt that is not ready, and containers that log the message and then exit, one-shot containers and containers restarting under a restart policy are not treated as failures.
  • What changes is the failure path. A start that was already going to fail now fails sooner (HTTP) or with a different message (the other strategies). withAbortOnContainerExit(false) restores the previous behaviour.
  • This changes a default, which the open-pr workflow lists under major. It is labelled minor because no start that succeeds today changes outcome; testcontainers-dotnet shipped its equivalent default-on check in a minor release (4.8.0).

Closes #1507

Wait.forHttp() polled until the startup timeout after the container had
exited. Exit detection (abortOnContainerExit) is now on by default; pass
{ abortOnContainerExit: false } to keep waiting.
@cristianrgreco cristianrgreco added enhancement New feature or request minor Backward compatible functionality labels Oct 12, 2026
@cristianrgreco
cristianrgreco marked this pull request as draft October 13, 2026 15:55
@cristianrgreco
cristianrgreco marked this pull request as ready for review October 13, 2026 16:08
@cristianrgreco cristianrgreco changed the title Fail the HTTP wait as soon as the container exits Fail wait strategies as soon as the container exits Oct 14, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request minor Backward compatible functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fail the HTTP wait as soon as the container exits

1 participant