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 66c3ef53..67c9c7b1 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx @@ -211,21 +211,17 @@ const Service = ( General - {data?.composeType === "docker-compose" && ( - - Environment - - )} + Environment {!data?.serverId && ( Monitoring )} diff --git a/apps/dokploy/templates/evolutionapi/index.ts b/apps/dokploy/templates/evolutionapi/index.ts index bd3e81aa..6ca7a3b6 100644 --- a/apps/dokploy/templates/evolutionapi/index.ts +++ b/apps/dokploy/templates/evolutionapi/index.ts @@ -1,59 +1,59 @@ import { - type DomainSchema, - type Schema, - type Template, - generateBase64, - generatePassword, - generateRandomDomain, + type DomainSchema, + type Schema, + type Template, + generateBase64, + generatePassword, + generateRandomDomain, } from "../utils"; export function generate(schema: Schema): Template { - const mainDomain = generateRandomDomain(schema); - const apiKey = generateBase64(64); - const postgresPassword = generatePassword(); + const mainDomain = generateRandomDomain(schema); + const apiKey = generateBase64(64); + const postgresPassword = generatePassword(); - const domains: DomainSchema[] = [ - { - host: mainDomain, - port: 8080, - serviceName: "evolution-api", - }, - ]; + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 8080, + serviceName: "evolution-api", + }, + ]; - const envs = [ - `SERVER_URL=https://${mainDomain}`, - "AUTHENTICATION_TYPE=apikey", - `AUTHENTICATION_API_KEY=${apiKey}`, - "AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true", + const envs = [ + `SERVER_URL=https://${mainDomain}`, + "AUTHENTICATION_TYPE=apikey", + `AUTHENTICATION_API_KEY=${apiKey}`, + "AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true", - "LANGUAGE=en", - "CONFIG_SESSION_PHONE_CLIENT=Evolution API", - "CONFIG_SESSION_PHONE_NAME=Chrome", - "TELEMETRY=false", - "TELEMETRY_URL=", + "LANGUAGE=en", + "CONFIG_SESSION_PHONE_CLIENT=Evolution API", + "CONFIG_SESSION_PHONE_NAME=Chrome", + "TELEMETRY=false", + "TELEMETRY_URL=", - "POSTGRES_DATABASE=evolution", - "POSTGRES_USERNAME=postgresql", - `POSTGRES_PASSWORD=${postgresPassword}`, - "DATABASE_ENABLED=true", - "DATABASE_PROVIDER=postgresql", - `DATABASE_CONNECTION_URI=postgres://postgresql:${postgresPassword}@evolution-postgres:5432/evolution`, - "DATABASE_SAVE_DATA_INSTANCE=true", - "DATABASE_SAVE_DATA_NEW_MESSAGE=true", - "DATABASE_SAVE_MESSAGE_UPDATE=true", - "DATABASE_SAVE_DATA_CONTACTS=true", - "DATABASE_SAVE_DATA_CHATS=true", - "DATABASE_SAVE_DATA_LABELS=true", - "DATABASE_SAVE_DATA_HISTORIC=true", + "POSTGRES_DATABASE=evolution", + "POSTGRES_USERNAME=postgresql", + `POSTGRES_PASSWORD=${postgresPassword}`, + "DATABASE_ENABLED=true", + "DATABASE_PROVIDER=postgresql", + `DATABASE_CONNECTION_URI=postgres://postgresql:${postgresPassword}@evolution-postgres:5432/evolution`, + "DATABASE_SAVE_DATA_INSTANCE=true", + "DATABASE_SAVE_DATA_NEW_MESSAGE=true", + "DATABASE_SAVE_MESSAGE_UPDATE=true", + "DATABASE_SAVE_DATA_CONTACTS=true", + "DATABASE_SAVE_DATA_CHATS=true", + "DATABASE_SAVE_DATA_LABELS=true", + "DATABASE_SAVE_DATA_HISTORIC=true", - "CACHE_REDIS_ENABLED=true", - "CACHE_REDIS_URI=redis://evolution-redis:6379", - "CACHE_REDIS_PREFIX_KEY=evolution", - "CACHE_REDIS_SAVE_INSTANCES=true", - ]; + "CACHE_REDIS_ENABLED=true", + "CACHE_REDIS_URI=redis://evolution-redis:6379", + "CACHE_REDIS_PREFIX_KEY=evolution", + "CACHE_REDIS_SAVE_INSTANCES=true", + ]; - return { - domains, - envs, - }; + return { + domains, + envs, + }; } diff --git a/apps/dokploy/templates/formbricks/index.ts b/apps/dokploy/templates/formbricks/index.ts index 8cede8ed..fc179f49 100644 --- a/apps/dokploy/templates/formbricks/index.ts +++ b/apps/dokploy/templates/formbricks/index.ts @@ -26,7 +26,6 @@ export function generate(schema: Schema): Template { `NEXTAUTH_SECRET=${secretBase}`, `ENCRYPTION_KEY=${encryptionKey}`, `CRON_SECRET=${cronSecret}`, - ]; const mounts: Template["mounts"] = []; diff --git a/apps/dokploy/templates/listmonk/index.ts b/apps/dokploy/templates/listmonk/index.ts index 113b3862..2a25efca 100644 --- a/apps/dokploy/templates/listmonk/index.ts +++ b/apps/dokploy/templates/listmonk/index.ts @@ -17,7 +17,7 @@ export function generate(schema: Schema): Template { ]; const envs = [ - `# visit the page to setup your super admin user`, + "# visit the page to setup your super admin user", "# check config.toml in Advanced / Volumes for more options", ]; diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index a0492314..39bf423f 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -558,6 +558,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", }); diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index ae181fc3..af560728 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -2,6 +2,7 @@ import { createWriteStream, existsSync, mkdirSync, + readFileSync, writeFileSync, } from "node:fs"; import { dirname, join } from "node:path"; @@ -12,8 +13,12 @@ import { writeDomainsToCompose, writeDomainsToComposeRemote, } from "../docker/domain"; -import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils"; -import { execAsyncRemote } from "../process/execAsync"; +import { + encodeBase64, + getEnviromentVariablesObject, + prepareEnvironmentVariables, +} from "../docker/utils"; +import { execAsync, execAsyncRemote } from "../process/execAsync"; import { spawnAsync } from "../process/spawnAsync"; export type ComposeNested = InferResultType< @@ -30,12 +35,12 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => { createEnvFile(compose); const logContent = ` -App Name: ${appName} -Build Compose 🐳 -Detected: ${mounts.length} mounts 📂 -Command: docker ${command} -Source Type: docker ${sourceType} ✅ -Compose Type: ${composeType} ✅`; + 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, @@ -46,7 +51,6 @@ Compose Type: ${composeType} ✅`; borderStyle: "double", }); writeStream.write(`\n${logBox}\n`); - const projectPath = join(COMPOSE_PATH, compose.appName, "code"); await spawnAsync( @@ -62,6 +66,9 @@ Compose Type: ${composeType} ✅`; env: { NODE_ENV: process.env.NODE_ENV, PATH: process.env.PATH, + ...(composeType === "stack" && { + ...getEnviromentVariablesObject(compose.env, compose.project.env), + }), }, }, ); @@ -85,6 +92,7 @@ export const getBuildComposeCommand = async ( 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, @@ -120,6 +128,8 @@ Compose Type: ${composeType} ✅`; cd "${projectPath}"; + ${exportEnvCommand} + docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; } echo "Docker Compose Deployed: ✅" >> "${logPath}" @@ -153,9 +163,7 @@ export const createCommand = (compose: ComposeNested) => { sourceType === "raw" ? "docker-compose.yml" : compose.composePath; let command = ""; - if (composeType === "docker-compose") { - command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`; - } else if (composeType === "stack") { + if (composeType === "stack") { command = `stack deploy -c ${path} ${appName} --prune`; } @@ -219,3 +227,17 @@ 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..062e0722 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -278,12 +278,15 @@ export const prepareEnvironmentVariables = ( 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 = {}; - for (const pair of pairs) { + for (const pair of envs) { const [key, value] = pair.split("="); if (key && value) { jsonObject[key] = value;