From 94786c738bb255378a581e591c798aa780341239 Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Fri, 1 Nov 2024 15:27:00 +0100
Subject: [PATCH 01/13] feat: added env support for dokploy
---
.../services/compose/[composeId].tsx | 10 +---
packages/server/src/utils/builders/compose.ts | 48 ++++++++++++++++++-
2 files changed, 49 insertions(+), 9 deletions(-)
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
index 60ddfeab..bcac11ed 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
@@ -185,18 +185,12 @@ const Service = (
General
- {data?.composeType === "docker-compose" && (
- Environment
- )}
+ Environment
{!data?.serverId && (
Monitoring
)}
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 7d3ce0ec..c2924897 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -2,12 +2,14 @@ import {
createWriteStream,
existsSync,
mkdirSync,
+ readFileSync,
writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type { InferResultType } from "@dokploy/server/types/with";
import boxen from "boxen";
+import dotenv from "dotenv";
import {
writeDomainsToCompose,
writeDomainsToComposeRemote,
@@ -28,6 +30,7 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
const command = createCommand(compose);
await writeDomainsToCompose(compose, domains);
createEnvFile(compose);
+ processComposeFile(compose);
const logContent = `
App Name: ${appName}
@@ -84,6 +87,7 @@ export const getBuildComposeCommand = async (
const command = createCommand(compose);
const envCommand = getCreateEnvFileCommand(compose);
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
+ const processComposeFileCommand = getProcessComposeFileCommand(compose);
const newCompose = await writeDomainsToComposeRemote(
compose,
@@ -119,6 +123,8 @@ Compose Type: ${composeType} ✅`;
cd "${projectPath}";
+ ${processComposeFileCommand}
+
docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }
echo "Docker Compose Deployed: ✅" >> "${logPath}"
@@ -145,7 +151,7 @@ export const createCommand = (compose: ComposeNested) => {
const { composeType, appName, sourceType } = compose;
const path =
- sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
+ sourceType === "raw" ? "docker-compose.processed.yml" : compose.composePath;
let command = "";
if (composeType === "docker-compose") {
@@ -188,6 +194,46 @@ const createEnvFile = (compose: ComposeNested) => {
writeFileSync(envFilePath, envFileContent);
};
+export const processComposeFile = (compose: ComposeNested) => {
+ const { COMPOSE_PATH } = paths();
+ const { env, appName, sourceType, composeType } = compose;
+
+ if (composeType === "stack") {
+ const inputPath =
+ sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
+ const composeInputFilePath =
+ join(COMPOSE_PATH, appName, "code", inputPath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
+
+ const outputPath = "docker-compose.processed.yml";
+ const composeOutputFilePath =
+ join(COMPOSE_PATH, appName, "code", outputPath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.processed.yml");
+
+ const envContent = prepareEnvironmentVariables(env || "").join("\n");
+ const envVariables = dotenv.parse(envContent);
+
+ let templateContent = readFileSync(composeInputFilePath, "utf8");
+
+ templateContent = templateContent.replace(
+ /\$\{([^}]+)\}/g,
+ (_, varName) => {
+ return envVariables[varName] || "";
+ },
+ );
+
+ writeFileSync(composeOutputFilePath, templateContent);
+ }
+};
+
+export const getProcessComposeFileCommand = (compose: ComposeNested) => {
+ const { composeType } = compose;
+ if (composeType === "stack") {
+ return "set -a; source .env; set +a; envsubst < docker-compose.yml > docker-compose.processed.yml";
+ }
+ return "cp docker-compose.yml docker-compose.processed.yml";
+};
+
export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const { COMPOSE_PATH } = paths(true);
const { env, composePath, appName } = compose;
From cb02deb837065438cc6de1f431d9297485b1279a Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Fri, 1 Nov 2024 23:15:12 +0100
Subject: [PATCH 02/13] fix(builder): fixed docker-compose issue
---
packages/server/src/utils/builders/compose.ts | 30 +++++++++----------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index c2924897..fd9824eb 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -198,32 +198,32 @@ export const processComposeFile = (compose: ComposeNested) => {
const { COMPOSE_PATH } = paths();
const { env, appName, sourceType, composeType } = compose;
+ const inputPath =
+ sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
+ const composeInputFilePath =
+ join(COMPOSE_PATH, appName, "code", inputPath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
+
+ const outputPath = "docker-compose.processed.yml";
+ const composeOutputFilePath =
+ join(COMPOSE_PATH, appName, "code", outputPath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.processed.yml");
+
+ let templateContent = readFileSync(composeInputFilePath, "utf8");
+
if (composeType === "stack") {
- const inputPath =
- sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
- const composeInputFilePath =
- join(COMPOSE_PATH, appName, "code", inputPath) ||
- join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
-
- const outputPath = "docker-compose.processed.yml";
- const composeOutputFilePath =
- join(COMPOSE_PATH, appName, "code", outputPath) ||
- join(COMPOSE_PATH, appName, "code", "docker-compose.processed.yml");
-
const envContent = prepareEnvironmentVariables(env || "").join("\n");
const envVariables = dotenv.parse(envContent);
- let templateContent = readFileSync(composeInputFilePath, "utf8");
-
templateContent = templateContent.replace(
/\$\{([^}]+)\}/g,
(_, varName) => {
return envVariables[varName] || "";
},
);
-
- writeFileSync(composeOutputFilePath, templateContent);
}
+
+ writeFileSync(composeOutputFilePath, templateContent);
};
export const getProcessComposeFileCommand = (compose: ComposeNested) => {
From dc1e12d6ed19ae1ec9d2e7b69b97d6a35464bd8b Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Fri, 1 Nov 2024 23:35:42 +0100
Subject: [PATCH 03/13] feat(compose): added stop functionality for stack
---
.../components/dashboard/compose/general/actions.tsx | 2 +-
packages/server/src/services/compose.ts | 11 +++++++++++
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx
index 365e37f5..37f8ffa8 100644
--- a/apps/dokploy/components/dashboard/compose/general/actions.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx
@@ -71,7 +71,7 @@ export const ComposeActions = ({ composeId }: Props) => {
Autodeploy {data?.autoDeploy && }
- {data?.composeType === "docker-compose" && (
+ {["running", "done"].includes(data?.composeStatus || "") && (
)}
diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts
index 61d7e5fc..7382fa99 100644
--- a/packages/server/src/services/compose.ts
+++ b/packages/server/src/services/compose.ts
@@ -476,6 +476,17 @@ export const stopCompose = async (composeId: string) => {
}
}
+ if (compose.composeType === "stack") {
+ if (compose.serverId) {
+ await execAsyncRemote(
+ compose.serverId,
+ `docker stack rm ${compose.appName}`,
+ );
+ } else {
+ await execAsync(`docker stack rm ${compose.appName}`);
+ }
+ }
+
await updateCompose(composeId, {
composeStatus: "idle",
});
From 06081627e85fec85198f9bc7c17ae1b0239251b0 Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Sat, 2 Nov 2024 00:35:38 +0100
Subject: [PATCH 04/13] refactor: used docker stack config
---
packages/server/src/utils/builders/compose.ts | 52 ++++++++-----------
1 file changed, 21 insertions(+), 31 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index fd9824eb..8d521d61 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -15,7 +15,7 @@ import {
writeDomainsToComposeRemote,
} from "../docker/domain";
import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils";
-import { execAsyncRemote } from "../process/execAsync";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
@@ -30,7 +30,7 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
const command = createCommand(compose);
await writeDomainsToCompose(compose, domains);
createEnvFile(compose);
- processComposeFile(compose);
+ await processComposeFile(compose);
const logContent = `
App Name: ${appName}
@@ -194,44 +194,34 @@ const createEnvFile = (compose: ComposeNested) => {
writeFileSync(envFilePath, envFileContent);
};
-export const processComposeFile = (compose: ComposeNested) => {
+export const processComposeFile = async (compose: ComposeNested) => {
const { COMPOSE_PATH } = paths();
- const { env, appName, sourceType, composeType } = compose;
+ let command = getProcessComposeFileCommand(compose);
- const inputPath =
- sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
- const composeInputFilePath =
- join(COMPOSE_PATH, appName, "code", inputPath) ||
- join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
-
- const outputPath = "docker-compose.processed.yml";
- const composeOutputFilePath =
- join(COMPOSE_PATH, appName, "code", outputPath) ||
- join(COMPOSE_PATH, appName, "code", "docker-compose.processed.yml");
-
- let templateContent = readFileSync(composeInputFilePath, "utf8");
-
- if (composeType === "stack") {
- const envContent = prepareEnvironmentVariables(env || "").join("\n");
- const envVariables = dotenv.parse(envContent);
-
- templateContent = templateContent.replace(
- /\$\{([^}]+)\}/g,
- (_, varName) => {
- return envVariables[varName] || "";
- },
- );
+ if (compose.serverId) {
+ command = `cd ${join(COMPOSE_PATH, compose.appName, "code")} && ${command}`;
+ await execAsyncRemote(compose.serverId, command);
+ } else {
+ await execAsync(command, {
+ cwd: join(COMPOSE_PATH, compose.appName, "code"),
+ });
}
-
- writeFileSync(composeOutputFilePath, templateContent);
};
export const getProcessComposeFileCommand = (compose: ComposeNested) => {
const { composeType } = compose;
+
+ let command = "";
+
if (composeType === "stack") {
- return "set -a; source .env; set +a; envsubst < docker-compose.yml > docker-compose.processed.yml";
+ command = `export $(grep -v '^#' .env | xargs) && docker stack config -c docker-compose.yml > docker-compose.processed.yml`;
}
- return "cp docker-compose.yml docker-compose.processed.yml";
+
+ if (composeType === "docker-compose") {
+ command = "cp docker-compose.yml docker-compose.processed.yml";
+ }
+
+ return command;
};
export const getCreateEnvFileCommand = (compose: ComposeNested) => {
From 9c355bcfb7426fc31b0f0b57d46989f96256c0c2 Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Sat, 2 Nov 2024 12:42:53 +0100
Subject: [PATCH 05/13] refactor(builder): removed unused and redundant code
---
packages/server/src/utils/builders/compose.ts | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 8d521d61..dd48e0c2 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -2,14 +2,12 @@ import {
createWriteStream,
existsSync,
mkdirSync,
- readFileSync,
writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type { InferResultType } from "@dokploy/server/types/with";
import boxen from "boxen";
-import dotenv from "dotenv";
import {
writeDomainsToCompose,
writeDomainsToComposeRemote,
@@ -196,10 +194,9 @@ const createEnvFile = (compose: ComposeNested) => {
export const processComposeFile = async (compose: ComposeNested) => {
const { COMPOSE_PATH } = paths();
- let command = getProcessComposeFileCommand(compose);
+ const command = getProcessComposeFileCommand(compose);
if (compose.serverId) {
- command = `cd ${join(COMPOSE_PATH, compose.appName, "code")} && ${command}`;
await execAsyncRemote(compose.serverId, command);
} else {
await execAsync(command, {
From 06cbd1fce1a96692917e7c6b88b87155174e77d7 Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Sun, 3 Nov 2024 15:26:54 +0100
Subject: [PATCH 06/13] refactor(bundler): removed redundant code
---
packages/server/src/utils/builders/compose.ts | 27 ++++++++++---------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index dd48e0c2..514d7bd8 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -148,8 +148,17 @@ const sanitizeCommand = (command: string) => {
export const createCommand = (compose: ComposeNested) => {
const { composeType, appName, sourceType } = compose;
- const path =
- sourceType === "raw" ? "docker-compose.processed.yml" : compose.composePath;
+ let path = "";
+
+ if (sourceType !== "raw") {
+ path = compose.composePath;
+ } else {
+ path =
+ composeType === "stack"
+ ? "docker-compose.processed.yml"
+ : "docker-compose.yml";
+ }
+
let command = "";
if (composeType === "docker-compose") {
@@ -196,13 +205,9 @@ export const processComposeFile = async (compose: ComposeNested) => {
const { COMPOSE_PATH } = paths();
const command = getProcessComposeFileCommand(compose);
- if (compose.serverId) {
- await execAsyncRemote(compose.serverId, command);
- } else {
- await execAsync(command, {
- cwd: join(COMPOSE_PATH, compose.appName, "code"),
- });
- }
+ await execAsync(command, {
+ cwd: join(COMPOSE_PATH, compose.appName, "code"),
+ });
};
export const getProcessComposeFileCommand = (compose: ComposeNested) => {
@@ -214,10 +219,6 @@ export const getProcessComposeFileCommand = (compose: ComposeNested) => {
command = `export $(grep -v '^#' .env | xargs) && docker stack config -c docker-compose.yml > docker-compose.processed.yml`;
}
- if (composeType === "docker-compose") {
- command = "cp docker-compose.yml docker-compose.processed.yml";
- }
-
return command;
};
From f772fec407d937b92812d092ac00001cb7744a44 Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Mon, 4 Nov 2024 11:02:46 +0100
Subject: [PATCH 07/13] fix(bundler): docker-compose bug
---
packages/server/src/utils/builders/compose.ts | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 514d7bd8..8941f4e1 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -150,15 +150,17 @@ export const createCommand = (compose: ComposeNested) => {
let path = "";
- if (sourceType !== "raw") {
- path = compose.composePath;
- } else {
+ if (sourceType === "raw") {
path =
composeType === "stack"
? "docker-compose.processed.yml"
: "docker-compose.yml";
+ } else {
+ path = compose.composePath;
}
+ console.log(path);
+
let command = "";
if (composeType === "docker-compose") {
@@ -203,11 +205,12 @@ const createEnvFile = (compose: ComposeNested) => {
export const processComposeFile = async (compose: ComposeNested) => {
const { COMPOSE_PATH } = paths();
- const command = getProcessComposeFileCommand(compose);
-
- await execAsync(command, {
- cwd: join(COMPOSE_PATH, compose.appName, "code"),
- });
+ if (compose.composeType === "stack") {
+ const command = getProcessComposeFileCommand(compose);
+ await execAsync(command, {
+ cwd: join(COMPOSE_PATH, compose.appName, "code"),
+ });
+ }
};
export const getProcessComposeFileCommand = (compose: ComposeNested) => {
From dafed3096f80c282e1033c2c6e51393db1fdd16f Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Mon, 4 Nov 2024 22:47:58 +0100
Subject: [PATCH 08/13] refactor(builder): removed path log
---
packages/server/src/utils/builders/compose.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 8941f4e1..38d94ab1 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -159,8 +159,6 @@ export const createCommand = (compose: ComposeNested) => {
path = compose.composePath;
}
- console.log(path);
-
let command = "";
if (composeType === "docker-compose") {
From c9b570e4697607141e246f88b194eb1539d94bd4 Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Sat, 9 Nov 2024 13:00:15 +0100
Subject: [PATCH 09/13] fix(builder): fixed issues on non-raw compose and
external servers
---
packages/server/src/utils/builders/compose.ts | 44 ++++++++-----------
1 file changed, 19 insertions(+), 25 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 38d94ab1..233e236b 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -148,32 +148,22 @@ const sanitizeCommand = (command: string) => {
export const createCommand = (compose: ComposeNested) => {
const { composeType, appName, sourceType } = compose;
- let path = "";
-
- if (sourceType === "raw") {
- path =
- composeType === "stack"
+ const path =
+ sourceType === "raw"
+ ? composeType === "stack"
? "docker-compose.processed.yml"
- : "docker-compose.yml";
- } else {
- path = compose.composePath;
- }
+ : "docker-compose.yml"
+ : composeType === "stack"
+ ? "docker-compose.processed.yml"
+ : compose.composePath;
- let command = "";
-
- if (composeType === "docker-compose") {
- command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`;
- } else if (composeType === "stack") {
- command = `stack deploy -c ${path} ${appName} --prune`;
- }
+ const baseCommand =
+ composeType === "docker-compose"
+ ? `compose -p ${appName} -f ${path} up -d --build --remove-orphans`
+ : `stack deploy -c ${path} ${appName} --prune`;
const customCommand = sanitizeCommand(compose.command);
-
- if (customCommand) {
- command = `${command} ${customCommand}`;
- }
-
- return command;
+ return customCommand ? `${baseCommand} ${customCommand}` : baseCommand;
};
const createEnvFile = (compose: ComposeNested) => {
@@ -212,12 +202,16 @@ export const processComposeFile = async (compose: ComposeNested) => {
};
export const getProcessComposeFileCommand = (compose: ComposeNested) => {
- const { composeType } = compose;
+ const { composeType, sourceType } = compose;
+ const composePath =
+ sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
let command = "";
-
if (composeType === "stack") {
- command = `export $(grep -v '^#' .env | xargs) && docker stack config -c docker-compose.yml > docker-compose.processed.yml`;
+ command = [
+ "export $(grep -v '^#' .env | xargs)",
+ `docker stack config -c ${composePath} > docker-compose.processed.yml`,
+ ].join(" && ");
}
return command;
From 65ee0a3e2264d9325cbb800e533d5f45ce9b889d Mon Sep 17 00:00:00 2001
From: xenonwellz <59710311+xenonwellz@users.noreply.github.com>
Date: Mon, 11 Nov 2024 02:22:48 +0100
Subject: [PATCH 10/13] fix(builder): created processed file in the same
directory as main stack.yml
---
packages/server/src/utils/builders/compose.ts | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 233e236b..b09333ba 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -154,7 +154,7 @@ export const createCommand = (compose: ComposeNested) => {
? "docker-compose.processed.yml"
: "docker-compose.yml"
: composeType === "stack"
- ? "docker-compose.processed.yml"
+ ? join(dirname(compose.composePath), "docker-compose.processed.yml")
: compose.composePath;
const baseCommand =
@@ -206,11 +206,17 @@ export const getProcessComposeFileCommand = (compose: ComposeNested) => {
const composePath =
sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
+
+ const destinationPath =
+ sourceType === "raw"
+ ? "docker-compose.processed.yml"
+ : join(dirname(compose.composePath), "docker-compose.processed.yml");
+
let command = "";
if (composeType === "stack") {
command = [
"export $(grep -v '^#' .env | xargs)",
- `docker stack config -c ${composePath} > docker-compose.processed.yml`,
+ `docker stack config -c ${composePath} > ${destinationPath}`,
].join(" && ");
}
From f7a29accb1d8335b200dca58cc6d911cc7158b16 Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Thu, 30 Jan 2025 23:40:01 -0600
Subject: [PATCH 11/13] refactor: lint
---
apps/dokploy/templates/listmonk/index.ts | 54 ++++++++++++------------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/apps/dokploy/templates/listmonk/index.ts b/apps/dokploy/templates/listmonk/index.ts
index f0dad544..2a25efca 100644
--- a/apps/dokploy/templates/listmonk/index.ts
+++ b/apps/dokploy/templates/listmonk/index.ts
@@ -1,30 +1,30 @@
import {
- type DomainSchema,
- type Schema,
- type Template,
- generateRandomDomain,
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const randomDomain = generateRandomDomain(schema);
+ const randomDomain = generateRandomDomain(schema);
- const domains: DomainSchema[] = [
- {
- host: randomDomain,
- port: 9000,
- serviceName: "app",
- },
- ];
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 9000,
+ serviceName: "app",
+ },
+ ];
- const envs = [
- "# visit the page to setup your super admin user",
- "# check config.toml in Advanced / Volumes for more options",
- ];
+ const envs = [
+ "# visit the page to setup your super admin user",
+ "# check config.toml in Advanced / Volumes for more options",
+ ];
- const mounts: Template["mounts"] = [
- {
- filePath: "config.toml",
- content: `[app]
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "config.toml",
+ content: `[app]
address = "0.0.0.0:9000"
[db]
@@ -41,12 +41,12 @@ max_lifetime = "300s"
params = ""
`,
- },
- ];
+ },
+ ];
- return {
- envs,
- mounts,
- domains,
- };
+ return {
+ envs,
+ mounts,
+ domains,
+ };
}
From 009859faa9dfffc755da97c95c10f5acb3105b47 Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Fri, 31 Jan 2025 01:20:10 -0600
Subject: [PATCH 12/13] refactor: add .env docker stack
---
.../services/compose/[composeId].tsx | 6 +-
packages/server/src/utils/builders/compose.ts | 339 ++++----
packages/server/src/utils/docker/utils.ts | 815 +++++++++---------
3 files changed, 571 insertions(+), 589 deletions(-)
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
index 9f4e9086..f9ece2e7 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
@@ -211,12 +211,12 @@ const Service = (
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 2573626a..03da7858 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -1,99 +1,105 @@
import {
- createWriteStream,
- existsSync,
- mkdirSync,
- writeFileSync,
+ createWriteStream,
+ existsSync,
+ mkdirSync,
+ readFileSync,
+ writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type { InferResultType } from "@dokploy/server/types/with";
import boxen from "boxen";
import {
- writeDomainsToCompose,
- writeDomainsToComposeRemote,
+ writeDomainsToCompose,
+ writeDomainsToComposeRemote,
} from "../docker/domain";
-import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils";
+import {
+ encodeBase64,
+ getEnviromentVariablesObject,
+ prepareEnvironmentVariables,
+} from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
- "compose",
- { project: true; mounts: true; domains: true }
+ "compose",
+ { project: true; mounts: true; domains: true }
>;
export const buildCompose = async (compose: ComposeNested, logPath: string) => {
- const writeStream = createWriteStream(logPath, { flags: "a" });
- const { sourceType, appName, mounts, composeType, domains } = compose;
- try {
- const { COMPOSE_PATH } = paths();
- const command = createCommand(compose);
- await writeDomainsToCompose(compose, domains);
- createEnvFile(compose);
- await processComposeFile(compose);
+ const writeStream = createWriteStream(logPath, { flags: "a" });
+ const { sourceType, appName, mounts, composeType, domains } = compose;
+ try {
+ const { COMPOSE_PATH } = paths();
+ const command = createCommand(compose);
+ await writeDomainsToCompose(compose, domains);
+ createEnvFile(compose);
- const logContent = `
-App Name: ${appName}
-Build Compose 🐳
-Detected: ${mounts.length} mounts 📂
-Command: docker ${command}
-Source Type: docker ${sourceType} ✅
-Compose Type: ${composeType} ✅`;
- const logBox = boxen(logContent, {
- padding: {
- left: 1,
- right: 1,
- bottom: 1,
- },
- width: 80,
- borderStyle: "double",
- });
- writeStream.write(`\n${logBox}\n`);
+ const logContent = `
+ App Name: ${appName}
+ Build Compose 🐳
+ Detected: ${mounts.length} mounts 📂
+ Command: docker ${command}
+ Source Type: docker ${sourceType} ✅
+ Compose Type: ${composeType} ✅`;
+ const logBox = boxen(logContent, {
+ padding: {
+ left: 1,
+ right: 1,
+ bottom: 1,
+ },
+ width: 80,
+ borderStyle: "double",
+ });
+ writeStream.write(`\n${logBox}\n`);
+ const projectPath = join(COMPOSE_PATH, compose.appName, "code");
- const projectPath = join(COMPOSE_PATH, compose.appName, "code");
+ await spawnAsync(
+ "docker",
+ [...command.split(" ")],
+ (data) => {
+ if (writeStream.writable) {
+ writeStream.write(data.toString());
+ }
+ },
+ {
+ cwd: projectPath,
+ env: {
+ NODE_ENV: process.env.NODE_ENV,
+ PATH: process.env.PATH,
+ ...(composeType === "stack" && {
+ ...getEnviromentVariablesObject(compose.env, compose.project.env),
+ }),
+ },
+ }
+ );
- await spawnAsync(
- "docker",
- [...command.split(" ")],
- (data) => {
- if (writeStream.writable) {
- writeStream.write(data.toString());
- }
- },
- {
- cwd: projectPath,
- env: {
- NODE_ENV: process.env.NODE_ENV,
- PATH: process.env.PATH,
- },
- },
- );
-
- writeStream.write("Docker Compose Deployed: ✅");
- } catch (error) {
- writeStream.write(`Error ❌ ${(error as Error).message}`);
- throw error;
- } finally {
- writeStream.end();
- }
+ writeStream.write("Docker Compose Deployed: ✅");
+ } catch (error) {
+ writeStream.write(`Error ❌ ${(error as Error).message}`);
+ throw error;
+ } finally {
+ writeStream.end();
+ }
};
export const getBuildComposeCommand = async (
- compose: ComposeNested,
- logPath: string,
+ compose: ComposeNested,
+ logPath: string
) => {
- const { COMPOSE_PATH } = paths(true);
- const { sourceType, appName, mounts, composeType, domains, composePath } =
- compose;
- const command = createCommand(compose);
- const envCommand = getCreateEnvFileCommand(compose);
- const projectPath = join(COMPOSE_PATH, compose.appName, "code");
- const processComposeFileCommand = getProcessComposeFileCommand(compose);
+ const { COMPOSE_PATH } = paths(true);
+ const { sourceType, appName, mounts, composeType, domains, composePath } =
+ compose;
+ const command = createCommand(compose);
+ const envCommand = getCreateEnvFileCommand(compose);
+ const projectPath = join(COMPOSE_PATH, compose.appName, "code");
+ const exportEnvCommand = getExportEnvCommand(compose);
- const newCompose = await writeDomainsToComposeRemote(
- compose,
- domains,
- logPath,
- );
- const logContent = `
+ const newCompose = await writeDomainsToComposeRemote(
+ compose,
+ domains,
+ logPath
+ );
+ const logContent = `
App Name: ${appName}
Build Compose 🐳
Detected: ${mounts.length} mounts 📂
@@ -101,17 +107,17 @@ Command: docker ${command}
Source Type: docker ${sourceType} ✅
Compose Type: ${composeType} ✅`;
- const logBox = boxen(logContent, {
- padding: {
- left: 1,
- right: 1,
- bottom: 1,
- },
- width: 80,
- borderStyle: "double",
- });
+ const logBox = boxen(logContent, {
+ padding: {
+ left: 1,
+ right: 1,
+ bottom: 1,
+ },
+ width: 80,
+ borderStyle: "double",
+ });
- const bashCommand = `
+ const bashCommand = `
set -e
{
echo "${logBox}" >> "${logPath}"
@@ -122,7 +128,7 @@ Compose Type: ${composeType} ✅`;
cd "${projectPath}";
- ${processComposeFileCommand}
+ ${exportEnvCommand}
docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }
@@ -133,129 +139,102 @@ Compose Type: ${composeType} ✅`;
}
`;
- return await execAsyncRemote(compose.serverId, bashCommand);
+ return await execAsyncRemote(compose.serverId, bashCommand);
};
const sanitizeCommand = (command: string) => {
- const sanitizedCommand = command.trim();
+ const sanitizedCommand = command.trim();
- const parts = sanitizedCommand.split(/\s+/);
+ const parts = sanitizedCommand.split(/\s+/);
- const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1"));
+ const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1"));
- return restCommand.join(" ");
+ return restCommand.join(" ");
};
export const createCommand = (compose: ComposeNested) => {
- const { composeType, appName, sourceType } = compose;
+ const { composeType, appName, sourceType } = compose;
- if (compose.command) {
- return `${sanitizeCommand(compose.command)}`;
- }
+ if (compose.command) {
+ return `${sanitizeCommand(compose.command)}`;
+ }
- const path =
- sourceType === "raw"
- ? composeType === "stack"
- ? "docker-compose.processed.yml"
- : "docker-compose.yml"
- : composeType === "stack"
- ? join(dirname(compose.composePath), "docker-compose.processed.yml")
- : compose.composePath;
+ const path =
+ sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
+ let command = "";
- const baseCommand =
- composeType === "docker-compose"
- ? `compose -p ${appName} -f ${path} up -d --build --remove-orphans`
- : `stack deploy -c ${path} ${appName} --prune`;
- const customCommand = sanitizeCommand(compose.command);
- return customCommand ? `${baseCommand} ${customCommand}` : baseCommand;
+ if (composeType === "stack") {
+ command = `stack deploy -c ${path} ${appName} --prune`;
+ }
+
+ return command;
};
const createEnvFile = (compose: ComposeNested) => {
- const { COMPOSE_PATH } = paths();
- const { env, composePath, appName } = compose;
- const composeFilePath =
- join(COMPOSE_PATH, appName, "code", composePath) ||
- join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
+ const { COMPOSE_PATH } = paths();
+ const { env, composePath, appName } = compose;
+ const composeFilePath =
+ join(COMPOSE_PATH, appName, "code", composePath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
- const envFilePath = join(dirname(composeFilePath), ".env");
- let envContent = env || "";
- if (!envContent.includes("DOCKER_CONFIG")) {
- envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
- }
+ const envFilePath = join(dirname(composeFilePath), ".env");
+ let envContent = env || "";
+ if (!envContent.includes("DOCKER_CONFIG")) {
+ envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
+ }
- if (compose.randomize) {
- envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
- }
+ if (compose.randomize) {
+ envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
+ }
- const envFileContent = prepareEnvironmentVariables(
- envContent,
- compose.project.env,
- ).join("\n");
+ const envFileContent = prepareEnvironmentVariables(
+ envContent,
+ compose.project.env
+ ).join("\n");
- if (!existsSync(dirname(envFilePath))) {
- mkdirSync(dirname(envFilePath), { recursive: true });
- }
- writeFileSync(envFilePath, envFileContent);
-};
-
-export const processComposeFile = async (compose: ComposeNested) => {
- const { COMPOSE_PATH } = paths();
- if (compose.composeType === "stack") {
- const command = getProcessComposeFileCommand(compose);
- await execAsync(command, {
- cwd: join(COMPOSE_PATH, compose.appName, "code"),
- });
- }
-};
-
-export const getProcessComposeFileCommand = (compose: ComposeNested) => {
- const { composeType, sourceType } = compose;
-
- const composePath =
- sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
-
- const destinationPath =
- sourceType === "raw"
- ? "docker-compose.processed.yml"
- : join(dirname(compose.composePath), "docker-compose.processed.yml");
-
- let command = "";
- if (composeType === "stack") {
- command = [
- "export $(grep -v '^#' .env | xargs)",
- `docker stack config -c ${composePath} > ${destinationPath}`,
- ].join(" && ");
- }
-
- return command;
+ if (!existsSync(dirname(envFilePath))) {
+ mkdirSync(dirname(envFilePath), { recursive: true });
+ }
+ writeFileSync(envFilePath, envFileContent);
};
export const getCreateEnvFileCommand = (compose: ComposeNested) => {
- const { COMPOSE_PATH } = paths(true);
- const { env, composePath, appName } = compose;
- const composeFilePath =
- join(COMPOSE_PATH, appName, "code", composePath) ||
- join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
+ const { COMPOSE_PATH } = paths(true);
+ const { env, composePath, appName } = compose;
+ const composeFilePath =
+ join(COMPOSE_PATH, appName, "code", composePath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
- const envFilePath = join(dirname(composeFilePath), ".env");
+ const envFilePath = join(dirname(composeFilePath), ".env");
- let envContent = env || "";
- if (!envContent.includes("DOCKER_CONFIG")) {
- envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
- }
+ let envContent = env || "";
+ if (!envContent.includes("DOCKER_CONFIG")) {
+ envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
+ }
- if (compose.randomize) {
- envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
- }
+ if (compose.randomize) {
+ envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
+ }
- const envFileContent = prepareEnvironmentVariables(
- envContent,
- compose.project.env,
- ).join("\n");
+ const envFileContent = prepareEnvironmentVariables(
+ envContent,
+ compose.project.env
+ ).join("\n");
- const encodedContent = encodeBase64(envFileContent);
- return `
+ const encodedContent = encodeBase64(envFileContent);
+ return `
touch ${envFilePath};
echo "${encodedContent}" | base64 -d > "${envFilePath}";
`;
};
+
+const getExportEnvCommand = (compose: ComposeNested) => {
+ if (compose.composeType !== "stack") return "";
+
+ const envVars = getEnviromentVariablesObject(compose.env, compose.project.env);
+ const exports = Object.entries(envVars)
+ .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
+ .join("\n");
+
+ return exports ? `\n# Export environment variables\n${exports}\n` : "";
+};
diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts
index 64e98306..96b2e08b 100644
--- a/packages/server/src/utils/docker/utils.ts
+++ b/packages/server/src/utils/docker/utils.ts
@@ -15,526 +15,529 @@ import { spawnAsync } from "../process/spawnAsync";
import { getRemoteDocker } from "../servers/remote-docker";
interface RegistryAuth {
- username: string;
- password: string;
- registryUrl: string;
+ username: string;
+ password: string;
+ registryUrl: string;
}
export const pullImage = async (
- dockerImage: string,
- onData?: (data: any) => void,
- authConfig?: Partial,
+ dockerImage: string,
+ onData?: (data: any) => void,
+ authConfig?: Partial
): Promise => {
- try {
- if (!dockerImage) {
- throw new Error("Docker image not found");
- }
+ try {
+ if (!dockerImage) {
+ throw new Error("Docker image not found");
+ }
- if (authConfig?.username && authConfig?.password) {
- await spawnAsync(
- "docker",
- [
- "login",
- authConfig.registryUrl || "",
- "-u",
- authConfig.username,
- "-p",
- authConfig.password,
- ],
- onData,
- );
- }
- await spawnAsync("docker", ["pull", dockerImage], onData);
- } catch (error) {
- throw error;
- }
+ if (authConfig?.username && authConfig?.password) {
+ await spawnAsync(
+ "docker",
+ [
+ "login",
+ authConfig.registryUrl || "",
+ "-u",
+ authConfig.username,
+ "-p",
+ authConfig.password,
+ ],
+ onData
+ );
+ }
+ await spawnAsync("docker", ["pull", dockerImage], onData);
+ } catch (error) {
+ throw error;
+ }
};
export const pullRemoteImage = async (
- dockerImage: string,
- serverId: string,
- onData?: (data: any) => void,
- authConfig?: Partial,
+ dockerImage: string,
+ serverId: string,
+ onData?: (data: any) => void,
+ authConfig?: Partial
): Promise => {
- try {
- if (!dockerImage) {
- throw new Error("Docker image not found");
- }
+ try {
+ if (!dockerImage) {
+ throw new Error("Docker image not found");
+ }
- const remoteDocker = await getRemoteDocker(serverId);
+ const remoteDocker = await getRemoteDocker(serverId);
- await new Promise((resolve, reject) => {
- remoteDocker.pull(
- dockerImage,
- { authconfig: authConfig },
- (err, stream) => {
- if (err) {
- reject(err);
- return;
- }
+ await new Promise((resolve, reject) => {
+ remoteDocker.pull(
+ dockerImage,
+ { authconfig: authConfig },
+ (err, stream) => {
+ if (err) {
+ reject(err);
+ return;
+ }
- remoteDocker.modem.followProgress(
- stream as Readable,
- (err: Error | null, res) => {
- if (!err) {
- resolve(res);
- }
- if (err) {
- reject(err);
- }
- },
- (event) => {
- onData?.(event);
- },
- );
- },
- );
- });
- } catch (error) {
- throw error;
- }
+ remoteDocker.modem.followProgress(
+ stream as Readable,
+ (err: Error | null, res) => {
+ if (!err) {
+ resolve(res);
+ }
+ if (err) {
+ reject(err);
+ }
+ },
+ (event) => {
+ onData?.(event);
+ }
+ );
+ }
+ );
+ });
+ } catch (error) {
+ throw error;
+ }
};
export const containerExists = async (containerName: string) => {
- const container = docker.getContainer(containerName);
- try {
- await container.inspect();
- return true;
- } catch (error) {
- return false;
- }
+ const container = docker.getContainer(containerName);
+ try {
+ await container.inspect();
+ return true;
+ } catch (error) {
+ return false;
+ }
};
export const stopService = async (appName: string) => {
- try {
- await execAsync(`docker service scale ${appName}=0 `);
- } catch (error) {
- console.error(error);
- return error;
- }
+ try {
+ await execAsync(`docker service scale ${appName}=0 `);
+ } catch (error) {
+ console.error(error);
+ return error;
+ }
};
export const stopServiceRemote = async (serverId: string, appName: string) => {
- try {
- await execAsyncRemote(serverId, `docker service scale ${appName}=0 `);
- } catch (error) {
- console.error(error);
- return error;
- }
+ try {
+ await execAsyncRemote(serverId, `docker service scale ${appName}=0 `);
+ } catch (error) {
+ console.error(error);
+ return error;
+ }
};
export const getContainerByName = (name: string): Promise => {
- const opts = {
- limit: 1,
- filters: {
- name: [name],
- },
- };
- return new Promise((resolve, reject) => {
- docker.listContainers(opts, (err, containers) => {
- if (err) {
- reject(err);
- } else if (containers?.length === 0) {
- reject(new Error(`No container found with name: ${name}`));
- } else if (containers && containers?.length > 0 && containers[0]) {
- resolve(containers[0]);
- }
- });
- });
+ const opts = {
+ limit: 1,
+ filters: {
+ name: [name],
+ },
+ };
+ return new Promise((resolve, reject) => {
+ docker.listContainers(opts, (err, containers) => {
+ if (err) {
+ reject(err);
+ } else if (containers?.length === 0) {
+ reject(new Error(`No container found with name: ${name}`));
+ } else if (containers && containers?.length > 0 && containers[0]) {
+ resolve(containers[0]);
+ }
+ });
+ });
};
export const cleanUpUnusedImages = async (serverId?: string) => {
- try {
- const command = "docker image prune --force";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ const command = "docker image prune --force";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const cleanStoppedContainers = async (serverId?: string) => {
- try {
- const command = "docker container prune --force";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ const command = "docker container prune --force";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const cleanUpUnusedVolumes = async (serverId?: string) => {
- try {
- const command = "docker volume prune --force";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ const command = "docker volume prune --force";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const cleanUpInactiveContainers = async () => {
- try {
- const containers = await docker.listContainers({ all: true });
- const inactiveContainers = containers.filter(
- (container) => container.State !== "running",
- );
+ try {
+ const containers = await docker.listContainers({ all: true });
+ const inactiveContainers = containers.filter(
+ (container) => container.State !== "running"
+ );
- for (const container of inactiveContainers) {
- await docker.getContainer(container.Id).remove({ force: true });
- console.log(`Cleaning up inactive container: ${container.Id}`);
- }
- } catch (error) {
- console.error("Error cleaning up inactive containers:", error);
- throw error;
- }
+ for (const container of inactiveContainers) {
+ await docker.getContainer(container.Id).remove({ force: true });
+ console.log(`Cleaning up inactive container: ${container.Id}`);
+ }
+ } catch (error) {
+ console.error("Error cleaning up inactive containers:", error);
+ throw error;
+ }
};
export const cleanUpDockerBuilder = async (serverId?: string) => {
- const command = "docker builder prune --all --force";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
+ const command = "docker builder prune --all --force";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
};
export const cleanUpSystemPrune = async (serverId?: string) => {
- const command = "docker system prune --all --force --volumes";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
+ const command = "docker system prune --all --force --volumes";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
};
export const startService = async (appName: string) => {
- try {
- await execAsync(`docker service scale ${appName}=1 `);
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ await execAsync(`docker service scale ${appName}=1 `);
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const startServiceRemote = async (serverId: string, appName: string) => {
- try {
- await execAsyncRemote(serverId, `docker service scale ${appName}=1 `);
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ await execAsyncRemote(serverId, `docker service scale ${appName}=1 `);
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const removeService = async (
- appName: string,
- serverId?: string | null,
- deleteVolumes = false,
+ appName: string,
+ serverId?: string | null,
+ deleteVolumes = false
) => {
- try {
- const command = `docker service rm ${appName}`;
+ try {
+ const command = `docker service rm ${appName}`;
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
- } catch (error) {
- return error;
- }
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } catch (error) {
+ return error;
+ }
};
export const prepareEnvironmentVariables = (
- serviceEnv: string | null,
- projectEnv?: string | null,
+ serviceEnv: string | null,
+ projectEnv?: string | null
) => {
- const projectVars = parse(projectEnv ?? "");
- const serviceVars = parse(serviceEnv ?? "");
+ const projectVars = parse(projectEnv ?? "");
+ const serviceVars = parse(serviceEnv ?? "");
- const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
- let resolvedValue = value;
- if (projectVars) {
- resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
- if (projectVars[ref] !== undefined) {
- return projectVars[ref];
- }
- throw new Error(`Invalid project environment variable: project.${ref}`);
- });
- }
- return `${key}=${resolvedValue}`;
- });
+ const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
+ let resolvedValue = value;
+ if (projectVars) {
+ resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
+ if (projectVars[ref] !== undefined) {
+ return projectVars[ref];
+ }
+ throw new Error(`Invalid project environment variable: project.${ref}`);
+ });
+ }
+ return `${key}=${resolvedValue}`;
+ });
- return resolvedVars;
+ return resolvedVars;
};
-export const prepareBuildArgs = (input: string | null) => {
- const pairs = (input ?? "").split("\n");
+export const getEnviromentVariablesObject = (
+ input: string | null,
+ projectEnv?: string | null
+) => {
+ const envs = prepareEnvironmentVariables(input, projectEnv);
- const jsonObject: Record = {};
+ const jsonObject: Record = {};
- for (const pair of pairs) {
- const [key, value] = pair.split("=");
- if (key && value) {
- jsonObject[key] = value;
- }
- }
+ for (const pair of envs) {
+ const [key, value] = pair.split("=");
+ if (key && value) {
+ jsonObject[key] = value;
+ }
+ }
- return jsonObject;
+ return jsonObject;
};
export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => {
- if (!mounts || mounts.length === 0) {
- return [];
- }
+ if (!mounts || mounts.length === 0) {
+ return [];
+ }
- return mounts
- .filter((mount) => mount.type === "volume")
- .map((mount) => ({
- Type: "volume" as const,
- Source: mount.volumeName || "",
- Target: mount.mountPath,
- }));
+ return mounts
+ .filter((mount) => mount.type === "volume")
+ .map((mount) => ({
+ Type: "volume" as const,
+ Source: mount.volumeName || "",
+ Target: mount.mountPath,
+ }));
};
type Resources = {
- memoryLimit: string | null;
- memoryReservation: string | null;
- cpuLimit: string | null;
- cpuReservation: string | null;
+ memoryLimit: string | null;
+ memoryReservation: string | null;
+ cpuLimit: string | null;
+ cpuReservation: string | null;
};
export const calculateResources = ({
- memoryLimit,
- memoryReservation,
- cpuLimit,
- cpuReservation,
+ memoryLimit,
+ memoryReservation,
+ cpuLimit,
+ cpuReservation,
}: Resources): ResourceRequirements => {
- return {
- Limits: {
- MemoryBytes: memoryLimit ? Number.parseInt(memoryLimit) : undefined,
- NanoCPUs: cpuLimit ? Number.parseInt(cpuLimit) : undefined,
- },
- Reservations: {
- MemoryBytes: memoryReservation
- ? Number.parseInt(memoryReservation)
- : undefined,
- NanoCPUs: cpuReservation ? Number.parseInt(cpuReservation) : undefined,
- },
- };
+ return {
+ Limits: {
+ MemoryBytes: memoryLimit ? Number.parseInt(memoryLimit) : undefined,
+ NanoCPUs: cpuLimit ? Number.parseInt(cpuLimit) : undefined,
+ },
+ Reservations: {
+ MemoryBytes: memoryReservation
+ ? Number.parseInt(memoryReservation)
+ : undefined,
+ NanoCPUs: cpuReservation ? Number.parseInt(cpuReservation) : undefined,
+ },
+ };
};
export const generateConfigContainer = (application: ApplicationNested) => {
- const {
- healthCheckSwarm,
- restartPolicySwarm,
- placementSwarm,
- updateConfigSwarm,
- rollbackConfigSwarm,
- modeSwarm,
- labelsSwarm,
- replicas,
- mounts,
- networkSwarm,
- } = application;
+ const {
+ healthCheckSwarm,
+ restartPolicySwarm,
+ placementSwarm,
+ updateConfigSwarm,
+ rollbackConfigSwarm,
+ modeSwarm,
+ labelsSwarm,
+ replicas,
+ mounts,
+ networkSwarm,
+ } = application;
- const haveMounts = mounts.length > 0;
+ const haveMounts = mounts.length > 0;
- return {
- ...(healthCheckSwarm && {
- HealthCheck: healthCheckSwarm,
- }),
- ...(restartPolicySwarm
- ? {
- RestartPolicy: restartPolicySwarm,
- }
- : {}),
- ...(placementSwarm
- ? {
- Placement: placementSwarm,
- }
- : {
- // if app have mounts keep manager as constraint
- Placement: {
- Constraints: haveMounts ? ["node.role==manager"] : [],
- },
- }),
- ...(labelsSwarm && {
- Labels: labelsSwarm,
- }),
- ...(modeSwarm
- ? {
- Mode: modeSwarm,
- }
- : {
- // use replicas value if no modeSwarm provided
- Mode: {
- Replicated: {
- Replicas: replicas,
- },
- },
- }),
- ...(rollbackConfigSwarm && {
- RollbackConfig: rollbackConfigSwarm,
- }),
- ...(updateConfigSwarm
- ? { UpdateConfig: updateConfigSwarm }
- : {
- // default config if no updateConfigSwarm provided
- UpdateConfig: {
- Parallelism: 1,
- Order: "start-first",
- },
- }),
- ...(networkSwarm
- ? {
- Networks: networkSwarm,
- }
- : {
- Networks: [{ Target: "dokploy-network" }],
- }),
- };
+ return {
+ ...(healthCheckSwarm && {
+ HealthCheck: healthCheckSwarm,
+ }),
+ ...(restartPolicySwarm
+ ? {
+ RestartPolicy: restartPolicySwarm,
+ }
+ : {}),
+ ...(placementSwarm
+ ? {
+ Placement: placementSwarm,
+ }
+ : {
+ // if app have mounts keep manager as constraint
+ Placement: {
+ Constraints: haveMounts ? ["node.role==manager"] : [],
+ },
+ }),
+ ...(labelsSwarm && {
+ Labels: labelsSwarm,
+ }),
+ ...(modeSwarm
+ ? {
+ Mode: modeSwarm,
+ }
+ : {
+ // use replicas value if no modeSwarm provided
+ Mode: {
+ Replicated: {
+ Replicas: replicas,
+ },
+ },
+ }),
+ ...(rollbackConfigSwarm && {
+ RollbackConfig: rollbackConfigSwarm,
+ }),
+ ...(updateConfigSwarm
+ ? { UpdateConfig: updateConfigSwarm }
+ : {
+ // default config if no updateConfigSwarm provided
+ UpdateConfig: {
+ Parallelism: 1,
+ Order: "start-first",
+ },
+ }),
+ ...(networkSwarm
+ ? {
+ Networks: networkSwarm,
+ }
+ : {
+ Networks: [{ Target: "dokploy-network" }],
+ }),
+ };
};
export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => {
- if (!mounts || mounts.length === 0) {
- return [];
- }
+ if (!mounts || mounts.length === 0) {
+ return [];
+ }
- return mounts
- .filter((mount) => mount.type === "bind")
- .map((mount) => ({
- Type: "bind" as const,
- Source: mount.hostPath || "",
- Target: mount.mountPath,
- }));
+ return mounts
+ .filter((mount) => mount.type === "bind")
+ .map((mount) => ({
+ Type: "bind" as const,
+ Source: mount.hostPath || "",
+ Target: mount.mountPath,
+ }));
};
export const generateFileMounts = (
- appName: string,
- service:
- | ApplicationNested
- | MongoNested
- | MariadbNested
- | MysqlNested
- | PostgresNested
- | RedisNested,
+ appName: string,
+ service:
+ | ApplicationNested
+ | MongoNested
+ | MariadbNested
+ | MysqlNested
+ | PostgresNested
+ | RedisNested
) => {
- const { mounts } = service;
- const { APPLICATIONS_PATH } = paths(!!service.serverId);
- if (!mounts || mounts.length === 0) {
- return [];
- }
+ const { mounts } = service;
+ const { APPLICATIONS_PATH } = paths(!!service.serverId);
+ if (!mounts || mounts.length === 0) {
+ return [];
+ }
- return mounts
- .filter((mount) => mount.type === "file")
- .map((mount) => {
- const fileName = mount.filePath;
- const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
- const directory = path.join(absoluteBasePath, appName, "files");
- const sourcePath = path.join(directory, fileName || "");
- return {
- Type: "bind" as const,
- Source: sourcePath,
- Target: mount.mountPath,
- };
- });
+ return mounts
+ .filter((mount) => mount.type === "file")
+ .map((mount) => {
+ const fileName = mount.filePath;
+ const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
+ const directory = path.join(absoluteBasePath, appName, "files");
+ const sourcePath = path.join(directory, fileName || "");
+ return {
+ Type: "bind" as const,
+ Source: sourcePath,
+ Target: mount.mountPath,
+ };
+ });
};
export const createFile = async (
- outputPath: string,
- filePath: string,
- content: string,
+ outputPath: string,
+ filePath: string,
+ content: string
) => {
- try {
- const fullPath = path.join(outputPath, filePath);
- if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
- fs.mkdirSync(fullPath, { recursive: true });
- return;
- }
+ try {
+ const fullPath = path.join(outputPath, filePath);
+ if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
+ fs.mkdirSync(fullPath, { recursive: true });
+ return;
+ }
- const directory = path.dirname(fullPath);
- fs.mkdirSync(directory, { recursive: true });
- fs.writeFileSync(fullPath, content || "");
- } catch (error) {
- throw error;
- }
+ const directory = path.dirname(fullPath);
+ fs.mkdirSync(directory, { recursive: true });
+ fs.writeFileSync(fullPath, content || "");
+ } catch (error) {
+ throw error;
+ }
};
export const encodeBase64 = (content: string) =>
- Buffer.from(content, "utf-8").toString("base64");
+ Buffer.from(content, "utf-8").toString("base64");
export const getCreateFileCommand = (
- outputPath: string,
- filePath: string,
- content: string,
+ outputPath: string,
+ filePath: string,
+ content: string
) => {
- const fullPath = path.join(outputPath, filePath);
- if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
- return `mkdir -p ${fullPath};`;
- }
+ const fullPath = path.join(outputPath, filePath);
+ if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
+ return `mkdir -p ${fullPath};`;
+ }
- const directory = path.dirname(fullPath);
- const encodedContent = encodeBase64(content);
- return `
+ const directory = path.dirname(fullPath);
+ const encodedContent = encodeBase64(content);
+ return `
mkdir -p ${directory};
echo "${encodedContent}" | base64 -d > "${fullPath}";
`;
};
export const getServiceContainer = async (appName: string) => {
- try {
- const filter = {
- status: ["running"],
- label: [`com.docker.swarm.service.name=${appName}`],
- };
+ try {
+ const filter = {
+ status: ["running"],
+ label: [`com.docker.swarm.service.name=${appName}`],
+ };
- const containers = await docker.listContainers({
- filters: JSON.stringify(filter),
- });
+ const containers = await docker.listContainers({
+ filters: JSON.stringify(filter),
+ });
- if (containers.length === 0 || !containers[0]) {
- throw new Error(`No container found with name: ${appName}`);
- }
+ if (containers.length === 0 || !containers[0]) {
+ throw new Error(`No container found with name: ${appName}`);
+ }
- const container = containers[0];
+ const container = containers[0];
- return container;
- } catch (error) {
- throw error;
- }
+ return container;
+ } catch (error) {
+ throw error;
+ }
};
export const getRemoteServiceContainer = async (
- serverId: string,
- appName: string,
+ serverId: string,
+ appName: string
) => {
- try {
- const filter = {
- status: ["running"],
- label: [`com.docker.swarm.service.name=${appName}`],
- };
- const remoteDocker = await getRemoteDocker(serverId);
- const containers = await remoteDocker.listContainers({
- filters: JSON.stringify(filter),
- });
+ try {
+ const filter = {
+ status: ["running"],
+ label: [`com.docker.swarm.service.name=${appName}`],
+ };
+ const remoteDocker = await getRemoteDocker(serverId);
+ const containers = await remoteDocker.listContainers({
+ filters: JSON.stringify(filter),
+ });
- if (containers.length === 0 || !containers[0]) {
- throw new Error(`No container found with name: ${appName}`);
- }
+ if (containers.length === 0 || !containers[0]) {
+ throw new Error(`No container found with name: ${appName}`);
+ }
- const container = containers[0];
+ const container = containers[0];
- return container;
- } catch (error) {
- throw error;
- }
+ return container;
+ } catch (error) {
+ throw error;
+ }
};
From 7369b54f322c324d04a5296a64338d61e26432ee Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Fri, 31 Jan 2025 01:20:52 -0600
Subject: [PATCH 13/13] refactor: update
---
.../services/compose/[composeId].tsx | 2 -
packages/server/src/utils/builders/compose.ts | 295 +++----
packages/server/src/utils/docker/utils.ts | 814 +++++++++---------
3 files changed, 556 insertions(+), 555 deletions(-)
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
index f9ece2e7..67c9c7b1 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
@@ -221,9 +221,7 @@ const Service = (
)}
>
General
- {/* {data?.composeType === "docker-compose" && ( */}
Environment
- {/* )} */}
{!data?.serverId && (
Monitoring
)}
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 03da7858..af560728 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -1,105 +1,105 @@
import {
- createWriteStream,
- existsSync,
- mkdirSync,
- readFileSync,
- writeFileSync,
+ createWriteStream,
+ existsSync,
+ mkdirSync,
+ readFileSync,
+ writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type { InferResultType } from "@dokploy/server/types/with";
import boxen from "boxen";
import {
- writeDomainsToCompose,
- writeDomainsToComposeRemote,
+ writeDomainsToCompose,
+ writeDomainsToComposeRemote,
} from "../docker/domain";
import {
- encodeBase64,
- getEnviromentVariablesObject,
- prepareEnvironmentVariables,
+ encodeBase64,
+ getEnviromentVariablesObject,
+ prepareEnvironmentVariables,
} from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
- "compose",
- { project: true; mounts: true; domains: true }
+ "compose",
+ { project: true; mounts: true; domains: true }
>;
export const buildCompose = async (compose: ComposeNested, logPath: string) => {
- const writeStream = createWriteStream(logPath, { flags: "a" });
- const { sourceType, appName, mounts, composeType, domains } = compose;
- try {
- const { COMPOSE_PATH } = paths();
- const command = createCommand(compose);
- await writeDomainsToCompose(compose, domains);
- createEnvFile(compose);
+ const writeStream = createWriteStream(logPath, { flags: "a" });
+ const { sourceType, appName, mounts, composeType, domains } = compose;
+ try {
+ const { COMPOSE_PATH } = paths();
+ const command = createCommand(compose);
+ await writeDomainsToCompose(compose, domains);
+ createEnvFile(compose);
- const logContent = `
+ const logContent = `
App Name: ${appName}
Build Compose 🐳
Detected: ${mounts.length} mounts 📂
Command: docker ${command}
Source Type: docker ${sourceType} ✅
Compose Type: ${composeType} ✅`;
- const logBox = boxen(logContent, {
- padding: {
- left: 1,
- right: 1,
- bottom: 1,
- },
- width: 80,
- borderStyle: "double",
- });
- writeStream.write(`\n${logBox}\n`);
- const projectPath = join(COMPOSE_PATH, compose.appName, "code");
+ const logBox = boxen(logContent, {
+ padding: {
+ left: 1,
+ right: 1,
+ bottom: 1,
+ },
+ width: 80,
+ borderStyle: "double",
+ });
+ writeStream.write(`\n${logBox}\n`);
+ const projectPath = join(COMPOSE_PATH, compose.appName, "code");
- await spawnAsync(
- "docker",
- [...command.split(" ")],
- (data) => {
- if (writeStream.writable) {
- writeStream.write(data.toString());
- }
- },
- {
- cwd: projectPath,
- env: {
- NODE_ENV: process.env.NODE_ENV,
- PATH: process.env.PATH,
- ...(composeType === "stack" && {
- ...getEnviromentVariablesObject(compose.env, compose.project.env),
- }),
- },
- }
- );
+ await spawnAsync(
+ "docker",
+ [...command.split(" ")],
+ (data) => {
+ if (writeStream.writable) {
+ writeStream.write(data.toString());
+ }
+ },
+ {
+ cwd: projectPath,
+ env: {
+ NODE_ENV: process.env.NODE_ENV,
+ PATH: process.env.PATH,
+ ...(composeType === "stack" && {
+ ...getEnviromentVariablesObject(compose.env, compose.project.env),
+ }),
+ },
+ },
+ );
- writeStream.write("Docker Compose Deployed: ✅");
- } catch (error) {
- writeStream.write(`Error ❌ ${(error as Error).message}`);
- throw error;
- } finally {
- writeStream.end();
- }
+ writeStream.write("Docker Compose Deployed: ✅");
+ } catch (error) {
+ writeStream.write(`Error ❌ ${(error as Error).message}`);
+ throw error;
+ } finally {
+ writeStream.end();
+ }
};
export const getBuildComposeCommand = async (
- compose: ComposeNested,
- logPath: string
+ compose: ComposeNested,
+ logPath: string,
) => {
- const { COMPOSE_PATH } = paths(true);
- const { sourceType, appName, mounts, composeType, domains, composePath } =
- compose;
- const command = createCommand(compose);
- const envCommand = getCreateEnvFileCommand(compose);
- const projectPath = join(COMPOSE_PATH, compose.appName, "code");
- const exportEnvCommand = getExportEnvCommand(compose);
+ const { COMPOSE_PATH } = paths(true);
+ const { sourceType, appName, mounts, composeType, domains, composePath } =
+ compose;
+ const command = createCommand(compose);
+ const envCommand = getCreateEnvFileCommand(compose);
+ const projectPath = join(COMPOSE_PATH, compose.appName, "code");
+ const exportEnvCommand = getExportEnvCommand(compose);
- const newCompose = await writeDomainsToComposeRemote(
- compose,
- domains,
- logPath
- );
- const logContent = `
+ const newCompose = await writeDomainsToComposeRemote(
+ compose,
+ domains,
+ logPath,
+ );
+ const logContent = `
App Name: ${appName}
Build Compose 🐳
Detected: ${mounts.length} mounts 📂
@@ -107,17 +107,17 @@ Command: docker ${command}
Source Type: docker ${sourceType} ✅
Compose Type: ${composeType} ✅`;
- const logBox = boxen(logContent, {
- padding: {
- left: 1,
- right: 1,
- bottom: 1,
- },
- width: 80,
- borderStyle: "double",
- });
+ const logBox = boxen(logContent, {
+ padding: {
+ left: 1,
+ right: 1,
+ bottom: 1,
+ },
+ width: 80,
+ borderStyle: "double",
+ });
- const bashCommand = `
+ const bashCommand = `
set -e
{
echo "${logBox}" >> "${logPath}"
@@ -139,102 +139,105 @@ Compose Type: ${composeType} ✅`;
}
`;
- return await execAsyncRemote(compose.serverId, bashCommand);
+ return await execAsyncRemote(compose.serverId, bashCommand);
};
const sanitizeCommand = (command: string) => {
- const sanitizedCommand = command.trim();
+ const sanitizedCommand = command.trim();
- const parts = sanitizedCommand.split(/\s+/);
+ const parts = sanitizedCommand.split(/\s+/);
- const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1"));
+ const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1"));
- return restCommand.join(" ");
+ return restCommand.join(" ");
};
export const createCommand = (compose: ComposeNested) => {
- const { composeType, appName, sourceType } = compose;
+ const { composeType, appName, sourceType } = compose;
- if (compose.command) {
- return `${sanitizeCommand(compose.command)}`;
- }
+ if (compose.command) {
+ return `${sanitizeCommand(compose.command)}`;
+ }
- const path =
- sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
- let command = "";
+ const path =
+ sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
+ let command = "";
- if (composeType === "stack") {
- command = `stack deploy -c ${path} ${appName} --prune`;
- }
+ if (composeType === "stack") {
+ command = `stack deploy -c ${path} ${appName} --prune`;
+ }
- return command;
+ return command;
};
const createEnvFile = (compose: ComposeNested) => {
- const { COMPOSE_PATH } = paths();
- const { env, composePath, appName } = compose;
- const composeFilePath =
- join(COMPOSE_PATH, appName, "code", composePath) ||
- join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
+ const { COMPOSE_PATH } = paths();
+ const { env, composePath, appName } = compose;
+ const composeFilePath =
+ join(COMPOSE_PATH, appName, "code", composePath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
- const envFilePath = join(dirname(composeFilePath), ".env");
- let envContent = env || "";
- if (!envContent.includes("DOCKER_CONFIG")) {
- envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
- }
+ const envFilePath = join(dirname(composeFilePath), ".env");
+ let envContent = env || "";
+ if (!envContent.includes("DOCKER_CONFIG")) {
+ envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
+ }
- if (compose.randomize) {
- envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
- }
+ if (compose.randomize) {
+ envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
+ }
- const envFileContent = prepareEnvironmentVariables(
- envContent,
- compose.project.env
- ).join("\n");
+ const envFileContent = prepareEnvironmentVariables(
+ envContent,
+ compose.project.env,
+ ).join("\n");
- if (!existsSync(dirname(envFilePath))) {
- mkdirSync(dirname(envFilePath), { recursive: true });
- }
- writeFileSync(envFilePath, envFileContent);
+ if (!existsSync(dirname(envFilePath))) {
+ mkdirSync(dirname(envFilePath), { recursive: true });
+ }
+ writeFileSync(envFilePath, envFileContent);
};
export const getCreateEnvFileCommand = (compose: ComposeNested) => {
- const { COMPOSE_PATH } = paths(true);
- const { env, composePath, appName } = compose;
- const composeFilePath =
- join(COMPOSE_PATH, appName, "code", composePath) ||
- join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
+ const { COMPOSE_PATH } = paths(true);
+ const { env, composePath, appName } = compose;
+ const composeFilePath =
+ join(COMPOSE_PATH, appName, "code", composePath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
- const envFilePath = join(dirname(composeFilePath), ".env");
+ const envFilePath = join(dirname(composeFilePath), ".env");
- let envContent = env || "";
- if (!envContent.includes("DOCKER_CONFIG")) {
- envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
- }
+ let envContent = env || "";
+ if (!envContent.includes("DOCKER_CONFIG")) {
+ envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
+ }
- if (compose.randomize) {
- envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
- }
+ if (compose.randomize) {
+ envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
+ }
- const envFileContent = prepareEnvironmentVariables(
- envContent,
- compose.project.env
- ).join("\n");
+ const envFileContent = prepareEnvironmentVariables(
+ envContent,
+ compose.project.env,
+ ).join("\n");
- const encodedContent = encodeBase64(envFileContent);
- return `
+ const encodedContent = encodeBase64(envFileContent);
+ return `
touch ${envFilePath};
echo "${encodedContent}" | base64 -d > "${envFilePath}";
`;
};
const getExportEnvCommand = (compose: ComposeNested) => {
- if (compose.composeType !== "stack") return "";
-
- const envVars = getEnviromentVariablesObject(compose.env, compose.project.env);
- const exports = Object.entries(envVars)
- .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
- .join("\n");
-
- return exports ? `\n# Export environment variables\n${exports}\n` : "";
+ if (compose.composeType !== "stack") return "";
+
+ const envVars = getEnviromentVariablesObject(
+ compose.env,
+ compose.project.env,
+ );
+ const exports = Object.entries(envVars)
+ .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
+ .join("\n");
+
+ return exports ? `\n# Export environment variables\n${exports}\n` : "";
};
diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts
index 96b2e08b..062e0722 100644
--- a/packages/server/src/utils/docker/utils.ts
+++ b/packages/server/src/utils/docker/utils.ts
@@ -15,529 +15,529 @@ import { spawnAsync } from "../process/spawnAsync";
import { getRemoteDocker } from "../servers/remote-docker";
interface RegistryAuth {
- username: string;
- password: string;
- registryUrl: string;
+ username: string;
+ password: string;
+ registryUrl: string;
}
export const pullImage = async (
- dockerImage: string,
- onData?: (data: any) => void,
- authConfig?: Partial
+ dockerImage: string,
+ onData?: (data: any) => void,
+ authConfig?: Partial,
): Promise => {
- try {
- if (!dockerImage) {
- throw new Error("Docker image not found");
- }
+ try {
+ if (!dockerImage) {
+ throw new Error("Docker image not found");
+ }
- if (authConfig?.username && authConfig?.password) {
- await spawnAsync(
- "docker",
- [
- "login",
- authConfig.registryUrl || "",
- "-u",
- authConfig.username,
- "-p",
- authConfig.password,
- ],
- onData
- );
- }
- await spawnAsync("docker", ["pull", dockerImage], onData);
- } catch (error) {
- throw error;
- }
+ if (authConfig?.username && authConfig?.password) {
+ await spawnAsync(
+ "docker",
+ [
+ "login",
+ authConfig.registryUrl || "",
+ "-u",
+ authConfig.username,
+ "-p",
+ authConfig.password,
+ ],
+ onData,
+ );
+ }
+ await spawnAsync("docker", ["pull", dockerImage], onData);
+ } catch (error) {
+ throw error;
+ }
};
export const pullRemoteImage = async (
- dockerImage: string,
- serverId: string,
- onData?: (data: any) => void,
- authConfig?: Partial
+ dockerImage: string,
+ serverId: string,
+ onData?: (data: any) => void,
+ authConfig?: Partial,
): Promise => {
- try {
- if (!dockerImage) {
- throw new Error("Docker image not found");
- }
+ try {
+ if (!dockerImage) {
+ throw new Error("Docker image not found");
+ }
- const remoteDocker = await getRemoteDocker(serverId);
+ const remoteDocker = await getRemoteDocker(serverId);
- await new Promise((resolve, reject) => {
- remoteDocker.pull(
- dockerImage,
- { authconfig: authConfig },
- (err, stream) => {
- if (err) {
- reject(err);
- return;
- }
+ await new Promise((resolve, reject) => {
+ remoteDocker.pull(
+ dockerImage,
+ { authconfig: authConfig },
+ (err, stream) => {
+ if (err) {
+ reject(err);
+ return;
+ }
- remoteDocker.modem.followProgress(
- stream as Readable,
- (err: Error | null, res) => {
- if (!err) {
- resolve(res);
- }
- if (err) {
- reject(err);
- }
- },
- (event) => {
- onData?.(event);
- }
- );
- }
- );
- });
- } catch (error) {
- throw error;
- }
+ remoteDocker.modem.followProgress(
+ stream as Readable,
+ (err: Error | null, res) => {
+ if (!err) {
+ resolve(res);
+ }
+ if (err) {
+ reject(err);
+ }
+ },
+ (event) => {
+ onData?.(event);
+ },
+ );
+ },
+ );
+ });
+ } catch (error) {
+ throw error;
+ }
};
export const containerExists = async (containerName: string) => {
- const container = docker.getContainer(containerName);
- try {
- await container.inspect();
- return true;
- } catch (error) {
- return false;
- }
+ const container = docker.getContainer(containerName);
+ try {
+ await container.inspect();
+ return true;
+ } catch (error) {
+ return false;
+ }
};
export const stopService = async (appName: string) => {
- try {
- await execAsync(`docker service scale ${appName}=0 `);
- } catch (error) {
- console.error(error);
- return error;
- }
+ try {
+ await execAsync(`docker service scale ${appName}=0 `);
+ } catch (error) {
+ console.error(error);
+ return error;
+ }
};
export const stopServiceRemote = async (serverId: string, appName: string) => {
- try {
- await execAsyncRemote(serverId, `docker service scale ${appName}=0 `);
- } catch (error) {
- console.error(error);
- return error;
- }
+ try {
+ await execAsyncRemote(serverId, `docker service scale ${appName}=0 `);
+ } catch (error) {
+ console.error(error);
+ return error;
+ }
};
export const getContainerByName = (name: string): Promise => {
- const opts = {
- limit: 1,
- filters: {
- name: [name],
- },
- };
- return new Promise((resolve, reject) => {
- docker.listContainers(opts, (err, containers) => {
- if (err) {
- reject(err);
- } else if (containers?.length === 0) {
- reject(new Error(`No container found with name: ${name}`));
- } else if (containers && containers?.length > 0 && containers[0]) {
- resolve(containers[0]);
- }
- });
- });
+ const opts = {
+ limit: 1,
+ filters: {
+ name: [name],
+ },
+ };
+ return new Promise((resolve, reject) => {
+ docker.listContainers(opts, (err, containers) => {
+ if (err) {
+ reject(err);
+ } else if (containers?.length === 0) {
+ reject(new Error(`No container found with name: ${name}`));
+ } else if (containers && containers?.length > 0 && containers[0]) {
+ resolve(containers[0]);
+ }
+ });
+ });
};
export const cleanUpUnusedImages = async (serverId?: string) => {
- try {
- const command = "docker image prune --force";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ const command = "docker image prune --force";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const cleanStoppedContainers = async (serverId?: string) => {
- try {
- const command = "docker container prune --force";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ const command = "docker container prune --force";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const cleanUpUnusedVolumes = async (serverId?: string) => {
- try {
- const command = "docker volume prune --force";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ const command = "docker volume prune --force";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const cleanUpInactiveContainers = async () => {
- try {
- const containers = await docker.listContainers({ all: true });
- const inactiveContainers = containers.filter(
- (container) => container.State !== "running"
- );
+ try {
+ const containers = await docker.listContainers({ all: true });
+ const inactiveContainers = containers.filter(
+ (container) => container.State !== "running",
+ );
- for (const container of inactiveContainers) {
- await docker.getContainer(container.Id).remove({ force: true });
- console.log(`Cleaning up inactive container: ${container.Id}`);
- }
- } catch (error) {
- console.error("Error cleaning up inactive containers:", error);
- throw error;
- }
+ for (const container of inactiveContainers) {
+ await docker.getContainer(container.Id).remove({ force: true });
+ console.log(`Cleaning up inactive container: ${container.Id}`);
+ }
+ } catch (error) {
+ console.error("Error cleaning up inactive containers:", error);
+ throw error;
+ }
};
export const cleanUpDockerBuilder = async (serverId?: string) => {
- const command = "docker builder prune --all --force";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
+ const command = "docker builder prune --all --force";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
};
export const cleanUpSystemPrune = async (serverId?: string) => {
- const command = "docker system prune --all --force --volumes";
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
+ const command = "docker system prune --all --force --volumes";
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
};
export const startService = async (appName: string) => {
- try {
- await execAsync(`docker service scale ${appName}=1 `);
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ await execAsync(`docker service scale ${appName}=1 `);
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const startServiceRemote = async (serverId: string, appName: string) => {
- try {
- await execAsyncRemote(serverId, `docker service scale ${appName}=1 `);
- } catch (error) {
- console.error(error);
- throw error;
- }
+ try {
+ await execAsyncRemote(serverId, `docker service scale ${appName}=1 `);
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
};
export const removeService = async (
- appName: string,
- serverId?: string | null,
- deleteVolumes = false
+ appName: string,
+ serverId?: string | null,
+ deleteVolumes = false,
) => {
- try {
- const command = `docker service rm ${appName}`;
+ try {
+ const command = `docker service rm ${appName}`;
- if (serverId) {
- await execAsyncRemote(serverId, command);
- } else {
- await execAsync(command);
- }
- } catch (error) {
- return error;
- }
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } catch (error) {
+ return error;
+ }
};
export const prepareEnvironmentVariables = (
- serviceEnv: string | null,
- projectEnv?: string | null
+ serviceEnv: string | null,
+ projectEnv?: string | null,
) => {
- const projectVars = parse(projectEnv ?? "");
- const serviceVars = parse(serviceEnv ?? "");
+ const projectVars = parse(projectEnv ?? "");
+ const serviceVars = parse(serviceEnv ?? "");
- const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
- let resolvedValue = value;
- if (projectVars) {
- resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
- if (projectVars[ref] !== undefined) {
- return projectVars[ref];
- }
- throw new Error(`Invalid project environment variable: project.${ref}`);
- });
- }
- return `${key}=${resolvedValue}`;
- });
+ const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
+ let resolvedValue = value;
+ if (projectVars) {
+ resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
+ if (projectVars[ref] !== undefined) {
+ return projectVars[ref];
+ }
+ throw new Error(`Invalid project environment variable: project.${ref}`);
+ });
+ }
+ return `${key}=${resolvedValue}`;
+ });
- return resolvedVars;
+ return resolvedVars;
};
export const getEnviromentVariablesObject = (
- input: string | null,
- projectEnv?: string | null
+ input: string | null,
+ projectEnv?: string | null,
) => {
- const envs = prepareEnvironmentVariables(input, projectEnv);
+ const envs = prepareEnvironmentVariables(input, projectEnv);
- const jsonObject: Record = {};
+ const jsonObject: Record = {};
- for (const pair of envs) {
- const [key, value] = pair.split("=");
- if (key && value) {
- jsonObject[key] = value;
- }
- }
+ for (const pair of envs) {
+ const [key, value] = pair.split("=");
+ if (key && value) {
+ jsonObject[key] = value;
+ }
+ }
- return jsonObject;
+ return jsonObject;
};
export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => {
- if (!mounts || mounts.length === 0) {
- return [];
- }
+ if (!mounts || mounts.length === 0) {
+ return [];
+ }
- return mounts
- .filter((mount) => mount.type === "volume")
- .map((mount) => ({
- Type: "volume" as const,
- Source: mount.volumeName || "",
- Target: mount.mountPath,
- }));
+ return mounts
+ .filter((mount) => mount.type === "volume")
+ .map((mount) => ({
+ Type: "volume" as const,
+ Source: mount.volumeName || "",
+ Target: mount.mountPath,
+ }));
};
type Resources = {
- memoryLimit: string | null;
- memoryReservation: string | null;
- cpuLimit: string | null;
- cpuReservation: string | null;
+ memoryLimit: string | null;
+ memoryReservation: string | null;
+ cpuLimit: string | null;
+ cpuReservation: string | null;
};
export const calculateResources = ({
- memoryLimit,
- memoryReservation,
- cpuLimit,
- cpuReservation,
+ memoryLimit,
+ memoryReservation,
+ cpuLimit,
+ cpuReservation,
}: Resources): ResourceRequirements => {
- return {
- Limits: {
- MemoryBytes: memoryLimit ? Number.parseInt(memoryLimit) : undefined,
- NanoCPUs: cpuLimit ? Number.parseInt(cpuLimit) : undefined,
- },
- Reservations: {
- MemoryBytes: memoryReservation
- ? Number.parseInt(memoryReservation)
- : undefined,
- NanoCPUs: cpuReservation ? Number.parseInt(cpuReservation) : undefined,
- },
- };
+ return {
+ Limits: {
+ MemoryBytes: memoryLimit ? Number.parseInt(memoryLimit) : undefined,
+ NanoCPUs: cpuLimit ? Number.parseInt(cpuLimit) : undefined,
+ },
+ Reservations: {
+ MemoryBytes: memoryReservation
+ ? Number.parseInt(memoryReservation)
+ : undefined,
+ NanoCPUs: cpuReservation ? Number.parseInt(cpuReservation) : undefined,
+ },
+ };
};
export const generateConfigContainer = (application: ApplicationNested) => {
- const {
- healthCheckSwarm,
- restartPolicySwarm,
- placementSwarm,
- updateConfigSwarm,
- rollbackConfigSwarm,
- modeSwarm,
- labelsSwarm,
- replicas,
- mounts,
- networkSwarm,
- } = application;
+ const {
+ healthCheckSwarm,
+ restartPolicySwarm,
+ placementSwarm,
+ updateConfigSwarm,
+ rollbackConfigSwarm,
+ modeSwarm,
+ labelsSwarm,
+ replicas,
+ mounts,
+ networkSwarm,
+ } = application;
- const haveMounts = mounts.length > 0;
+ const haveMounts = mounts.length > 0;
- return {
- ...(healthCheckSwarm && {
- HealthCheck: healthCheckSwarm,
- }),
- ...(restartPolicySwarm
- ? {
- RestartPolicy: restartPolicySwarm,
- }
- : {}),
- ...(placementSwarm
- ? {
- Placement: placementSwarm,
- }
- : {
- // if app have mounts keep manager as constraint
- Placement: {
- Constraints: haveMounts ? ["node.role==manager"] : [],
- },
- }),
- ...(labelsSwarm && {
- Labels: labelsSwarm,
- }),
- ...(modeSwarm
- ? {
- Mode: modeSwarm,
- }
- : {
- // use replicas value if no modeSwarm provided
- Mode: {
- Replicated: {
- Replicas: replicas,
- },
- },
- }),
- ...(rollbackConfigSwarm && {
- RollbackConfig: rollbackConfigSwarm,
- }),
- ...(updateConfigSwarm
- ? { UpdateConfig: updateConfigSwarm }
- : {
- // default config if no updateConfigSwarm provided
- UpdateConfig: {
- Parallelism: 1,
- Order: "start-first",
- },
- }),
- ...(networkSwarm
- ? {
- Networks: networkSwarm,
- }
- : {
- Networks: [{ Target: "dokploy-network" }],
- }),
- };
+ return {
+ ...(healthCheckSwarm && {
+ HealthCheck: healthCheckSwarm,
+ }),
+ ...(restartPolicySwarm
+ ? {
+ RestartPolicy: restartPolicySwarm,
+ }
+ : {}),
+ ...(placementSwarm
+ ? {
+ Placement: placementSwarm,
+ }
+ : {
+ // if app have mounts keep manager as constraint
+ Placement: {
+ Constraints: haveMounts ? ["node.role==manager"] : [],
+ },
+ }),
+ ...(labelsSwarm && {
+ Labels: labelsSwarm,
+ }),
+ ...(modeSwarm
+ ? {
+ Mode: modeSwarm,
+ }
+ : {
+ // use replicas value if no modeSwarm provided
+ Mode: {
+ Replicated: {
+ Replicas: replicas,
+ },
+ },
+ }),
+ ...(rollbackConfigSwarm && {
+ RollbackConfig: rollbackConfigSwarm,
+ }),
+ ...(updateConfigSwarm
+ ? { UpdateConfig: updateConfigSwarm }
+ : {
+ // default config if no updateConfigSwarm provided
+ UpdateConfig: {
+ Parallelism: 1,
+ Order: "start-first",
+ },
+ }),
+ ...(networkSwarm
+ ? {
+ Networks: networkSwarm,
+ }
+ : {
+ Networks: [{ Target: "dokploy-network" }],
+ }),
+ };
};
export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => {
- if (!mounts || mounts.length === 0) {
- return [];
- }
+ if (!mounts || mounts.length === 0) {
+ return [];
+ }
- return mounts
- .filter((mount) => mount.type === "bind")
- .map((mount) => ({
- Type: "bind" as const,
- Source: mount.hostPath || "",
- Target: mount.mountPath,
- }));
+ return mounts
+ .filter((mount) => mount.type === "bind")
+ .map((mount) => ({
+ Type: "bind" as const,
+ Source: mount.hostPath || "",
+ Target: mount.mountPath,
+ }));
};
export const generateFileMounts = (
- appName: string,
- service:
- | ApplicationNested
- | MongoNested
- | MariadbNested
- | MysqlNested
- | PostgresNested
- | RedisNested
+ appName: string,
+ service:
+ | ApplicationNested
+ | MongoNested
+ | MariadbNested
+ | MysqlNested
+ | PostgresNested
+ | RedisNested,
) => {
- const { mounts } = service;
- const { APPLICATIONS_PATH } = paths(!!service.serverId);
- if (!mounts || mounts.length === 0) {
- return [];
- }
+ const { mounts } = service;
+ const { APPLICATIONS_PATH } = paths(!!service.serverId);
+ if (!mounts || mounts.length === 0) {
+ return [];
+ }
- return mounts
- .filter((mount) => mount.type === "file")
- .map((mount) => {
- const fileName = mount.filePath;
- const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
- const directory = path.join(absoluteBasePath, appName, "files");
- const sourcePath = path.join(directory, fileName || "");
- return {
- Type: "bind" as const,
- Source: sourcePath,
- Target: mount.mountPath,
- };
- });
+ return mounts
+ .filter((mount) => mount.type === "file")
+ .map((mount) => {
+ const fileName = mount.filePath;
+ const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
+ const directory = path.join(absoluteBasePath, appName, "files");
+ const sourcePath = path.join(directory, fileName || "");
+ return {
+ Type: "bind" as const,
+ Source: sourcePath,
+ Target: mount.mountPath,
+ };
+ });
};
export const createFile = async (
- outputPath: string,
- filePath: string,
- content: string
+ outputPath: string,
+ filePath: string,
+ content: string,
) => {
- try {
- const fullPath = path.join(outputPath, filePath);
- if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
- fs.mkdirSync(fullPath, { recursive: true });
- return;
- }
+ try {
+ const fullPath = path.join(outputPath, filePath);
+ if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
+ fs.mkdirSync(fullPath, { recursive: true });
+ return;
+ }
- const directory = path.dirname(fullPath);
- fs.mkdirSync(directory, { recursive: true });
- fs.writeFileSync(fullPath, content || "");
- } catch (error) {
- throw error;
- }
+ const directory = path.dirname(fullPath);
+ fs.mkdirSync(directory, { recursive: true });
+ fs.writeFileSync(fullPath, content || "");
+ } catch (error) {
+ throw error;
+ }
};
export const encodeBase64 = (content: string) =>
- Buffer.from(content, "utf-8").toString("base64");
+ Buffer.from(content, "utf-8").toString("base64");
export const getCreateFileCommand = (
- outputPath: string,
- filePath: string,
- content: string
+ outputPath: string,
+ filePath: string,
+ content: string,
) => {
- const fullPath = path.join(outputPath, filePath);
- if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
- return `mkdir -p ${fullPath};`;
- }
+ const fullPath = path.join(outputPath, filePath);
+ if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
+ return `mkdir -p ${fullPath};`;
+ }
- const directory = path.dirname(fullPath);
- const encodedContent = encodeBase64(content);
- return `
+ const directory = path.dirname(fullPath);
+ const encodedContent = encodeBase64(content);
+ return `
mkdir -p ${directory};
echo "${encodedContent}" | base64 -d > "${fullPath}";
`;
};
export const getServiceContainer = async (appName: string) => {
- try {
- const filter = {
- status: ["running"],
- label: [`com.docker.swarm.service.name=${appName}`],
- };
+ try {
+ const filter = {
+ status: ["running"],
+ label: [`com.docker.swarm.service.name=${appName}`],
+ };
- const containers = await docker.listContainers({
- filters: JSON.stringify(filter),
- });
+ const containers = await docker.listContainers({
+ filters: JSON.stringify(filter),
+ });
- if (containers.length === 0 || !containers[0]) {
- throw new Error(`No container found with name: ${appName}`);
- }
+ if (containers.length === 0 || !containers[0]) {
+ throw new Error(`No container found with name: ${appName}`);
+ }
- const container = containers[0];
+ const container = containers[0];
- return container;
- } catch (error) {
- throw error;
- }
+ return container;
+ } catch (error) {
+ throw error;
+ }
};
export const getRemoteServiceContainer = async (
- serverId: string,
- appName: string
+ serverId: string,
+ appName: string,
) => {
- try {
- const filter = {
- status: ["running"],
- label: [`com.docker.swarm.service.name=${appName}`],
- };
- const remoteDocker = await getRemoteDocker(serverId);
- const containers = await remoteDocker.listContainers({
- filters: JSON.stringify(filter),
- });
+ try {
+ const filter = {
+ status: ["running"],
+ label: [`com.docker.swarm.service.name=${appName}`],
+ };
+ const remoteDocker = await getRemoteDocker(serverId);
+ const containers = await remoteDocker.listContainers({
+ filters: JSON.stringify(filter),
+ });
- if (containers.length === 0 || !containers[0]) {
- throw new Error(`No container found with name: ${appName}`);
- }
+ if (containers.length === 0 || !containers[0]) {
+ throw new Error(`No container found with name: ${appName}`);
+ }
- const container = containers[0];
+ const container = containers[0];
- return container;
- } catch (error) {
- throw error;
- }
+ return container;
+ } catch (error) {
+ throw error;
+ }
};