@@ -159,10 +191,14 @@ export const DuplicateProject = ({
{isLoading ? (
<>
- Duplicating...
+ {duplicateType === "new-project"
+ ? "Duplicating project..."
+ : "Duplicating services..."}
>
+ ) : duplicateType === "new-project" ? (
+ "Duplicate project"
) : (
- "Duplicate"
+ "Duplicate services"
)}
diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts
index 05875450..b98c93fa 100644
--- a/apps/dokploy/server/api/routers/project.ts
+++ b/apps/dokploy/server/api/routers/project.ts
@@ -309,6 +309,7 @@ export const projectRouter = createTRPCRouter({
}),
)
.optional(),
+ duplicateInSameProject: z.boolean().default(false),
}),
)
.mutation(async ({ ctx, input }) => {
@@ -331,15 +332,17 @@ export const projectRouter = createTRPCRouter({
});
}
- // Create new project
- const newProject = await createProject(
- {
- name: input.name,
- description: input.description,
- env: sourceProject.env,
- },
- ctx.session.activeOrganizationId,
- );
+ // Create new project or use existing one
+ const targetProject = input.duplicateInSameProject
+ ? sourceProject
+ : await createProject(
+ {
+ name: input.name,
+ description: input.description,
+ env: sourceProject.env,
+ },
+ ctx.session.activeOrganizationId,
+ );
if (input.includeServices) {
const servicesToDuplicate = input.selectedServices || [];
@@ -362,7 +365,10 @@ export const projectRouter = createTRPCRouter({
const newApplication = await createApplication({
...application,
- projectId: newProject.projectId,
+ name: input.duplicateInSameProject
+ ? `${application.name} (copy)`
+ : application.name,
+ projectId: targetProject.projectId,
});
for (const domain of domains) {
@@ -423,7 +429,10 @@ export const projectRouter = createTRPCRouter({
const newPostgres = await createPostgres({
...postgres,
- projectId: newProject.projectId,
+ name: input.duplicateInSameProject
+ ? `${postgres.name} (copy)`
+ : postgres.name,
+ projectId: targetProject.projectId,
});
for (const mount of mounts) {
@@ -449,7 +458,10 @@ export const projectRouter = createTRPCRouter({
await findMariadbById(id);
const newMariadb = await createMariadb({
...mariadb,
- projectId: newProject.projectId,
+ name: input.duplicateInSameProject
+ ? `${mariadb.name} (copy)`
+ : mariadb.name,
+ projectId: targetProject.projectId,
});
for (const mount of mounts) {
@@ -475,7 +487,10 @@ export const projectRouter = createTRPCRouter({
await findMongoById(id);
const newMongo = await createMongo({
...mongo,
- projectId: newProject.projectId,
+ name: input.duplicateInSameProject
+ ? `${mongo.name} (copy)`
+ : mongo.name,
+ projectId: targetProject.projectId,
});
for (const mount of mounts) {
@@ -501,7 +516,10 @@ export const projectRouter = createTRPCRouter({
await findMySqlById(id);
const newMysql = await createMysql({
...mysql,
- projectId: newProject.projectId,
+ name: input.duplicateInSameProject
+ ? `${mysql.name} (copy)`
+ : mysql.name,
+ projectId: targetProject.projectId,
});
for (const mount of mounts) {
@@ -526,7 +544,10 @@ export const projectRouter = createTRPCRouter({
const { redisId, mounts, ...redis } = await findRedisById(id);
const newRedis = await createRedis({
...redis,
- projectId: newProject.projectId,
+ name: input.duplicateInSameProject
+ ? `${redis.name} (copy)`
+ : redis.name,
+ projectId: targetProject.projectId,
});
for (const mount of mounts) {
@@ -545,7 +566,10 @@ export const projectRouter = createTRPCRouter({
await findComposeById(id);
const newCompose = await createCompose({
...compose,
- projectId: newProject.projectId,
+ name: input.duplicateInSameProject
+ ? `${compose.name} (copy)`
+ : compose.name,
+ projectId: targetProject.projectId,
});
for (const mount of mounts) {
@@ -572,21 +596,20 @@ export const projectRouter = createTRPCRouter({
};
// Duplicate selected services
-
for (const service of servicesToDuplicate) {
await duplicateService(service.id, service.type);
}
}
- if (ctx.user.role === "member") {
+ if (!input.duplicateInSameProject && ctx.user.role === "member") {
await addNewProject(
ctx.user.id,
- newProject.projectId,
+ targetProject.projectId,
ctx.session.activeOrganizationId,
);
}
- return newProject;
+ return targetProject;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
From 10fa3c8cf156a2a42cbdc0a1a4ae1b60915b41ed Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Sat, 17 May 2025 15:18:31 -0600
Subject: [PATCH 21/55] fix: update environment file generation to include
APP_NAME variable
---
packages/server/src/utils/builders/compose.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 7b00fc72..92add1e6 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -190,7 +190,8 @@ const createEnvFile = (compose: ComposeNested) => {
join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
const envFilePath = join(dirname(composeFilePath), ".env");
- let envContent = env || "";
+ let envContent = `APP_NAME=${appName}\n`;
+ envContent += env || "";
if (!envContent.includes("DOCKER_CONFIG")) {
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
}
@@ -219,7 +220,8 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const envFilePath = join(dirname(composeFilePath), ".env");
- let envContent = env || "";
+ let envContent = `APP_NAME=${appName}\n`;
+ envContent += env || "";
if (!envContent.includes("DOCKER_CONFIG")) {
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
}
From cff01ed4383d65b3488e9cc22d6d3e551b20d7a0 Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Sat, 17 May 2025 15:27:42 -0600
Subject: [PATCH 22/55] refactor: modify template processing to include
APP_NAME variable in configuration
---
apps/dokploy/server/api/routers/compose.ts | 32 +++++++++++++++++++---
1 file changed, 28 insertions(+), 4 deletions(-)
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index 48c2bfa7..583dde5c 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -439,7 +439,15 @@ export const composeRouter = createTRPCRouter({
}
const projectName = slugify(`${project.name} ${input.id}`);
- const generate = processTemplate(template.config, {
+ const appName = `${projectName}-${generatePassword(6)}`;
+ const config = {
+ ...template.config,
+ variables: {
+ APP_NAME: appName,
+ ...template.config.variables,
+ },
+ };
+ const generate = processTemplate(config, {
serverIp: serverIp,
projectName: projectName,
});
@@ -451,7 +459,7 @@ export const composeRouter = createTRPCRouter({
serverId: input.serverId,
name: input.id,
sourceType: "raw",
- appName: `${projectName}-${generatePassword(6)}`,
+ appName: appName,
isolatedDeployment: true,
});
@@ -605,7 +613,15 @@ export const composeRouter = createTRPCRouter({
});
}
- const processedTemplate = processTemplate(config, {
+ const configModified = {
+ ...config,
+ variables: {
+ APP_NAME: compose.appName,
+ ...config.variables,
+ },
+ };
+
+ const processedTemplate = processTemplate(configModified, {
serverIp: serverIp,
projectName: compose.appName,
});
@@ -675,7 +691,15 @@ export const composeRouter = createTRPCRouter({
});
}
- const processedTemplate = processTemplate(config, {
+ const configModified = {
+ ...config,
+ variables: {
+ APP_NAME: compose.appName,
+ ...config.variables,
+ },
+ };
+
+ const processedTemplate = processTemplate(configModified, {
serverIp: serverIp,
projectName: compose.appName,
});
From 19b56771b8073453372a6d597b53bc268adbdbaa Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Sat, 17 May 2025 15:28:52 -0600
Subject: [PATCH 23/55] style: update styling for environment display and
increase scroll area height in import component
---
.../dashboard/application/advanced/import/show-import.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx b/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx
index 0e848fec..aa359d67 100644
--- a/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx
+++ b/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx
@@ -263,7 +263,7 @@ export const ShowImport = ({ composeId }: Props) => {
{templateInfo.template.envs.map((env, index) => (
{env}
@@ -328,7 +328,7 @@ export const ShowImport = ({ composeId }: Props) => {
Mount File Content
-
+
Date: Sun, 18 May 2025 15:07:05 +1000
Subject: [PATCH 24/55] fix: multiple notifications for isolated compose and
randomize compose
---
.../dashboard/compose/general/isolated-deployment.tsx | 8 ++------
.../dashboard/compose/general/randomize-compose.tsx | 4 ----
2 files changed, 2 insertions(+), 10 deletions(-)
diff --git a/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx b/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx
index 8ee9c786..29aa3bc0 100644
--- a/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx
@@ -71,8 +71,8 @@ export const IsolatedDeployment = ({ composeId }: Props) => {
isolatedDeployment: formData?.isolatedDeployment || false,
})
.then(async (_data) => {
- randomizeCompose();
- refetch();
+ await randomizeCompose();
+ await refetch();
toast.success("Compose updated");
})
.catch(() => {
@@ -88,11 +88,7 @@ export const IsolatedDeployment = ({ composeId }: Props) => {
.then(async (data) => {
await utils.project.all.invalidate();
setCompose(data);
- toast.success("Compose Isolated");
})
- .catch(() => {
- toast.error("Error isolating the compose");
- });
};
return (
diff --git a/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx b/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
index 4cc877fd..46ec61d3 100644
--- a/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
@@ -94,11 +94,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
.then(async (data) => {
await utils.project.all.invalidate();
setCompose(data);
- toast.success("Compose randomized");
})
- .catch(() => {
- toast.error("Error randomizing the compose");
- });
};
return (
From 0f48f2c83024b3b6e74510cdbc0aaf98410a6de3 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Sun, 18 May 2025 05:12:06 +0000
Subject: [PATCH 25/55] [autofix.ci] apply automated fixes
---
.../dashboard/compose/general/isolated-deployment.tsx | 9 ++++-----
.../dashboard/compose/general/randomize-compose.tsx | 9 ++++-----
2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx b/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx
index 29aa3bc0..d76f7902 100644
--- a/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/isolated-deployment.tsx
@@ -84,11 +84,10 @@ export const IsolatedDeployment = ({ composeId }: Props) => {
await mutateAsync({
composeId,
suffix: data?.appName || "",
- })
- .then(async (data) => {
- await utils.project.all.invalidate();
- setCompose(data);
- })
+ }).then(async (data) => {
+ await utils.project.all.invalidate();
+ setCompose(data);
+ });
};
return (
diff --git a/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx b/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
index 46ec61d3..8315416d 100644
--- a/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
@@ -90,11 +90,10 @@ export const RandomizeCompose = ({ composeId }: Props) => {
await mutateAsync({
composeId,
suffix,
- })
- .then(async (data) => {
- await utils.project.all.invalidate();
- setCompose(data);
- })
+ }).then(async (data) => {
+ await utils.project.all.invalidate();
+ setCompose(data);
+ });
};
return (
From e2befc24a5052060c0d17396220a4ee0814c381c Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Sun, 18 May 2025 02:17:41 -0600
Subject: [PATCH 26/55] fix: update rsync command in web server backup to
remove verbose flag
---
packages/server/src/utils/backups/web-server.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/packages/server/src/utils/backups/web-server.ts b/packages/server/src/utils/backups/web-server.ts
index ed6b020f..7af65b1c 100644
--- a/packages/server/src/utils/backups/web-server.ts
+++ b/packages/server/src/utils/backups/web-server.ts
@@ -66,8 +66,12 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
writeStream.write(`Cleaning up temp file: ${cleanupCommand}\n`);
await execAsync(cleanupCommand);
+ console.log(
+ `rsync -a --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
+ );
+
await execAsync(
- `rsync -av --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
+ `rsync -a --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
);
writeStream.write("Copied filesystem to temp directory\n");
From cbf9aef0df6492ac7c92285feba41a445e1bb77f Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Sun, 18 May 2025 02:18:05 -0600
Subject: [PATCH 27/55] fix: remove console log for rsync command in web server
backup
---
packages/server/src/utils/backups/web-server.ts | 4 ----
1 file changed, 4 deletions(-)
diff --git a/packages/server/src/utils/backups/web-server.ts b/packages/server/src/utils/backups/web-server.ts
index 7af65b1c..54592d87 100644
--- a/packages/server/src/utils/backups/web-server.ts
+++ b/packages/server/src/utils/backups/web-server.ts
@@ -66,10 +66,6 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
writeStream.write(`Cleaning up temp file: ${cleanupCommand}\n`);
await execAsync(cleanupCommand);
- console.log(
- `rsync -a --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
- );
-
await execAsync(
`rsync -a --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
);
From 616e11722ca7776901c6a43faf8a001fccf7bef3 Mon Sep 17 00:00:00 2001
From: Khiet Tam Nguyen
Date: Sun, 18 May 2025 18:26:44 +1000
Subject: [PATCH 28/55] fix: randomize-compose missing await
---
.../dashboard/compose/general/randomize-compose.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx b/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
index 8315416d..5ac67e0c 100644
--- a/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx
@@ -77,8 +77,8 @@ export const RandomizeCompose = ({ composeId }: Props) => {
randomize: formData?.randomize || false,
})
.then(async (_data) => {
- randomizeCompose();
- refetch();
+ await randomizeCompose();
+ await refetch();
toast.success("Compose updated");
})
.catch(() => {
From 17a26353b6d2412b6023ddfc4cc785605c1368fc Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Sun, 18 May 2025 02:30:04 -0600
Subject: [PATCH 29/55] chore: bump version to v0.22.6 in package.json
---
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 12ed499b..adb10f0e 100644
--- a/apps/dokploy/package.json
+++ b/apps/dokploy/package.json
@@ -1,6 +1,6 @@
{
"name": "dokploy",
- "version": "v0.22.5",
+ "version": "v0.22.6",
"private": true,
"license": "Apache-2.0",
"type": "module",
From 2fa2e76e2eeafa2ba3da791bb7e6f205463d618f Mon Sep 17 00:00:00 2001
From: Khiet Tam Nguyen
Date: Tue, 20 May 2025 15:28:37 +1000
Subject: [PATCH 30/55] fix: throw error if traefik container creation fails
for a reason other than port
---
packages/server/src/setup/traefik-setup.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts
index 3039d43f..4d26c655 100644
--- a/packages/server/src/setup/traefik-setup.ts
+++ b/packages/server/src/setup/traefik-setup.ts
@@ -139,6 +139,8 @@ export const initializeTraefik = async ({
const newContainer = docker.getContainer(containerName);
await newContainer.start();
console.log("Traefik container started successfully after retry");
+ } else {
+ throw error;
}
}
} catch (error) {
From ba3645933f995b549112065bc7aa3c92bd98fb70 Mon Sep 17 00:00:00 2001
From: Khiet Tam Nguyen
Date: Tue, 20 May 2025 16:11:48 +1000
Subject: [PATCH 31/55] feat: added SPA option for static sites
---
.../dashboard/application/build/show.tsx | 34 +-
.../drizzle/0092_stiff_the_watchers.sql | 1 +
apps/dokploy/drizzle/meta/0092_snapshot.json | 5717 ++++++++++++++++
apps/dokploy/drizzle/meta/_journal.json | 7 +
.../dokploy/server/api/routers/application.ts | 1 +
packages/server/src/db/schema/application.ts | 4 +-
packages/server/src/utils/builders/static.ts | 43 +-
pnpm-lock.yaml | 6084 ++++++++---------
8 files changed, 8493 insertions(+), 3398 deletions(-)
create mode 100644 apps/dokploy/drizzle/0092_stiff_the_watchers.sql
create mode 100644 apps/dokploy/drizzle/meta/0092_snapshot.json
diff --git a/apps/dokploy/components/dashboard/application/build/show.tsx b/apps/dokploy/components/dashboard/application/build/show.tsx
index 9535a318..fbefab6d 100644
--- a/apps/dokploy/components/dashboard/application/build/show.tsx
+++ b/apps/dokploy/components/dashboard/application/build/show.tsx
@@ -12,6 +12,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
+import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -63,10 +64,11 @@ const mySchema = z.discriminatedUnion("buildType", [
publishDirectory: z.string().optional(),
}),
z.object({
- buildType: z.literal(BuildType.static),
+ buildType: z.literal(BuildType.railpack),
}),
z.object({
- buildType: z.literal(BuildType.railpack),
+ buildType: z.literal(BuildType.static),
+ isStaticSpa: z.boolean().default(false),
}),
]);
@@ -83,6 +85,7 @@ interface ApplicationData {
dockerBuildStage?: string | null;
herokuVersion?: string | null;
publishDirectory?: string | null;
+ isStaticSpa?: boolean | null;
}
function isValidBuildType(value: string): value is BuildType {
@@ -115,6 +118,7 @@ const resetData = (data: ApplicationData): AddTemplate => {
case BuildType.static:
return {
buildType: BuildType.static,
+ isStaticSpa: data.isStaticSpa ?? false,
};
case BuildType.railpack:
return {
@@ -174,6 +178,8 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
data.buildType === BuildType.heroku_buildpacks
? data.herokuVersion
: null,
+ isStaticSpa:
+ data.buildType === BuildType.static ? data.isStaticSpa : null,
})
.then(async () => {
toast.success("Build type saved");
@@ -364,6 +370,30 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
)}
/>
)}
+ {buildType === BuildType.static && (
+ (
+
+
+
+
+
+ Single Page Application (SPA)
+
+
+
+
+
+ )}
+ />
+ )}