From 4ca05414af2390f1be87154418d88aa5c828bd8f Mon Sep 17 00:00:00 2001 From: Leonhard Breuer Date: Wed, 3 Sep 2025 19:52:01 +0200 Subject: [PATCH 01/32] fix: use shellsafe docker command - add `shEscape` function - add `safeDockerLoginCommand` - use the new functions to contruct better registry login command --- packages/server/src/services/registry.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/server/src/services/registry.ts b/packages/server/src/services/registry.ts index 6468cd970..7c16bf017 100644 --- a/packages/server/src/services/registry.ts +++ b/packages/server/src/services/registry.ts @@ -10,6 +10,18 @@ import { IS_CLOUD } from "../constants"; export type Registry = typeof registry.$inferSelect; +function shQ(s: string): string { + if (!s) return "''"; + return `'${s.replace(/'/g, `'\\''`)}'`; +} + +function safeDockerLoginCommand(registry: string, user: string, pass: string) { + const escapedRegistry = shQ(registry) + const escapedUser = shQ(user) + const escapedPassword = shQ(pass) + return `echo ${escapedPassword} | docker login ${escapedRegistry} -u ${escapedUser} --password-stdin`; +} + export const createRegistry = async ( input: typeof apiCreateRegistry._type, organizationId: string, @@ -37,7 +49,7 @@ export const createRegistry = async ( message: "Select a server to add the registry", }); } - const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`; + const loginCommand = safeDockerLoginCommand(input.registryUrl, input.username, input.password) if (input.serverId && input.serverId !== "none") { await execAsyncRemote(input.serverId, loginCommand); } else if (newRegistry.registryType === "cloud") { From 02215d4e21c1baec76e326212d7227a48f6ed949 Mon Sep 17 00:00:00 2001 From: Leonhard Breuer Date: Wed, 3 Sep 2025 19:59:17 +0200 Subject: [PATCH 02/32] fix: use new command for registry updates --- packages/server/src/services/registry.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/server/src/services/registry.ts b/packages/server/src/services/registry.ts index 7c16bf017..150374be8 100644 --- a/packages/server/src/services/registry.ts +++ b/packages/server/src/services/registry.ts @@ -10,15 +10,15 @@ import { IS_CLOUD } from "../constants"; export type Registry = typeof registry.$inferSelect; -function shQ(s: string): string { +function shEscape(s: string | undefined): string { if (!s) return "''"; return `'${s.replace(/'/g, `'\\''`)}'`; } -function safeDockerLoginCommand(registry: string, user: string, pass: string) { - const escapedRegistry = shQ(registry) - const escapedUser = shQ(user) - const escapedPassword = shQ(pass) +function safeDockerLoginCommand(registry: string | undefined, user: string | undefined, pass: string | undefined) { + const escapedRegistry = shEscape(registry) + const escapedUser = shEscape(user) + const escapedPassword = shEscape(pass) return `echo ${escapedPassword} | docker login ${escapedRegistry} -u ${escapedUser} --password-stdin`; } @@ -103,7 +103,7 @@ export const updateRegistry = async ( .returning() .then((res) => res[0]); - const loginCommand = `echo ${response?.password} | docker login ${response?.registryUrl} --username ${response?.username} --password-stdin`; + const loginCommand = safeDockerLoginCommand(response?.registryUrl, response?.username, response?.password) if ( IS_CLOUD && From 146d82b6c4305da06970f336564ae7736b6f5708 Mon Sep 17 00:00:00 2001 From: Leonhard Breuer Date: Wed, 3 Sep 2025 20:12:16 +0200 Subject: [PATCH 03/32] feat: use `printf` instead of `echo` --- packages/server/src/services/registry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/services/registry.ts b/packages/server/src/services/registry.ts index 150374be8..8e98d2765 100644 --- a/packages/server/src/services/registry.ts +++ b/packages/server/src/services/registry.ts @@ -19,7 +19,7 @@ function safeDockerLoginCommand(registry: string | undefined, user: string | und const escapedRegistry = shEscape(registry) const escapedUser = shEscape(user) const escapedPassword = shEscape(pass) - return `echo ${escapedPassword} | docker login ${escapedRegistry} -u ${escapedUser} --password-stdin`; + return `printf %s ${escapedPassword} | docker login ${escapedRegistry} -u ${escapedUser} --password-stdin`; } export const createRegistry = async ( From 68945c6888909a884b5ce3d272ad77a0c5563a7a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 18:17:04 +0000 Subject: [PATCH 04/32] [autofix.ci] apply automated fixes --- packages/server/src/services/registry.ts | 30 +++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/server/src/services/registry.ts b/packages/server/src/services/registry.ts index 8e98d2765..ec8db8fa8 100644 --- a/packages/server/src/services/registry.ts +++ b/packages/server/src/services/registry.ts @@ -11,15 +11,19 @@ import { IS_CLOUD } from "../constants"; export type Registry = typeof registry.$inferSelect; function shEscape(s: string | undefined): string { - if (!s) return "''"; - return `'${s.replace(/'/g, `'\\''`)}'`; + if (!s) return "''"; + return `'${s.replace(/'/g, `'\\''`)}'`; } -function safeDockerLoginCommand(registry: string | undefined, user: string | undefined, pass: string | undefined) { - const escapedRegistry = shEscape(registry) - const escapedUser = shEscape(user) - const escapedPassword = shEscape(pass) - return `printf %s ${escapedPassword} | docker login ${escapedRegistry} -u ${escapedUser} --password-stdin`; +function safeDockerLoginCommand( + registry: string | undefined, + user: string | undefined, + pass: string | undefined, +) { + const escapedRegistry = shEscape(registry); + const escapedUser = shEscape(user); + const escapedPassword = shEscape(pass); + return `printf %s ${escapedPassword} | docker login ${escapedRegistry} -u ${escapedUser} --password-stdin`; } export const createRegistry = async ( @@ -49,7 +53,11 @@ export const createRegistry = async ( message: "Select a server to add the registry", }); } - const loginCommand = safeDockerLoginCommand(input.registryUrl, input.username, input.password) + const loginCommand = safeDockerLoginCommand( + input.registryUrl, + input.username, + input.password, + ); if (input.serverId && input.serverId !== "none") { await execAsyncRemote(input.serverId, loginCommand); } else if (newRegistry.registryType === "cloud") { @@ -103,7 +111,11 @@ export const updateRegistry = async ( .returning() .then((res) => res[0]); - const loginCommand = safeDockerLoginCommand(response?.registryUrl, response?.username, response?.password) + const loginCommand = safeDockerLoginCommand( + response?.registryUrl, + response?.username, + response?.password, + ); if ( IS_CLOUD && From 1664ae9b92bf744878cc003dade629da7d6b1e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B0=B8=E6=81=92?= Date: Mon, 8 Sep 2025 12:26:36 +0800 Subject: [PATCH 05/32] fix traefik 3.5.0 error fix traefik error:"both Docker and Swarm labels are defined" --- packages/server/src/utils/docker/domain.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index 4bd38f874..cfb74e974 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -251,11 +251,15 @@ export const addDomainToCompose = async ( } labels.unshift(...httpLabels); if (!compose.isolatedDeployment) { - if (!labels.includes("traefik.docker.network=dokploy-network")) { - labels.unshift("traefik.docker.network=dokploy-network"); - } - if (!labels.includes("traefik.swarm.network=dokploy-network")) { - labels.unshift("traefik.swarm.network=dokploy-network"); + if (compose.composeType === "docker-compose") { + if (!labels.includes("traefik.docker.network=dokploy-network")) { + labels.unshift("traefik.docker.network=dokploy-network"); + } + } else { + // Stack Case + if (!labels.includes("traefik.swarm.network=dokploy-network")) { + labels.unshift("traefik.swarm.network=dokploy-network"); + } } } } From abcbd2d59946da9ac749ab83396763c23b0c9352 Mon Sep 17 00:00:00 2001 From: HarikrishnanD Date: Tue, 9 Sep 2025 22:07:40 +0530 Subject: [PATCH 06/32] feat: auto-refresh services list when duplicating to same environment - Add cache invalidation for environment.one and environment.byProjectId queries - Fix issue where duplicated services weren't visible until hard refresh - Ensure proper invalidation when duplicating to current environment - Resolves #2565 --- .../dashboard/project/duplicate-project.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/project/duplicate-project.tsx b/apps/dokploy/components/dashboard/project/duplicate-project.tsx index df81c41f0..b87faa1ed 100644 --- a/apps/dokploy/components/dashboard/project/duplicate-project.tsx +++ b/apps/dokploy/components/dashboard/project/duplicate-project.tsx @@ -80,6 +80,25 @@ export const DuplicateProject = ({ api.project.duplicate.useMutation({ onSuccess: async (newProject) => { await utils.project.all.invalidate(); + + // If duplicating to same project+environment, invalidate the environment query + // to refresh the services list + if (duplicateType === "existing-environment") { + await utils.environment.one.invalidate({ environmentId: selectedTargetEnvironment }); + await utils.environment.byProjectId.invalidate({ projectId: selectedTargetProject }); + + // If duplicating to the same environment we're currently viewing, + // also invalidate the current environment to refresh the services list + if (selectedTargetEnvironment === environmentId) { + await utils.environment.one.invalidate({ environmentId }); + // Also invalidate the project query to refresh the project data + const projectId = router.query.projectId as string; + if (projectId) { + await utils.project.one.invalidate({ projectId }); + } + } + } + toast.success( duplicateType === "new-project" ? "Project duplicated successfully" @@ -328,4 +347,4 @@ export const DuplicateProject = ({ ); -}; +}; \ No newline at end of file From ec11325165f9ad52952911c26e22c1d6cf0bdf1e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:40:00 +0000 Subject: [PATCH 07/32] [autofix.ci] apply automated fixes --- .../dashboard/project/duplicate-project.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/dokploy/components/dashboard/project/duplicate-project.tsx b/apps/dokploy/components/dashboard/project/duplicate-project.tsx index b87faa1ed..3455f34cf 100644 --- a/apps/dokploy/components/dashboard/project/duplicate-project.tsx +++ b/apps/dokploy/components/dashboard/project/duplicate-project.tsx @@ -80,14 +80,18 @@ export const DuplicateProject = ({ api.project.duplicate.useMutation({ onSuccess: async (newProject) => { await utils.project.all.invalidate(); - + // If duplicating to same project+environment, invalidate the environment query // to refresh the services list if (duplicateType === "existing-environment") { - await utils.environment.one.invalidate({ environmentId: selectedTargetEnvironment }); - await utils.environment.byProjectId.invalidate({ projectId: selectedTargetProject }); - - // If duplicating to the same environment we're currently viewing, + await utils.environment.one.invalidate({ + environmentId: selectedTargetEnvironment, + }); + await utils.environment.byProjectId.invalidate({ + projectId: selectedTargetProject, + }); + + // If duplicating to the same environment we're currently viewing, // also invalidate the current environment to refresh the services list if (selectedTargetEnvironment === environmentId) { await utils.environment.one.invalidate({ environmentId }); @@ -98,7 +102,7 @@ export const DuplicateProject = ({ } } } - + toast.success( duplicateType === "new-project" ? "Project duplicated successfully" @@ -347,4 +351,4 @@ export const DuplicateProject = ({ ); -}; \ No newline at end of file +}; From c9715b19a3d6a046bef099ea7bc8471bd6c3924b Mon Sep 17 00:00:00 2001 From: Yigit SAHIN Date: Wed, 10 Sep 2025 11:27:22 +0300 Subject: [PATCH 08/32] feat(backups): make mariadb backups non-blocking closes #2443 --- packages/server/src/utils/backups/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/backups/utils.ts b/packages/server/src/utils/backups/utils.ts index b4563287b..d9d5150b9 100644 --- a/packages/server/src/utils/backups/utils.ts +++ b/packages/server/src/utils/backups/utils.ts @@ -89,7 +89,7 @@ export const getMariadbBackupCommand = ( databaseUser: string, databasePassword: string, ) => { - return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`; + return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --single-transaction --quick --databases ${database} | gzip"`; }; export const getMysqlBackupCommand = ( From d8a98f39361b9e2d2bbd7d9cac145405810b5c46 Mon Sep 17 00:00:00 2001 From: Andrew Margetts Date: Fri, 12 Sep 2025 15:27:10 +0200 Subject: [PATCH 09/32] =?UTF-8?q?=C2=A0fix:=20fix=20typo=20for=20Github=20?= =?UTF-8?q?clone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/server/src/utils/providers/github.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/providers/github.ts b/packages/server/src/utils/providers/github.ts index b13bbfcc4..30125db8b 100644 --- a/packages/server/src/utils/providers/github.ts +++ b/packages/server/src/utils/providers/github.ts @@ -171,7 +171,7 @@ export const cloneGithubRepository = async ({ const cloneUrl = `https://oauth2:${token}@${repoclone}`; try { - writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`); + writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: ✅\n`); const cloneArgs = [ "clone", "--branch", From 788dbe405092c447335c1b05580ca5f66d7806f9 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:23:03 -0600 Subject: [PATCH 10/32] chore(package): bump version from v0.25.1 to v0.25.2 --- apps/dokploy/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index 883b0f880..68958907c 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.25.1", + "version": "v0.25.2", "private": true, "license": "Apache-2.0", "type": "module", From d9398b955816c68a22826490461b1ca5585493fd Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:43:27 -0600 Subject: [PATCH 11/32] feat(workers): add third worker and increase concurrency for existing workers --- apps/schedules/src/index.ts | 3 ++- apps/schedules/src/workers.ts | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/schedules/src/index.ts b/apps/schedules/src/index.ts index af8ad8ff9..c37deac56 100644 --- a/apps/schedules/src/index.ts +++ b/apps/schedules/src/index.ts @@ -11,7 +11,7 @@ import { } from "./queue.js"; import { jobQueueSchema } from "./schema.js"; import { initializeJobs } from "./utils.js"; -import { firstWorker, secondWorker } from "./workers.js"; +import { firstWorker, secondWorker, thirdWorker } from "./workers.js"; const app = new Hono(); @@ -91,6 +91,7 @@ export const gracefulShutdown = async (signal: string) => { logger.warn(`Received ${signal}, closing server...`); await firstWorker.close(); await secondWorker.close(); + await thirdWorker.close(); process.exit(0); }; diff --git a/apps/schedules/src/workers.ts b/apps/schedules/src/workers.ts index 1a6d24705..37f24b38d 100644 --- a/apps/schedules/src/workers.ts +++ b/apps/schedules/src/workers.ts @@ -7,22 +7,34 @@ import { runJobs } from "./utils.js"; export const firstWorker = new Worker( "backupQueue", async (job: Job) => { - logger.info({ data: job.data }, "Running job"); + logger.info({ data: job.data }, "Running job first worker"); await runJobs(job.data); }, { - concurrency: 50, + concurrency: 100, connection, }, ); export const secondWorker = new Worker( "backupQueue", async (job: Job) => { - logger.info({ data: job.data }, "Running job"); + logger.info({ data: job.data }, "Running job second worker"); await runJobs(job.data); }, { - concurrency: 50, + concurrency: 100, + connection, + }, +); + +export const thirdWorker = new Worker( + "backupQueue", + async (job: Job) => { + logger.info({ data: job.data }, "Running job third worker"); + await runJobs(job.data); + }, + { + concurrency: 100, connection, }, ); From d13975adac62e14cbb1dc59734200c9ab394b1bf Mon Sep 17 00:00:00 2001 From: HarikrishnanD Date: Tue, 16 Sep 2025 13:11:22 +0530 Subject: [PATCH 12/32] fix: add email validation to profile form to prevent empty values - Add email format and required validation to profile form schema - Add email validation to API schema and service layer - Improve error handling in user update mutation - Fixes issue where users could save empty email causing sign-in failures -#2613 --- .../dashboard/settings/profile/profile-form.tsx | 2 +- apps/dokploy/server/api/routers/user.ts | 10 +++++++++- packages/server/src/db/schema/user.ts | 1 + packages/server/src/services/user.ts | 13 +++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx index 7ac65f1b2..2f6d6793c 100644 --- a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx @@ -33,7 +33,7 @@ import { Disable2FA } from "./disable-2fa"; import { Enable2FA } from "./enable-2fa"; const profileSchema = z.object({ - email: z.string(), + email: z.string().email("Please enter a valid email address").min(1, "Email is required"), password: z.string().nullable(), currentPassword: z.string().nullable(), image: z.string().optional(), diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 2e7c7a0c5..362f97727 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -192,7 +192,15 @@ export const userRouter = createTRPCRouter({ }) .where(eq(account.userId, ctx.user.id)); } - return await updateUser(ctx.user.id, input); + + try { + return await updateUser(ctx.user.id, input); + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error.message : "Failed to update user", + }); + } }), getUserByToken: publicProcedure .input(apiFindOneToken) diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index 933a7490c..a26a8ccdd 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -322,6 +322,7 @@ export const apiUpdateWebServerMonitoring = z.object({ }); export const apiUpdateUser = createSchema.partial().extend({ + email: z.string().email("Please enter a valid email address").min(1, "Email is required").optional(), password: z.string().optional(), currentPassword: z.string().optional(), name: z.string().optional(), diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts index 728d5b8ee..adfccd5b2 100644 --- a/packages/server/src/services/user.ts +++ b/packages/server/src/services/user.ts @@ -296,6 +296,19 @@ export const findMemberById = async ( }; export const updateUser = async (userId: string, userData: Partial) => { + // Validate email if it's being updated + if (userData.email !== undefined) { + if (!userData.email || userData.email.trim() === "") { + throw new Error("Email is required and cannot be empty"); + } + + // Basic email format validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(userData.email)) { + throw new Error("Please enter a valid email address"); + } + } + const user = await db .update(users_temp) .set({ From c1896f88776d1b1d14801a77c58a3b6d829e2d6f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 07:47:55 +0000 Subject: [PATCH 13/32] [autofix.ci] apply automated fixes --- .../components/dashboard/settings/profile/profile-form.tsx | 5 ++++- apps/dokploy/server/api/routers/user.ts | 5 +++-- packages/server/src/db/schema/user.ts | 6 +++++- packages/server/src/services/user.ts | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx index 2f6d6793c..d040472d6 100644 --- a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx @@ -33,7 +33,10 @@ import { Disable2FA } from "./disable-2fa"; import { Enable2FA } from "./enable-2fa"; const profileSchema = z.object({ - email: z.string().email("Please enter a valid email address").min(1, "Email is required"), + email: z + .string() + .email("Please enter a valid email address") + .min(1, "Email is required"), password: z.string().nullable(), currentPassword: z.string().nullable(), image: z.string().optional(), diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 362f97727..d30b99b3a 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -192,13 +192,14 @@ export const userRouter = createTRPCRouter({ }) .where(eq(account.userId, ctx.user.id)); } - + try { return await updateUser(ctx.user.id, input); } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", - message: error instanceof Error ? error.message : "Failed to update user", + message: + error instanceof Error ? error.message : "Failed to update user", }); } }), diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index a26a8ccdd..ca92f50e8 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -322,7 +322,11 @@ export const apiUpdateWebServerMonitoring = z.object({ }); export const apiUpdateUser = createSchema.partial().extend({ - email: z.string().email("Please enter a valid email address").min(1, "Email is required").optional(), + email: z + .string() + .email("Please enter a valid email address") + .min(1, "Email is required") + .optional(), password: z.string().optional(), currentPassword: z.string().optional(), name: z.string().optional(), diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts index adfccd5b2..ae03432a1 100644 --- a/packages/server/src/services/user.ts +++ b/packages/server/src/services/user.ts @@ -301,7 +301,7 @@ export const updateUser = async (userId: string, userData: Partial) => { if (!userData.email || userData.email.trim() === "") { throw new Error("Email is required and cannot be empty"); } - + // Basic email format validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(userData.email)) { From 64475bbb13f0ba0b1cae6b1d0472ab333c0fedda Mon Sep 17 00:00:00 2001 From: HarikrishnanD Date: Wed, 17 Sep 2025 14:07:03 +0530 Subject: [PATCH 14/32] fix: Compose domain display logic in projects dashboard - Uncommented the commented-out Compose domain rendering code in ShowProjects.tsx - Fixed data structure to properly iterate through project.environments and env.compose - Added proper condition checking for compose services - Compose services now display their domains in the projects dashboard dropdown - Resolves issue #2606 where template-deployed Compose services didn't show domains --- .../components/dashboard/projects/show.tsx | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index fafa08ffc..12ee3b211 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -291,45 +291,48 @@ export const ShowProjects = () => { )} )} - {/* - {project.compose.length > 0 && ( + {project.environments.some( + (env) => env.compose.length > 0, + ) && ( Compose - {project.compose.map((comp) => ( -
- - - - {comp.name} - - + {project.environments.map((env) => + env.compose.map((comp) => ( +
- {comp.domains.map((domain) => ( - - + + {comp.name} + + + + {comp.domains.map((domain) => ( + - - {domain.host} - - - - - ))} - -
- ))} + + + {domain.host} + + + + + ))} +
+
+ )), + )}
- )} */} + )} ) : null} From f2ead668908470e13ec086cadea0f80f8dc70ba2 Mon Sep 17 00:00:00 2001 From: Dragos-Paul Pop Date: Wed, 17 Sep 2025 11:48:12 +0300 Subject: [PATCH 15/32] Update gitlab.ts cloneRawGitlabRepositoryRemote to use gitlabBranch Cloning a GitLab repository for a compose service to a remote server incorrectly used the "branch" column from Postgres' "compose" table instead of the "gitlabBranch" column causing an error. --- packages/server/src/utils/providers/gitlab.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/providers/gitlab.ts b/packages/server/src/utils/providers/gitlab.ts index 4b6bb7965..840347fdb 100644 --- a/packages/server/src/utils/providers/gitlab.ts +++ b/packages/server/src/utils/providers/gitlab.ts @@ -401,7 +401,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => { const { appName, gitlabPathNamespace, - branch, + gitlabBranch, gitlabId, serverId, enableSubmodules, @@ -429,7 +429,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => { try { const command = ` rm -rf ${outputPath}; - git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} + git clone --branch ${gitlabBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} `; await execAsyncRemote(serverId, command); } catch (error) { From 30b66a4828eb191669fdebf624a50d2bb6a57693 Mon Sep 17 00:00:00 2001 From: Nishant Mogha Date: Fri, 19 Sep 2025 21:13:20 +0530 Subject: [PATCH 16/32] fix: prevent shrinking icon button for view mode on add template --- apps/dokploy/components/dashboard/project/add-template.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index 38ff85d15..f79038386 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -248,7 +248,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => { onClick={() => setViewMode(viewMode === "detailed" ? "icon" : "detailed") } - className="h-9 w-9" + className="h-9 w-9 flex-shrink-0" > {viewMode === "detailed" ? ( From 5e01505e4d88b2de909c9b0ff0044447d83d2c15 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 20 Sep 2025 16:36:36 -0600 Subject: [PATCH 17/32] fix: update input class for better responsiveness in add template component --- apps/dokploy/components/dashboard/project/add-template.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index f79038386..72c42da49 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -171,7 +171,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => { setQuery(e.target.value)} - className="w-full sm:w-[200px]" + className="w-full" value={query} /> Date: Sat, 20 Sep 2025 23:57:38 -0600 Subject: [PATCH 18/32] refactor: replace getPublicIpWithFallback with getLocalServerIp for improved local IP retrieval --- apps/dokploy/server/api/routers/cluster.ts | 6 +++--- apps/dokploy/server/wss/terminal.ts | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/server/api/routers/cluster.ts b/apps/dokploy/server/api/routers/cluster.ts index 7dde96df5..6c118d802 100644 --- a/apps/dokploy/server/api/routers/cluster.ts +++ b/apps/dokploy/server/api/routers/cluster.ts @@ -7,7 +7,7 @@ import { } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; -import { getPublicIpWithFallback } from "@/server/wss/terminal"; +import { getLocalServerIp } from "@/server/wss/terminal"; import { createTRPCRouter, protectedProcedure } from "../trpc"; export const clusterRouter = createTRPCRouter({ getNodes: protectedProcedure @@ -61,7 +61,7 @@ export const clusterRouter = createTRPCRouter({ const result = await docker.swarmInspect(); const docker_version = await docker.version(); - let ip = await getPublicIpWithFallback(); + let ip = await getLocalServerIp(); if (input.serverId) { const server = await findServerById(input.serverId); ip = server?.ipAddress; @@ -85,7 +85,7 @@ export const clusterRouter = createTRPCRouter({ const result = await docker.swarmInspect(); const docker_version = await docker.version(); - let ip = await getPublicIpWithFallback(); + let ip = await getLocalServerIp(); if (input.serverId) { const server = await findServerById(input.serverId); ip = server?.ipAddress; diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index c24be3122..fa37d492b 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -1,5 +1,10 @@ import type http from "node:http"; -import { findServerById, IS_CLOUD, validateRequest } from "@dokploy/server"; +import { + execAsync, + findServerById, + IS_CLOUD, + validateRequest, +} from "@dokploy/server"; import { publicIpv4, publicIpv6 } from "public-ip"; import { Client, type ConnectConfig } from "ssh2"; import { WebSocketServer } from "ws"; @@ -44,6 +49,21 @@ export const getPublicIpWithFallback = async () => { return ip; }; +export const getLocalServerIp = async () => { + try { + const command = `ip addr show | grep -E "inet (192\.168\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.)" | head -n1 | awk '{print $2}' | cut -d/ -f1`; + const { stdout } = await execAsync(command); + const ip = stdout.trim(); + return ( + ip || + "We were unable to obtain the local server IP, please use your private IP address" + ); + } catch (error) { + console.error("Error to obtain local server IP", error); + return "We were unable to obtain the local server IP, please use your private IP address"; + } +}; + export const setupTerminalWebSocketServer = ( server: http.Server, ) => { From 8524cd0972cfbc82a9779461b671e77959ee9fa7 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:09:19 -0600 Subject: [PATCH 19/32] fix: update registry tag construction to handle optional registry URL --- packages/server/src/utils/cluster/upload.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/cluster/upload.ts b/packages/server/src/utils/cluster/upload.ts index f982d414b..c13a2701c 100644 --- a/packages/server/src/utils/cluster/upload.ts +++ b/packages/server/src/utils/cluster/upload.ts @@ -22,8 +22,8 @@ export const uploadImage = async ( // For ghcr.io: ghcr.io/username/image:tag // For docker.io: docker.io/username/image:tag const registryTag = imagePrefix - ? `${registryUrl}/${imagePrefix}/${imageName}` - : `${registryUrl}/${username}/${imageName}`; + ? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}` + : `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`; try { writeStream.write( From 948ed2cc0d8ec8a3a86e5275169d2e24386e407a Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:13:56 -0600 Subject: [PATCH 20/32] fix: improve registry tag construction to conditionally include registry URL --- packages/server/src/utils/builders/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index 492f799a8..5d3261dbb 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -220,8 +220,8 @@ const getImageName = (application: ApplicationNested) => { if (registry) { const { registryUrl, imagePrefix, username } = registry; const registryTag = imagePrefix - ? `${registryUrl}/${imagePrefix}/${imageName}` - : `${registryUrl}/${username}/${imageName}`; + ? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}` + : `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`; return registryTag; } From f46637b8e18ae720642396ea7c378b6bc0e30197 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:26:48 -0600 Subject: [PATCH 21/32] fix: enhance error handling in volume backup process by adding cleanup for .tar files --- .../server/src/utils/volume-backups/utils.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/server/src/utils/volume-backups/utils.ts b/packages/server/src/utils/volume-backups/utils.ts index 5b55c240c..b6a34e2aa 100644 --- a/packages/server/src/utils/volume-backups/utils.ts +++ b/packages/server/src/utils/volume-backups/utils.ts @@ -1,15 +1,17 @@ -import { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; -import { scheduledJobs, scheduleJob } from "node-schedule"; +import path from "node:path"; +import { paths } from "@dokploy/server/constants"; import { createDeploymentVolumeBackup, updateDeploymentStatus, } from "@dokploy/server/services/deployment"; +import { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; import { execAsync, execAsyncRemote, } from "@dokploy/server/utils/process/execAsync"; -import { backupVolume } from "./backup"; +import { scheduledJobs, scheduleJob } from "node-schedule"; import { getS3Credentials, normalizeS3Path } from "../backups/utils"; +import { backupVolume } from "./backup"; export const scheduleVolumeBackup = async (volumeBackupId: string) => { const volumeBackup = await findVolumeBackupById(volumeBackupId); @@ -76,7 +78,20 @@ export const runVolumeBackup = async (volumeBackupId: string) => { await updateDeploymentStatus(deployment.deploymentId, "done"); } catch (error) { + const { VOLUME_BACKUPS_PATH } = paths(!!serverId); + const volumeBackupPath = path.join( + VOLUME_BACKUPS_PATH, + volumeBackup.appName, + ); + // delete all the .tar files + const command = `rm -rf ${volumeBackupPath}/*.tar`; + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command); + } await updateDeploymentStatus(deployment.deploymentId, "error"); + console.error(error); } }; From f13028ee70d724ea44c14c795afbe16a51e77c75 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 21 Sep 2025 01:07:25 -0600 Subject: [PATCH 22/32] fix: handle optional configFiles in template details and improve mapping safety --- .../dashboard/project/ai/step-three.tsx | 2 +- .../dashboard/project/ai/step-two.tsx | 33 ++------- .../project/ai/template-generator.tsx | 2 +- packages/server/src/services/ai.ts | 71 +++++++++++++------ 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/apps/dokploy/components/dashboard/project/ai/step-three.tsx b/apps/dokploy/components/dashboard/project/ai/step-three.tsx index 188cd5257..bf074f3bd 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-three.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-three.tsx @@ -88,7 +88,7 @@ export const StepThree = ({ templateInfo }: StepProps) => {

Configuration Files

    - {templateInfo?.details?.configFiles.map((file, index) => ( + {templateInfo?.details?.configFiles?.map((file, index) => (
  • {file.filePath} diff --git a/apps/dokploy/components/dashboard/project/ai/step-two.tsx b/apps/dokploy/components/dashboard/project/ai/step-two.tsx index e76e8ff76..57a00a72d 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-two.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-two.tsx @@ -1,5 +1,5 @@ -import { Bot, Eye, EyeOff, PlusCircle, Trash2 } from "lucide-react"; -import { useEffect, useState } from "react"; +import { Bot, PlusCircle, Trash2 } from "lucide-react"; +import { useEffect } from "react"; import ReactMarkdown from "react-markdown"; import { toast } from "sonner"; import { AlertBlock } from "@/components/shared/alert-block"; @@ -27,7 +27,6 @@ export interface StepProps { export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { const suggestions = templateInfo.suggestions || []; const selectedVariant = templateInfo.details; - const [showValues, setShowValues] = useState>({}); const { mutateAsync, isLoading, error, isError } = api.ai.suggest.useMutation(); @@ -44,7 +43,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { .then((data) => { setTemplateInfo({ ...templateInfo, - suggestions: data, + suggestions: data || [], }); }) .catch((error) => { @@ -54,10 +53,6 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { }); }, [templateInfo.userInput]); - const toggleShowValue = (name: string) => { - setShowValues((prev) => ({ ...prev, [name]: !prev[name] })); - }; - const handleEnvVariableChange = ( index: number, field: "name" | "value", @@ -310,9 +305,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { />
    handleEnvVariableChange( @@ -323,19 +316,6 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => { } placeholder="Variable Value" /> -