From ec8e6457581117cad5d0ad28ce1f7d3abbadd424 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 1 Sep 2026 00:50:05 -0600 Subject: [PATCH] fix: only pin --project-directory when compose has mounts --project-directory was added unconditionally in 87b914996 to fix relative bind mount resolution for git-based compose deploys with a nested composePath (#5181). It also moves where build.context and the generated .env resolve, breaking any compose file in a subdirectory that has context: . alongside its Dockerfile (#5230), or interpolates env vars (#5242). Only pin --project-directory when the compose actually has mounts configured; otherwise build.context and .env resolve against the compose file's own directory like plain docker compose. Also pass --env-file explicitly pointing at the generated .env next to the compose file, so it's found even when --project-directory is pinned (credit: tonnenpinguin, PR #5235). Fixes #5230 Fixes #5242 --- .../compose/compose-project-directory.test.ts | 65 +++++++++++++++++++ apps/dokploy/server/api/routers/compose.ts | 5 +- packages/server/src/utils/builders/compose.ts | 10 ++- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/__test__/compose/compose-project-directory.test.ts b/apps/dokploy/__test__/compose/compose-project-directory.test.ts index a117de5b2..6c409cc77 100644 --- a/apps/dokploy/__test__/compose/compose-project-directory.test.ts +++ b/apps/dokploy/__test__/compose/compose-project-directory.test.ts @@ -55,4 +55,69 @@ describe("compose createCommand --project-directory", () => { ); expect(cmd).toContain("-f docker-compose.yml"); }); + + it("resolves build.context against the compose file's own directory when there are no mounts", () => { + const cmd = createCommand({ + ...base, + composePath: "./backend/docker-compose.yml", + } as any); + + expect(cmd).not.toContain("--project-directory"); + expect(cmd).toContain("-f ./backend/docker-compose.yml"); + }); +}); + +describe("compose createCommand --env-file", () => { + it("points --env-file at the generated .env next to a nested compose file", () => { + const cmd = createCommand( + { + ...base, + composePath: "./deploy/docker-compose.yml", + createEnvFile: true, + } as any, + "/etc/dokploy/compose/compose-app/code", + ); + + expect(cmd).toContain("--env-file deploy/.env"); + expect(cmd).toContain( + "--project-directory /etc/dokploy/compose/compose-app/code", + ); + }); + + it("omits --env-file when createEnvFile is disabled", () => { + const cmd = createCommand( + { ...base, composePath: "./deploy/docker-compose.yml" } as any, + "/etc/dokploy/compose/compose-app/code", + ); + + expect(cmd).not.toContain("--env-file"); + }); + + it("uses the code-root .env for raw sourceType", () => { + const cmd = createCommand( + { + ...base, + sourceType: "raw", + composePath: "docker-compose.yml", + createEnvFile: true, + } as any, + "/etc/dokploy/compose/compose-app/code", + ); + + expect(cmd).toContain("--env-file .env"); + }); + + it("does not add --env-file to stack deploy (unsupported flag)", () => { + const cmd = createCommand( + { + ...base, + composeType: "stack", + composePath: "./deploy/docker-compose.yml", + createEnvFile: true, + } as any, + "/etc/dokploy/compose/compose-app/code", + ); + + expect(cmd).not.toContain("--env-file"); + }); }); diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index cfd272f43..8f5904501 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -551,7 +551,10 @@ export const composeRouter = createTRPCRouter({ const compose = await findComposeById(input.composeId); const { COMPOSE_PATH } = paths(!!compose.serverId); const projectPath = join(COMPOSE_PATH, compose.appName, "code"); - const command = createCommand(compose, projectPath); + const command = createCommand( + compose, + compose.mounts.length > 0 ? projectPath : undefined, + ); return `docker ${command}`; }), refreshToken: protectedProcedure diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index 6d58c7398..01902e4c4 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -22,7 +22,10 @@ export const getBuildComposeCommand = async (rawCompose: ComposeNested) => { const { COMPOSE_PATH } = paths(!!compose.serverId); const { sourceType, appName, mounts, composeType, domains } = compose; const projectPath = join(COMPOSE_PATH, compose.appName, "code"); - const command = createCommand(compose, projectPath); + const command = createCommand( + compose, + mounts.length > 0 ? projectPath : undefined, + ); const envCommand = compose.createEnvFile ? getCreateEnvFileCommand(compose) : ""; @@ -133,7 +136,10 @@ export const createCommand = (compose: ComposeNested, projectPath?: string) => { const projectDirectoryFlag = projectPath ? `--project-directory ${quote([projectPath])} ` : ""; - command = `compose -p ${quote([appName])} ${projectDirectoryFlag}-f ${quote([path])} up -d --build --remove-orphans`; + const envFileFlag = compose.createEnvFile + ? `--env-file ${quote([join(dirname(compose.composePath || "docker-compose.yml"), ".env")])} ` + : ""; + command = `compose -p ${quote([appName])} ${projectDirectoryFlag}${envFileFlag}-f ${quote([path])} up -d --build --remove-orphans`; } else if (composeType === "stack") { command = `stack deploy -c ${quote([path])} ${quote([appName])} --prune --with-registry-auth`; }