mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-19 15:11:36 +00:00
fix: resolve relative bind mounts against code dir for git-based compose deploys
Compose deployments using a git provider (GitHub/GitLab/Gitea/Bitbucket/ custom Git) clone the repo verbatim, so composePath can point into a subdirectory (e.g. deploy/docker-compose.yml). Docker Compose resolves relative bind mounts (../files) against the compose file's own directory, not against the code/ dir where file mounts are actually written, so the mount silently binds an empty directory instead of the configured content. Pin --project-directory to code/ when building the docker compose command so relative mounts always resolve the same way raw deployments already do. Fixes #5181
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
import { createCommand } from "@dokploy/server/utils/builders/compose";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
const base = {
|
||||||
|
composeType: "docker-compose" as const,
|
||||||
|
appName: "compose-app",
|
||||||
|
sourceType: "github" as const,
|
||||||
|
command: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("compose createCommand --project-directory", () => {
|
||||||
|
it("pins --project-directory to the code dir when composePath is nested", () => {
|
||||||
|
const cmd = createCommand(
|
||||||
|
{ ...base, composePath: "./deploy/docker-compose.yml" } as any,
|
||||||
|
"/etc/dokploy/compose/compose-app/code",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(cmd).toContain(
|
||||||
|
"--project-directory /etc/dokploy/compose/compose-app/code",
|
||||||
|
);
|
||||||
|
expect(cmd).toContain("-f ./deploy/docker-compose.yml");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits --project-directory when no projectPath is passed", () => {
|
||||||
|
const cmd = createCommand({
|
||||||
|
...base,
|
||||||
|
composePath: "./deploy/docker-compose.yml",
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(cmd).not.toContain("--project-directory");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not add --project-directory to stack deploy (unsupported flag)", () => {
|
||||||
|
const cmd = createCommand(
|
||||||
|
{
|
||||||
|
...base,
|
||||||
|
composeType: "stack",
|
||||||
|
composePath: "./deploy/docker-compose.yml",
|
||||||
|
} as any,
|
||||||
|
"/etc/dokploy/compose/compose-app/code",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(cmd).not.toContain("--project-directory");
|
||||||
|
expect(cmd.startsWith("stack deploy")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps raw sourceType resolving from the code dir (root docker-compose.yml)", () => {
|
||||||
|
const cmd = createCommand(
|
||||||
|
{ ...base, sourceType: "raw", composePath: "docker-compose.yml" } as any,
|
||||||
|
"/etc/dokploy/compose/compose-app/code",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(cmd).toContain(
|
||||||
|
"--project-directory /etc/dokploy/compose/compose-app/code",
|
||||||
|
);
|
||||||
|
expect(cmd).toContain("-f docker-compose.yml");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { join } from "node:path";
|
||||||
import {
|
import {
|
||||||
addDomainToCompose,
|
addDomainToCompose,
|
||||||
clearOldDeployments,
|
clearOldDeployments,
|
||||||
@@ -32,6 +33,7 @@ import {
|
|||||||
updateCompose,
|
updateCompose,
|
||||||
updateDeploymentStatus,
|
updateDeploymentStatus,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
|
import { paths } from "@dokploy/server/constants";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { canEditDeployGitSource } from "@dokploy/server/services/git-provider";
|
import { canEditDeployGitSource } from "@dokploy/server/services/git-provider";
|
||||||
import {
|
import {
|
||||||
@@ -547,7 +549,9 @@ export const composeRouter = createTRPCRouter({
|
|||||||
service: ["create"],
|
service: ["create"],
|
||||||
});
|
});
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
const command = createCommand(compose);
|
const { COMPOSE_PATH } = paths(!!compose.serverId);
|
||||||
|
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
|
||||||
|
const command = createCommand(compose, projectPath);
|
||||||
return `docker ${command}`;
|
return `docker ${command}`;
|
||||||
}),
|
}),
|
||||||
refreshToken: protectedProcedure
|
refreshToken: protectedProcedure
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ export const getBuildComposeCommand = async (rawCompose: ComposeNested) => {
|
|||||||
const compose = await withResolvedVaultRefs(rawCompose);
|
const compose = await withResolvedVaultRefs(rawCompose);
|
||||||
const { COMPOSE_PATH } = paths(!!compose.serverId);
|
const { COMPOSE_PATH } = paths(!!compose.serverId);
|
||||||
const { sourceType, appName, mounts, composeType, domains } = compose;
|
const { sourceType, appName, mounts, composeType, domains } = compose;
|
||||||
const command = createCommand(compose);
|
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
|
||||||
|
const command = createCommand(compose, projectPath);
|
||||||
const envCommand = compose.createEnvFile
|
const envCommand = compose.createEnvFile
|
||||||
? getCreateEnvFileCommand(compose)
|
? getCreateEnvFileCommand(compose)
|
||||||
: "";
|
: "";
|
||||||
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
|
|
||||||
const exportEnvCommand = getExportEnvCommand(compose);
|
const exportEnvCommand = getExportEnvCommand(compose);
|
||||||
|
|
||||||
const newCompose = await writeDomainsToCompose(compose, domains);
|
const newCompose = await writeDomainsToCompose(compose, domains);
|
||||||
@@ -119,7 +119,7 @@ const sanitizeCommand = (command: string) => {
|
|||||||
return restCommand.join(" ");
|
return restCommand.join(" ");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createCommand = (compose: ComposeNested) => {
|
export const createCommand = (compose: ComposeNested, projectPath?: string) => {
|
||||||
const { composeType, appName, sourceType } = compose;
|
const { composeType, appName, sourceType } = compose;
|
||||||
if (compose.command) {
|
if (compose.command) {
|
||||||
return `${sanitizeCommand(compose.command)}`;
|
return `${sanitizeCommand(compose.command)}`;
|
||||||
@@ -130,7 +130,10 @@ export const createCommand = (compose: ComposeNested) => {
|
|||||||
let command = "";
|
let command = "";
|
||||||
|
|
||||||
if (composeType === "docker-compose") {
|
if (composeType === "docker-compose") {
|
||||||
command = `compose -p ${quote([appName])} -f ${quote([path])} up -d --build --remove-orphans`;
|
const projectDirectoryFlag = projectPath
|
||||||
|
? `--project-directory ${quote([projectPath])} `
|
||||||
|
: "";
|
||||||
|
command = `compose -p ${quote([appName])} ${projectDirectoryFlag}-f ${quote([path])} up -d --build --remove-orphans`;
|
||||||
} else if (composeType === "stack") {
|
} else if (composeType === "stack") {
|
||||||
command = `stack deploy -c ${quote([path])} ${quote([appName])} --prune --with-registry-auth`;
|
command = `stack deploy -c ${quote([path])} ${quote([appName])} --prune --with-registry-auth`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user