Skip to content
Closed
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
20 changes: 20 additions & 0 deletions docs/features/containers.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,26 @@ const container = await new GenericContainer("alpine").start();
await container.stop({ remove: false });
```

Alternatively, you can disable automatic removal while configuring the container:

```javascript
const container = await new GenericContainer("alpine")
.withAutoRemove(false)
.start();

await container.stop()
```

The value specified to `.withAutoRemove()` can be overridden by `.stop()`:

```javascript
const container = await new GenericContainer("alpine")
.withAutoRemove(false)
.start();

await container.stop({ remove: true }) // The container is stopped *AND* removed
```

Volumes created by the container are removed when stopped. This is configurable:

```javascript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ export class DockerComposeEnvironment {
inspectResult,
boundPorts,
containerName,
waitStrategy
waitStrategy,
true
);
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
checkContainerIsHealthy,
getDockerEventStream,
getRunningContainerNames,
getStoppedContainerNames,
waitForDockerEvent,
} from "../utils/test-helper";
import { getContainerRuntimeClient } from "../container-runtime";
Expand Down Expand Up @@ -519,6 +520,30 @@ describe("GenericContainer", () => {
expect(await getRunningContainerNames()).not.toContain(container.getName());
});

it("should stop but not remove the container", async () => {
const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14")
.withName(`container-${new RandomUuid().nextUuid()}`)
.withAutoRemove(false)
.start();

await container.stop();

expect(await getRunningContainerNames()).not.toContain(container.getName().replace("/", ""));
expect(await getStoppedContainerNames()).toContain(container.getName().replace("/", ""));
});

it("should stop and override .withAutoRemove", async () => {
const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14")
.withName(`container-${new RandomUuid().nextUuid()}`)
.withAutoRemove(false)
.start();

await container.stop({ remove: true });

expect(await getRunningContainerNames()).not.toContain(container.getName().replace("/", ""));
expect(await getStoppedContainerNames()).not.toContain(container.getName().replace("/", ""));
});

it("should build a target stage", async () => {
const context = path.resolve(fixtures, "docker-multi-stage");
const firstContainer = await GenericContainer.fromDockerfile(context).withTarget("first").build();
Expand Down
12 changes: 10 additions & 2 deletions packages/testcontainers/src/generic-container/generic-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class GenericContainer implements TestContainer {
protected environment: Record<string, string> = {};
protected exposedPorts: PortWithOptionalBinding[] = [];
protected reuse = false;
protected autoRemove = true;
protected networkMode?: string;
protected networkAliases: string[] = [];
protected pullPolicy: ImagePullPolicy = PullPolicy.defaultPolicy();
Expand Down Expand Up @@ -157,7 +158,8 @@ export class GenericContainer implements TestContainer {
inspectResult,
boundPorts,
inspectResult.Name,
this.waitStrategy
this.waitStrategy,
this.autoRemove
);
}

Expand Down Expand Up @@ -221,7 +223,8 @@ export class GenericContainer implements TestContainer {
inspectResult,
boundPorts,
inspectResult.Name,
this.waitStrategy
this.waitStrategy,
this.autoRemove
);

if (this.containerStarted) {
Expand Down Expand Up @@ -430,6 +433,11 @@ export class GenericContainer implements TestContainer {
return this;
}

public withAutoRemove(autoRemove: boolean): this {
this.autoRemove = autoRemove;
return this;
}

public withPullPolicy(pullPolicy: ImagePullPolicy): this {
this.pullPolicy = pullPolicy;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export class StartedGenericContainer implements StartedTestContainer {
private inspectResult: ContainerInspectInfo,
private boundPorts: BoundPorts,
private readonly name: string,
private readonly waitStrategy: WaitStrategy
private readonly waitStrategy: WaitStrategy,
private readonly autoRemove: boolean
) {}

protected containerIsStopping?(): Promise<void>;
Expand Down Expand Up @@ -71,7 +72,7 @@ export class StartedGenericContainer implements StartedTestContainer {
await this.containerIsStopping();
}

const resolvedOptions: StopOptions = { remove: true, timeout: 0, removeVolumes: true, ...options };
const resolvedOptions: StopOptions = { remove: this.autoRemove, timeout: 0, removeVolumes: true, ...options };
await client.container.stop(this.container, { timeout: resolvedOptions.timeout });
if (resolvedOptions.remove) {
await client.container.remove(this.container, { removeVolumes: resolvedOptions.removeVolumes });
Expand Down
1 change: 1 addition & 0 deletions packages/testcontainers/src/test-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface TestContainer {
withUser(user: string): this;
withPullPolicy(pullPolicy: ImagePullPolicy): this;
withReuse(): this;
withAutoRemove(autoRemove: boolean): this;
withCopyFilesToContainer(filesToCopy: FileToCopy[]): this;
withCopyDirectoriesToContainer(directoriesToCopy: DirectoryToCopy[]): this;
withCopyContentToContainer(contentsToCopy: ContentToCopy[]): this;
Expand Down
10 changes: 10 additions & 0 deletions packages/testcontainers/src/utils/test-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ export const getRunningContainerNames = async (): Promise<string[]> => {
.map((containerName) => containerName.replace("/", ""));
};

export const getStoppedContainerNames = async (): Promise<string[]> => {
const dockerode = (await getContainerRuntimeClient()).container.dockerode;
const containers = await dockerode.listContainers({ all: true });
return containers
.filter((container) => container.State === "exited")
.map((container) => container.Names)
.reduce((result, containerNames) => [...result, ...containerNames], [])
.map((containerName) => containerName.replace("/", ""));
};

export const getContainerIds = async (): Promise<string[]> => {
const dockerode = (await getContainerRuntimeClient()).container.dockerode;
const containers = await dockerode.listContainers({ all: true });
Expand Down