Skip to content

Fail the start when the container exits before its ports are bound - #1513

Draft
cristianrgreco wants to merge 1 commit into
mainfrom
claude/fail-start-when-container-exits-before-ports-bound
Draft

cristianrgreco wants to merge 1 commit into
mainfrom
claude/fail-start-when-container-exits-before-ports-bound

Conversation

@cristianrgreco

Copy link
Copy Markdown
Collaborator

Summary

A container that exits before its ports are bound, for example one that crashes immediately on startup, held start() for 30 seconds and then failed with Timed out after 30000ms while waiting for container ports to be bound to the host, whatever the wait strategy. An exited container never reports port bindings, so the wait could not succeed.

  • The port-binding wait now checks the container state on each poll. If the container has exited and its ports are not bound, start() fails straight away with Container exited with code <code> before its ports were bound to the host.
  • A container that is restarting under a restart policy is still waited on: Docker only reports exited once the policy gives up.
  • An exited container with no exposed ports, the usual one-shot case, is unaffected.

The same helper is used by start(), container reuse and restart().

This follows testcontainers-go, whose host port wait checks the container state on every retry while polling for the mapped port (wait/host_port.go). testcontainers-java and testcontainers-dotnet do not check the state at this step.

Related to #1507 and #1512, which fix the same problem inside the HTTP wait strategy.

Verification

npm run format
npm run lint
npm run knip
npm run check-compiles
npx vitest run packages/testcontainers/src/generic-container/inspect-container-util-ports-exposed.test.ts
npx vitest run packages/testcontainers/src/generic-container/generic-container.test.ts packages/testcontainers/src/generic-container/generic-container-restart.test.ts packages/testcontainers/src/generic-container/generic-container-reuse.test.ts packages/testcontainers/src/wait-strategies/one-shot-startup-startegy.test.ts
  • format, lint, knip, check-compiles: clean.
  • inspect-container-util-ports-exposed.test.ts: 9 tests passed.
  • Start, restart, reuse and one-shot tests: 76 tests, 1 failure on the first run. should follow symlink when copying file to started container got exec exit code 128 instead of 1; it passed alone and generic-container.test.ts passed in full on rerun (51 passed, 1 skipped). It does not exercise the changed code.

Red-green

throws an error when the container exits before its ports are exposed:

  • Before the fix: Test timed out in 5000ms, because the helper keeps polling an exited container.
  • After the fix: passes, with a single inspect call.

End to end, a container with entrypoint exit 1 and an exposed port took 30s to fail before the fix and about 0.3s after it, three runs out of three.

There is no integration test: whether the first inspect sees an immediately exiting container as still running or already exited depends on timing inside the Docker daemon, so the unit tests cover the logic deterministically instead.

Not breaking

  • No API or default changes.
  • A start that succeeds today still succeeds: the new branch is only taken when the container has exited and its ports are not bound, which could only end in the 30 second timeout before.
  • The error message for that case changes from the timeout message to the exit message.

@cristianrgreco cristianrgreco added bug Something isn't working patch Backward compatible bug fix labels Oct 12, 2026
@cristianrgreco
cristianrgreco marked this pull request as draft October 13, 2026 15:55

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

bug Something isn't working patch Backward compatible bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant