From ea486ef830fefa40939351ff6ebc15b94208acd7 Mon Sep 17 00:00:00 2001 From: kenny-ish <114967656+kenny-ish@users.noreply.github.com> Date: Mon, 19 Oct 2026 09:55:19 +0000 Subject: [PATCH 1/2] Keep a custom project name when withNoRecreate is used withNoRecreate() replaced the project name with the fixed value "testcontainers-node" as a side effect, so a name set earlier with withProjectName() was silently discarded and the result depended on the order of the two calls. Resolve the project name in up() instead: an explicit project name always wins, otherwise the generated name is used, or the fixed name when recreation is disabled. Fixes #1524 --- docs/features/compose.md | 2 ++ .../docker-compose-environment.test.ts | 10 +++++++ .../docker-compose-environment.ts | 26 ++++++++++++------- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/docs/features/compose.md b/docs/features/compose.md index 8d93bf63e..2c346674f 100644 --- a/docs/features/compose.md +++ b/docs/features/compose.md @@ -133,6 +133,8 @@ const environment = await new DockerComposeEnvironment(composeFilePath, composeF .up(); ``` +Unless a project name is set with `withProjectName`, the environment uses the fixed project name `testcontainers-node` so that the containers are found again on the next run. + ### With environment Bind environment variables to the docker-compose file: diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts index 79c117ecd..59ee8abd2 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts @@ -311,4 +311,14 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => { expect(await getRunningContainerNames()).toContain(`${customProjectName}-container-1`); }); + + it("should keep a custom project name when no recreate option is set", async () => { + const customProjectName = `custom-${new RandomUuid().nextUuid()}`; + await using _ = await new DockerComposeEnvironment(fixtures, "docker-compose.yml") + .withProjectName(customProjectName) + .withNoRecreate() + .up(); + + expect(await getRunningContainerNames()).toContain(`${customProjectName}-container-1`); + }); }); diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts index 8a6c29b74..244313352 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts @@ -20,8 +20,9 @@ import { StartedDockerComposeEnvironment } from "./started-docker-compose-enviro export class DockerComposeEnvironment { private readonly composeFilePath: string; private readonly composeFiles: string | string[]; + private readonly defaultProjectName: string; - private projectName: string; + private projectName?: string; private build = false; private autoCleanup = true; private recreate = true; @@ -37,7 +38,7 @@ export class DockerComposeEnvironment { constructor(composeFilePath: string, composeFiles: string | string[], uuid: Uuid = new RandomUuid()) { this.composeFilePath = composeFilePath; this.composeFiles = composeFiles; - this.projectName = `testcontainers-${uuid.nextUuid()}`; + this.defaultProjectName = `testcontainers-${uuid.nextUuid()}`; } public withBuild(): this { @@ -67,7 +68,6 @@ export class DockerComposeEnvironment { public withNoRecreate(): this { this.recreate = false; - this.projectName = "testcontainers-node"; return this; } @@ -104,11 +104,12 @@ export class DockerComposeEnvironment { } public async up(services?: Array): Promise { - log.info(`Starting DockerCompose environment "${this.projectName}"...`); + const projectName = this.getProjectName(); + log.info(`Starting DockerCompose environment "${projectName}"...`); const client = await getContainerRuntimeClient(); if (this.autoCleanup) { const reaper = await getReaper(client); - reaper.addComposeProject(this.projectName); + reaper.addComposeProject(projectName); } const { @@ -121,7 +122,7 @@ export class DockerComposeEnvironment { ...remainingClientOptions, filePath: this.composeFilePath, files: this.composeFiles, - projectName: this.projectName, + projectName, }; const shouldPull = this.pullPolicy.shouldPull(); @@ -152,6 +153,13 @@ export class DockerComposeEnvironment { }); } + private getProjectName(): string { + if (this.projectName !== undefined) { + return this.projectName; + } + return this.recreate ? this.defaultProjectName : "testcontainers-node"; + } + private createCommandOptions(clientCommandOptions: string[], shouldPull: boolean | "never"): string[] { const commandOptions = [...clientCommandOptions]; @@ -185,7 +193,7 @@ export class DockerComposeEnvironment { options: ComposeOptions ): Promise<{ [containerName: string]: StartedGenericContainer }> { const startedContainers = (await client.container.list()).filter( - (container) => container.Labels["com.docker.compose.project"] === this.projectName + (container) => container.Labels["com.docker.compose.project"] === options.projectName ); const startedContainerNames = startedContainers.map((startedContainer: ContainerInfo) => startedContainer.Names.join(", ") @@ -194,7 +202,7 @@ export class DockerComposeEnvironment { const startedContainerNameSet = new Set( startedContainers.map((startedContainer) => - parseComposeContainerName(this.projectName, startedContainer.Names[0]) + parseComposeContainerName(options.projectName, startedContainer.Names[0]) ) ); this.warnForUnusedWaitStrategies(startedContainerNameSet); @@ -222,7 +230,7 @@ export class DockerComposeEnvironment { startedContainer: ContainerInfo ): Promise { const container = client.container.getById(startedContainer.Id); - const containerName = parseComposeContainerName(this.projectName, startedContainer.Names[0]); + const containerName = parseComposeContainerName(options.projectName, startedContainer.Names[0]); const inspectResult = await client.container.inspect(container); const mappedInspectResult = mapInspectResult(inspectResult); From a4a3a5a512208d57265b505b0d2d0e81fc4ad12f Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sun, 25 Oct 2026 09:32:13 +0000 Subject: [PATCH 2/2] Remove withNoRecreate project name note from compose docs --- docs/features/compose.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/features/compose.md b/docs/features/compose.md index 2c346674f..8d93bf63e 100644 --- a/docs/features/compose.md +++ b/docs/features/compose.md @@ -133,8 +133,6 @@ const environment = await new DockerComposeEnvironment(composeFilePath, composeF .up(); ``` -Unless a project name is set with `withProjectName`, the environment uses the fixed project name `testcontainers-node` so that the containers are found again on the next run. - ### With environment Bind environment variables to the docker-compose file: