Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions docs/features/wait-strategies.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ const container = await new GenericContainer("alpine")
.start();
```

To keep waiting after the container has exited, for example when something other than a restart policy starts it again:

```js
const { GenericContainer, Wait } = require("testcontainers");

const container = await new GenericContainer("alpine")
.withWaitStrategy(Wait.forListeningPorts().withAbortOnContainerExit(false))
.start();
```

## Default wait strategy

By default, Testcontainers waits for a container health check when one is defined by the image or configured with `withHealthCheck`. If no health check is defined, or the image disables health checks with `HEALTHCHECK NONE`, it waits up to 60 seconds for mapped network ports to be bound.
Expand Down Expand Up @@ -125,16 +135,6 @@ const container = await new GenericContainer("redis")
.start();
```

Stop waiting after container exited if waiting for container restart not needed.

```js
const { GenericContainer, Wait } = require("testcontainers");

const container = await new GenericContainer("redis")
.withWaitStrategy(Wait.forHttp("/health", 8080, { abortOnContainerExit: true }))
.start();
```

### For status code

```js
Expand Down Expand Up @@ -174,7 +174,7 @@ const container = await new GenericContainer("redis")
```js
.withWaitStrategy(Wait.forHttp("/health", 8443)
.usingTls()
.insecureTls())
.allowInsecure())
```

## Shell command
Expand Down
26 changes: 0 additions & 26 deletions packages/testcontainers/src/utils/test-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import path from "node:path";
import type { Readable } from "node:stream";
import type { GetEventsOptions, ImageInspectInfo } from "dockerode";
import { Agent, request } from "undici";
import { IntervalRetry } from "../common";
import { getContainerRuntimeClient } from "../container-runtime";
import type { StartedDockerComposeEnvironment } from "../docker-compose-environment/started-docker-compose-environment";
import type { GenericContainer } from "../generic-container/generic-container";
import type { StartedTestContainer } from "../test-container";
import type { HealthCheckStatus } from "../types";
import { getHealthCheckStatusFromInspect } from "../wait-strategies/utils/health-check";
Expand Down Expand Up @@ -142,30 +140,6 @@ export async function deleteImageByName(imageName: string): Promise<void> {
await dockerode.getImage(imageName).remove();
}

export async function stopStartingContainer(container: GenericContainer, name: string) {
const client = await getContainerRuntimeClient();
const containerStartPromise = container.start();

const status = await new IntervalRetry<boolean, boolean>(500).retryUntil(
() =>
client.container
.getById(name)
.inspect()
.then((i) => i.State.Running)
.catch(() => false),
(running) => running,
() => false,
10000
);

if (!status) {
throw Error("failed start container");
}

await client.container.getById(name).stop();
await containerStartPromise;
}

export async function createTestServer(port: number): Promise<Server> {
const server = createServer((_req, res) => {
res.writeHead(200);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ describe("CompositeWaitStrategy", { timeout: 180_000 }, () => {
await expect(container.start()).rejects.toThrowError(`Shell command "exit 1" not successful after 100ms`);
});

it("should apply abortOnContainerExit to its wait strategies", async () => {
const container = new GenericContainer("cristianrgreco/testcontainer:1.1.14")
.withEntrypoint(["/bin/sh", "-c", "exit 1"])
.withWaitStrategy(
Wait.forAll([Wait.forSuccessfulCommand("stat /tmp/never.sock")]).withAbortOnContainerExit(false)
);

await expect(container.start()).rejects.not.toThrowError("Container exited");
});

it("should work with maximum outer timeout", async () => {
const container = new GenericContainer("cristianrgreco/testcontainer:1.1.14").withWaitStrategy(
Wait.forAll([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ export class CompositeWaitStrategy extends AbstractWaitStrategy {
return this;
}

public override withAbortOnContainerExit(abortOnContainerExit: boolean): this {
this.waitStrategies.forEach((waitStrategy) => {
if (waitStrategy instanceof AbstractWaitStrategy) {
waitStrategy.withAbortOnContainerExit(abortOnContainerExit);
}
});
return this;
}

public withDeadline(deadline: number): this {
this.deadline = deadline;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ describe("HealthCheckWaitStrategy", { timeout: 180_000 }, () => {
expect(await getRunningContainerNames()).not.toContain(containerName);
});

it("should fail when the container exits", async () => {
await expect(() =>
new GenericContainer("cristianrgreco/testcontainer:1.1.14")
.withEntrypoint(["/bin/sh", "-c", "exit 1"])
.withHealthCheck({ test: ["CMD-SHELL", "exit 0"] })
.withWaitStrategy(Wait.forHealthCheck())
.start()
).rejects.toThrowError("Container exited with code 1");
});

it("should wait for custom health check using CMD to run the command directly without a shell", async () => {
await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14")
.withExposedPorts(8080)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type Dockerode from "dockerode";
import { IntervalRetry, log } from "../common";
import { log } from "../common";
import { getContainerRuntimeClient } from "../container-runtime";
import { getHealthCheckStatusFromInspect } from "./utils/health-check";
import { AbstractWaitStrategy } from "./wait-strategy";
Expand All @@ -9,19 +9,21 @@ export class HealthCheckWaitStrategy extends AbstractWaitStrategy {
log.debug(`Waiting for health check...`, { containerId: container.id });
const client = await getContainerRuntimeClient();

const status = await new IntervalRetry<string | undefined, Error>(100).retryUntil(
const status = await this.retryUntil(
container,
100,
async () => getHealthCheckStatusFromInspect(await client.container.inspect(container)),
(healthCheckStatus) => healthCheckStatus === "healthy" || healthCheckStatus === "unhealthy",
() => {
const timeout = this.startupTimeoutMs;
const message = `Health check not healthy after ${timeout}ms`;
log.error(message, { containerId: container.id });
throw new Error(message);
},
this.startupTimeoutMs
}
);

if (status !== "healthy") {
await this.throwIfContainerExited(container);
const message = `Health check failed: ${status}`;
log.error(message, { containerId: container.id });
throw new Error(message);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type Dockerode from "dockerode";
import { IntervalRetry, log } from "../common";
import { log } from "../common";
import { getContainerRuntimeClient } from "../container-runtime";
import type { BoundPorts } from "../utils/bound-ports";
import { HostPortCheck, InternalPortCheck, type PortCheck } from "./utils/port-check";
Expand Down Expand Up @@ -60,15 +60,16 @@
port: number | string,
portCheck: PortCheck
): Promise<void> {
await new IntervalRetry<boolean, Error>(100).retryUntil(
await this.retryUntil(
container,
100,
() => portCheck.isBound(port),
(isBound) => isBound,
() => {
const message = `Port ${port} not bound after ${this.startupTimeoutMs}ms`;
log.error(message, { containerId: container.id });
throw new Error(message);

Check failure on line 71 in packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts

View workflow job for this annotation

GitHub Actions / Tests (scylladb, 22.x, docker) / Run

packages/modules/scylladb/src/scylladb-container.test.ts > ScyllaDB (scylladb/scylla:2026.3.3) > should create keyspace, a table, insert data, and retrieve it

Error: Port 9042/tcp not bound after 60000ms ❯ packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:71:15 ❯ IntervalRetry.retryUntil packages/testcontainers/src/common/retry.ts:49:16 ❯ HostPortWaitStrategy.retryUntil packages/testcontainers/src/wait-strategies/wait-strategy.ts:53:20 ❯ HostPortWaitStrategy.waitForPort packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:63:5 ❯ HostPortWaitStrategy.waitForInternalPorts packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:52:7 ❯ HostPortWaitStrategy.waitUntilReady packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:14:5 ❯ waitForContainer packages/testcontainers/src/wait-strategies/wait-for-container.ts:17:5 ❯ ScyllaContainer.startContainer packages/testcontainers/src/generic-container/generic-container.ts:232:5

Check failure on line 71 in packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts

View workflow job for this annotation

GitHub Actions / Tests (scylladb, 22.x, docker) / Run

packages/modules/scylladb/src/scylladb-container.test.ts > ScyllaDB (scylladb/scylla:2026.3.3) > should create keyspace, a table, insert data, and retrieve it

Error: Port 9042/tcp not bound after 60000ms ❯ packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:71:15 ❯ IntervalRetry.retryUntil packages/testcontainers/src/common/retry.ts:49:16 ❯ HostPortWaitStrategy.retryUntil packages/testcontainers/src/wait-strategies/wait-strategy.ts:53:20 ❯ HostPortWaitStrategy.waitForPort packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:63:5 ❯ HostPortWaitStrategy.waitForInternalPorts packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:52:7 ❯ HostPortWaitStrategy.waitUntilReady packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:14:5 ❯ waitForContainer packages/testcontainers/src/wait-strategies/wait-for-container.ts:17:5 ❯ ScyllaContainer.startContainer packages/testcontainers/src/generic-container/generic-container.ts:232:5

Check failure on line 71 in packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts

View workflow job for this annotation

GitHub Actions / Tests (scylladb, 22.x, docker) / Run

packages/modules/scylladb/src/scylladb-container.test.ts > ScyllaDB (scylladb/scylla:2026.3.3) > should create keyspace, a table, insert data, and retrieve it

Error: Port 9042/tcp not bound after 60000ms ❯ packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:71:15 ❯ IntervalRetry.retryUntil packages/testcontainers/src/common/retry.ts:49:16 ❯ HostPortWaitStrategy.retryUntil packages/testcontainers/src/wait-strategies/wait-strategy.ts:53:20 ❯ HostPortWaitStrategy.waitForPort packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:63:5 ❯ HostPortWaitStrategy.waitForInternalPorts packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:52:7 ❯ HostPortWaitStrategy.waitUntilReady packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:14:5 ❯ waitForContainer packages/testcontainers/src/wait-strategies/wait-for-container.ts:17:5 ❯ ScyllaContainer.startContainer packages/testcontainers/src/generic-container/generic-container.ts:232:5

Check failure on line 71 in packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts

View workflow job for this annotation

GitHub Actions / Tests (scylladb, 22.x, docker) / Run

packages/modules/scylladb/src/scylladb-container.test.ts > ScyllaDB (scylladb/scylla:2026.3.3) > should create keyspace, a table, insert data, and retrieve it

Error: Port 9042/tcp not bound after 60000ms ❯ packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:71:15 ❯ IntervalRetry.retryUntil packages/testcontainers/src/common/retry.ts:49:16 ❯ HostPortWaitStrategy.retryUntil packages/testcontainers/src/wait-strategies/wait-strategy.ts:53:20 ❯ HostPortWaitStrategy.waitForPort packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:63:5 ❯ HostPortWaitStrategy.waitForInternalPorts packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:52:7 ❯ HostPortWaitStrategy.waitUntilReady packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:14:5 ❯ waitForContainer packages/testcontainers/src/wait-strategies/wait-for-container.ts:17:5 ❯ ScyllaContainer.startContainer packages/testcontainers/src/generic-container/generic-container.ts:232:5

Check failure on line 71 in packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts

View workflow job for this annotation

GitHub Actions / Tests (scylladb, 22.x, docker) / Run

packages/modules/scylladb/src/scylladb-container.test.ts > ScyllaDB (scylladb/scylla:2026.3.3) > should connect and execute a query

Error: Port 9042/tcp not bound after 60000ms ❯ packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:71:15 ❯ IntervalRetry.retryUntil packages/testcontainers/src/common/retry.ts:49:16 ❯ HostPortWaitStrategy.retryUntil packages/testcontainers/src/wait-strategies/wait-strategy.ts:53:20 ❯ HostPortWaitStrategy.waitForPort packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:63:5 ❯ HostPortWaitStrategy.waitForInternalPorts packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:52:7 ❯ HostPortWaitStrategy.waitUntilReady packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:14:5 ❯ waitForContainer packages/testcontainers/src/wait-strategies/wait-for-container.ts:17:5 ❯ ScyllaContainer.startContainer packages/testcontainers/src/generic-container/generic-container.ts:232:5

Check failure on line 71 in packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts

View workflow job for this annotation

GitHub Actions / Tests (scylladb, 22.x, docker) / Run

packages/modules/scylladb/src/scylladb-container.test.ts > ScyllaDB (scylladb/scylla:2026.3.3) > should connect and execute a query

Error: Port 9042/tcp not bound after 60000ms ❯ packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:71:15 ❯ IntervalRetry.retryUntil packages/testcontainers/src/common/retry.ts:49:16 ❯ HostPortWaitStrategy.retryUntil packages/testcontainers/src/wait-strategies/wait-strategy.ts:53:20 ❯ HostPortWaitStrategy.waitForPort packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:63:5 ❯ HostPortWaitStrategy.waitForInternalPorts packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:52:7 ❯ HostPortWaitStrategy.waitUntilReady packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:14:5 ❯ waitForContainer packages/testcontainers/src/wait-strategies/wait-for-container.ts:17:5 ❯ ScyllaContainer.startContainer packages/testcontainers/src/generic-container/generic-container.ts:232:5

Check failure on line 71 in packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts

View workflow job for this annotation

GitHub Actions / Tests (scylladb, 22.x, docker) / Run

packages/modules/scylladb/src/scylladb-container.test.ts > ScyllaDB (scylladb/scylla:2026.3.3) > should connect and execute a query

Error: Port 9042/tcp not bound after 60000ms ❯ packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:71:15 ❯ IntervalRetry.retryUntil packages/testcontainers/src/common/retry.ts:49:16 ❯ HostPortWaitStrategy.retryUntil packages/testcontainers/src/wait-strategies/wait-strategy.ts:53:20 ❯ HostPortWaitStrategy.waitForPort packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:63:5 ❯ HostPortWaitStrategy.waitForInternalPorts packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:52:7 ❯ HostPortWaitStrategy.waitUntilReady packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:14:5 ❯ waitForContainer packages/testcontainers/src/wait-strategies/wait-for-container.ts:17:5 ❯ ScyllaContainer.startContainer packages/testcontainers/src/generic-container/generic-container.ts:232:5

Check failure on line 71 in packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts

View workflow job for this annotation

GitHub Actions / Tests (scylladb, 22.x, docker) / Run

packages/modules/scylladb/src/scylladb-container.test.ts > ScyllaDB (scylladb/scylla:2026.3.3) > should connect and execute a query

Error: Port 9042/tcp not bound after 60000ms ❯ packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:71:15 ❯ IntervalRetry.retryUntil packages/testcontainers/src/common/retry.ts:49:16 ❯ HostPortWaitStrategy.retryUntil packages/testcontainers/src/wait-strategies/wait-strategy.ts:53:20 ❯ HostPortWaitStrategy.waitForPort packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:63:5 ❯ HostPortWaitStrategy.waitForInternalPorts packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:52:7 ❯ HostPortWaitStrategy.waitUntilReady packages/testcontainers/src/wait-strategies/host-port-wait-strategy.ts:14:5 ❯ waitForContainer packages/testcontainers/src/wait-strategies/wait-for-container.ts:17:5 ❯ ScyllaContainer.startContainer packages/testcontainers/src/generic-container/generic-container.ts:232:5
},
this.startupTimeoutMs
}
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ vi.mock("../container-runtime", async (importOriginal) => {
...actual,
getContainerRuntimeClient: async () => ({
info: { containerRuntime: { host: "localhost" } },
container: { inspect: vi.fn() },
container: { inspect: async () => ({ State: { Status: "running" } }) },
}),
};
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { RandomUuid } from "../common";
import { GenericContainer } from "../generic-container/generic-container";
import { checkContainerIsHealthy, checkContainerIsHealthyTls, stopStartingContainer } from "../utils/test-helper";
import { checkContainerIsHealthy, checkContainerIsHealthyTls } from "../utils/test-helper";
import { Wait } from "./wait";

const exitAfterFirstRequest = ["/bin/sh", "-c", "nc -l -p 8080; exit 1"];

describe("HttpWaitStrategy", { timeout: 180_000 }, () => {
it("should wait for 200", async () => {
await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14")
Expand Down Expand Up @@ -76,42 +77,25 @@ describe("HttpWaitStrategy", { timeout: 180_000 }, () => {
).rejects.toThrowError("URL /hello-world not accessible after 3000ms");
});

describe("when options.abortOnContainerExit is true", () => {
it("should fail if container exited before waiting pass", async () => {
const name = `container-name-${new RandomUuid().nextUuid()}`;
const data = [1, 2, 3];
const tail = 50;
const echoCmd = data.map((i) => `echo ${i}`).join(" && ");
const lastLogs = data.join("\n");
const container = new GenericContainer("cristianrgreco/testcontainer:1.1.14")
it("should fail when the container exits before the wait succeeds", async () => {
await expect(() =>
new GenericContainer("cristianrgreco/testcontainer:1.1.14")
.withExposedPorts(8080)
.withStartupTimeout(20000)
.withEntrypoint(["/bin/sh", "-c", `${echoCmd} && sleep infinity`])
.withWaitStrategy(Wait.forHttp("/hello-world", 8080, { abortOnContainerExit: true }))
.withName(name);

await expect(stopStartingContainer(container, name)).rejects.toThrowError(
new Error(`Container exited during HTTP healthCheck, last ${tail} logs: ${lastLogs}`)
);
});
.withEntrypoint(exitAfterFirstRequest)
.withWaitStrategy(Wait.forHttp("/hello-world", 8080))
.start()
).rejects.toThrowError("Container exited with code 1");
});

it("should log only $tail logs if container exited before waiting pass", async () => {
const name = `container-name-${new RandomUuid().nextUuid()}`;
const tail = 50;
const data = [...Array(tail + 5).keys()];
const echoCmd = data.map((i) => `echo ${i}`).join(" && ");
const lastLogs = data.slice(tail * -1).join("\n");
const container = new GenericContainer("cristianrgreco/testcontainer:1.1.14")
it("should keep waiting after the container exits when abortOnContainerExit is false", async () => {
await expect(() =>
new GenericContainer("cristianrgreco/testcontainer:1.1.14")
.withExposedPorts(8080)
.withStartupTimeout(20000)
.withEntrypoint(["/bin/sh", "-c", `${echoCmd} && sleep infinity`])
.withWaitStrategy(Wait.forHttp("/hello-world", 8080, { abortOnContainerExit: true }))
.withName(name);

await expect(stopStartingContainer(container, name)).rejects.toThrowError(
new Error(`Container exited during HTTP healthCheck, last ${tail} logs: ${lastLogs}`)
);
});
.withStartupTimeout(3000)
.withEntrypoint(exitAfterFirstRequest)
.withWaitStrategy(Wait.forHttp("/hello-world", 8080, { abortOnContainerExit: false }))
.start()
).rejects.toThrowError("URL /hello-world not accessible after 3000ms");
});

it("should set method", async () => {
Expand Down
58 changes: 10 additions & 48 deletions packages/testcontainers/src/wait-strategies/http-wait-strategy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { text } from "node:stream/consumers";
import type Dockerode from "dockerode";
import { Agent, request } from "undici";
import { IntervalRetry, log } from "../common";
import { log } from "../common";
import { getContainerRuntimeClient } from "../container-runtime";
import type { BoundPorts } from "../utils/bound-ports";
import { undiciResponseToFetchResponse } from "./utils/undici-response-parser";
Expand All @@ -22,9 +21,12 @@ export class HttpWaitStrategy extends AbstractWaitStrategy {
constructor(
private readonly path: string,
private readonly port: number,
private readonly options: HttpWaitStrategyOptions
options: HttpWaitStrategyOptions
) {
super();
if (options.abortOnContainerExit !== undefined) {
this.withAbortOnContainerExit(options.abortOnContainerExit);
}
}

public forStatusCode(statusCode: number): this {
Expand Down Expand Up @@ -76,60 +78,38 @@ export class HttpWaitStrategy extends AbstractWaitStrategy {
public async waitUntilReady(container: Dockerode.Container, boundPorts: BoundPorts): Promise<void> {
log.debug(`Waiting for HTTP...`, { containerId: container.id });

const exitStatus = "exited";
let containerExited = false;
const client = await getContainerRuntimeClient();
const { abortOnContainerExit } = this.options;
// Scoped per invocation: one strategy instance can drive concurrent waits, so a shared
// agent would let one finished wait destroy a dispatcher another is still using.
const agent = this.createInsecureAgent();

try {
await new IntervalRetry<Response | undefined, Error>(this.readTimeoutMs).retryUntil(
await this.retryUntil(
container,
this.readTimeoutMs,
async () => {
try {
const url = `${this.protocol}://${client.info.containerRuntime.host}:${boundPorts.getBinding(this.port)}${
this.path
}`;

if (abortOnContainerExit) {
const containerStatus = (await client.container.inspect(container)).State.Status;

if (containerStatus === exitStatus) {
containerExited = true;
return;
}
}

return await this.sendRequest(url, agent);
} catch {
return undefined;
}
},
async (response) => {
if (abortOnContainerExit && containerExited) {
return true;
}

return this.matchesPredicates(response);
},
(response) => this.matchesPredicates(response),
() => {
const message = `URL ${this.path} not accessible after ${this.startupTimeoutMs}ms`;
log.error(message, { containerId: container.id });
throw new Error(message);
},
this.startupTimeoutMs
}
);
} finally {
// Force-close: status-only predicates never read the body, so a graceful close() could
// hang waiting on unreleased connections. Nothing left to drain once the wait is done.
await agent?.destroy();
}

if (abortOnContainerExit && containerExited) {
return this.handleContainerExit(container);
}

log.debug(`HTTP wait strategy complete`, { containerId: container.id });
}

Expand Down Expand Up @@ -160,24 +140,6 @@ export class HttpWaitStrategy extends AbstractWaitStrategy {
return true;
}

private async handleContainerExit(container: Dockerode.Container) {
const tail = 50;
const client = await getContainerRuntimeClient();
let message: string;

try {
const lastLogs = await text(await client.container.logs(container, { tail }));

message = `Container exited during HTTP healthCheck, last ${tail} logs: ${lastLogs.trim()}`;
} catch (_err) {
message = "Container exited during HTTP healthCheck, failed to get last logs";
}

log.error(message, { containerId: container.id });

throw new Error(message);
}

private createInsecureAgent(): Agent | undefined {
if (!this._allowInsecure) {
return undefined;
Expand Down
Loading
Loading