From 33fb21bfe1a1311b504d43ccfed69ab294fe074d Mon Sep 17 00:00:00 2001 From: Marc Fernandez Date: Sun, 7 Dec 2025 12:39:31 +0100 Subject: [PATCH 1/7] feat: add ability to delete old deployments --- .../application/deployments/cancel-queues.tsx | 4 +- .../deployments/clear-deployments.tsx | 78 +++++++++++++ .../deployments/show-deployments.tsx | 4 + .../dokploy/server/api/routers/application.ts | 21 ++++ apps/dokploy/server/api/routers/compose.ts | 21 ++++ packages/server/src/services/deployment.ts | 108 ++++++++++++++++++ 6 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx diff --git a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx index e957a496c..6e19767b7 100644 --- a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx @@ -1,4 +1,4 @@ -import { Paintbrush } from "lucide-react"; +import { Ban } from "lucide-react"; import { toast } from "sonner"; import { AlertDialog, @@ -35,7 +35,7 @@ export const CancelQueues = ({ id, type }: Props) => { diff --git a/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx new file mode 100644 index 000000000..d0f695ddb --- /dev/null +++ b/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx @@ -0,0 +1,78 @@ +import { Paintbrush } from "lucide-react"; +import { toast } from "sonner"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { api } from "@/utils/api"; + +interface Props { + id: string; + type: "application" | "compose"; +} + +export const ClearDeployments = ({ id, type }: Props) => { + const utils = api.useUtils(); + const { mutateAsync, isLoading } = + type === "application" + ? api.application.clearDeployments.useMutation() + : api.compose.clearDeployments.useMutation(); + const { data: isCloud } = api.settings.isCloud.useQuery(); + + if (isCloud) { + return null; + } + + return ( + + + + + + + + Are you sure you want to clear old deployments? + + + This will delete all old deployment records and logs, keeping only the active deployment (the most recent successful one). + + + + Cancel + { + await mutateAsync({ + applicationId: id || "", + composeId: id || "", + }) + .then(async (result) => { + toast.success(`${result.deletedCount} old deployments cleared successfully`); + // Invalidate deployment queries to refresh the list + await utils.deployment.allByType.invalidate({ + id, + type, + }); + }) + .catch((err) => { + toast.error(err.message); + }); + }} + > + Confirm + + + + + ); +}; diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index cfe747d27..159b89442 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -25,6 +25,7 @@ import { import { api, type RouterOutputs } from "@/utils/api"; import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings"; import { CancelQueues } from "./cancel-queues"; +import { ClearDeployments } from "./clear-deployments"; import { KillBuild } from "./kill-build"; import { RefreshToken } from "./refresh-token"; import { ShowDeployment } from "./show-deployment"; @@ -144,6 +145,9 @@ export const ShowDeployments = ({
+ {(type === "application" || type === "compose") && ( + + )} {(type === "application" || type === "compose") && ( )} diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index c0666fcc7..71d5dadb3 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -1,6 +1,7 @@ import { addNewService, checkServiceAccess, + clearOldDeploymentsByApplicationId, createApplication, deleteAllMiddlewares, findApplicationById, @@ -734,6 +735,26 @@ export const applicationRouter = createTRPCRouter({ } await cleanQueuesByApplication(input.applicationId); }), + clearDeployments: protectedProcedure + .input(apiFindOneApplication) + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if ( + application.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to clear deployments for this application", + }); + } + const result = await clearOldDeploymentsByApplicationId(input.applicationId); + return { + success: true, + message: `${result.deletedCount} old deployments cleared successfully`, + deletedCount: result.deletedCount, + }; + }), killBuild: protectedProcedure .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 9354988a8..2b548e1f0 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -2,6 +2,7 @@ import { addDomainToCompose, addNewService, checkServiceAccess, + clearOldDeploymentsByComposeId, cloneCompose, createCommand, createCompose, @@ -252,6 +253,26 @@ export const composeRouter = createTRPCRouter({ await cleanQueuesByCompose(input.composeId); return { success: true, message: "Queues cleaned successfully" }; }), + clearDeployments: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if ( + compose.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to clear deployments for this compose", + }); + } + const result = await clearOldDeploymentsByComposeId(input.composeId); + return { + success: true, + message: `${result.deletedCount} old deployments cleared successfully`, + deletedCount: result.deletedCount, + }; + }), killBuild: protectedProcedure .input(apiFindCompose) .mutation(async ({ input, ctx }) => { diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts index 6244ec8eb..1ba477cf0 100644 --- a/packages/server/src/services/deployment.ts +++ b/packages/server/src/services/deployment.ts @@ -831,3 +831,111 @@ export const findAllDeploymentsByServerId = async (serverId: string) => { }); return deploymentsList; }; + +export const clearOldDeploymentsByApplicationId = async ( + applicationId: string, +) => { + // Get all deployments ordered by creation date (newest first) + const deploymentsList = await db.query.deployments.findMany({ + where: eq(deployments.applicationId, applicationId), + orderBy: desc(deployments.createdAt), + }); + + // Find the most recent successful deployment (status "done") + const activeDeployment = deploymentsList.find( + (deployment) => deployment.status === "done", + ); + + // If there's an active deployment, keep it and remove all others + // If there's no active deployment, keep the most recent one and remove the rest + let deploymentsToKeep: string[] = []; + + if (activeDeployment) { + deploymentsToKeep.push(activeDeployment.deploymentId); + } else if (deploymentsList.length > 0) { + // Keep the most recent deployment even if it's not "done" + deploymentsToKeep.push(deploymentsList[0]!.deploymentId); + } + + const deploymentsToDelete = deploymentsList.filter( + (deployment) => !deploymentsToKeep.includes(deployment.deploymentId), + ); + + // Delete old deployments and their log files + for (const deployment of deploymentsToDelete) { + if (deployment.rollbackId) { + await removeRollbackById(deployment.rollbackId); + } + + // Remove log file if it exists + const logPath = deployment.logPath; + if (logPath && logPath !== "." && existsSync(logPath)) { + try { + await fsPromises.unlink(logPath); + } catch (error) { + console.error(`Error removing log file ${logPath}:`, error); + } + } + + // Delete deployment from database + await removeDeployment(deployment.deploymentId); + } + + return { + deletedCount: deploymentsToDelete.length, + keptDeployment: deploymentsToKeep[0] || null, + }; +}; + +export const clearOldDeploymentsByComposeId = async (composeId: string) => { + // Get all deployments ordered by creation date (newest first) + const deploymentsList = await db.query.deployments.findMany({ + where: eq(deployments.composeId, composeId), + orderBy: desc(deployments.createdAt), + }); + + // Find the most recent successful deployment (status "done") + const activeDeployment = deploymentsList.find( + (deployment) => deployment.status === "done", + ); + + // If there's an active deployment, keep it and remove all others + // If there's no active deployment, keep the most recent one and remove the rest + let deploymentsToKeep: string[] = []; + + if (activeDeployment) { + deploymentsToKeep.push(activeDeployment.deploymentId); + } else if (deploymentsList.length > 0) { + // Keep the most recent deployment even if it's not "done" + deploymentsToKeep.push(deploymentsList[0]!.deploymentId); + } + + const deploymentsToDelete = deploymentsList.filter( + (deployment) => !deploymentsToKeep.includes(deployment.deploymentId), + ); + + // Delete old deployments and their log files + for (const deployment of deploymentsToDelete) { + if (deployment.rollbackId) { + await removeRollbackById(deployment.rollbackId); + } + + // Remove log file if it exists + const logPath = deployment.logPath; + if (logPath && logPath !== "." && existsSync(logPath)) { + try { + await fsPromises.unlink(logPath); + } catch (error) { + console.error(`Error removing log file ${logPath}:`, error); + } + } + + // Delete deployment from database + await removeDeployment(deployment.deploymentId); + } + + return { + deletedCount: deploymentsToDelete.length, + keptDeployment: deploymentsToKeep[0] || null, + }; +}; From 95dd9ddeb61f767e1b8b50f1a9c61bd2eb41355b Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 02:30:23 +0000 Subject: [PATCH 2/7] [autofix.ci] apply automated fixes --- .../deployments/clear-deployments.tsx | 131 +++++++++--------- .../dokploy/server/api/routers/application.ts | 7 +- apps/dokploy/server/api/routers/compose.ts | 3 +- packages/server/src/services/deployment.ts | 12 +- 4 files changed, 80 insertions(+), 73 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx index d0f695ddb..70f9d2554 100644 --- a/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx @@ -1,78 +1,81 @@ import { Paintbrush } from "lucide-react"; import { toast } from "sonner"; import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { api } from "@/utils/api"; interface Props { - id: string; - type: "application" | "compose"; + id: string; + type: "application" | "compose"; } export const ClearDeployments = ({ id, type }: Props) => { - const utils = api.useUtils(); - const { mutateAsync, isLoading } = - type === "application" - ? api.application.clearDeployments.useMutation() - : api.compose.clearDeployments.useMutation(); - const { data: isCloud } = api.settings.isCloud.useQuery(); + const utils = api.useUtils(); + const { mutateAsync, isLoading } = + type === "application" + ? api.application.clearDeployments.useMutation() + : api.compose.clearDeployments.useMutation(); + const { data: isCloud } = api.settings.isCloud.useQuery(); - if (isCloud) { - return null; - } + if (isCloud) { + return null; + } - return ( - - - - - - - - Are you sure you want to clear old deployments? - - - This will delete all old deployment records and logs, keeping only the active deployment (the most recent successful one). - - - - Cancel - { - await mutateAsync({ - applicationId: id || "", - composeId: id || "", - }) - .then(async (result) => { - toast.success(`${result.deletedCount} old deployments cleared successfully`); - // Invalidate deployment queries to refresh the list - await utils.deployment.allByType.invalidate({ - id, - type, - }); - }) - .catch((err) => { - toast.error(err.message); - }); - }} - > - Confirm - - - - - ); + return ( + + + + + + + + Are you sure you want to clear old deployments? + + + This will delete all old deployment records and logs, keeping only + the active deployment (the most recent successful one). + + + + Cancel + { + await mutateAsync({ + applicationId: id || "", + composeId: id || "", + }) + .then(async (result) => { + toast.success( + `${result.deletedCount} old deployments cleared successfully`, + ); + // Invalidate deployment queries to refresh the list + await utils.deployment.allByType.invalidate({ + id, + type, + }); + }) + .catch((err) => { + toast.error(err.message); + }); + }} + > + Confirm + + + + + ); }; diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 71d5dadb3..1cd659644 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -745,10 +745,13 @@ export const applicationRouter = createTRPCRouter({ ) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to clear deployments for this application", + message: + "You are not authorized to clear deployments for this application", }); } - const result = await clearOldDeploymentsByApplicationId(input.applicationId); + const result = await clearOldDeploymentsByApplicationId( + input.applicationId, + ); return { success: true, message: `${result.deletedCount} old deployments cleared successfully`, diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 2b548e1f0..7e5ccf52e 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -263,7 +263,8 @@ export const composeRouter = createTRPCRouter({ ) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "You are not authorized to clear deployments for this compose", + message: + "You are not authorized to clear deployments for this compose", }); } const result = await clearOldDeploymentsByComposeId(input.composeId); diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts index 1ba477cf0..279a089aa 100644 --- a/packages/server/src/services/deployment.ts +++ b/packages/server/src/services/deployment.ts @@ -849,7 +849,7 @@ export const clearOldDeploymentsByApplicationId = async ( // If there's an active deployment, keep it and remove all others // If there's no active deployment, keep the most recent one and remove the rest let deploymentsToKeep: string[] = []; - + if (activeDeployment) { deploymentsToKeep.push(activeDeployment.deploymentId); } else if (deploymentsList.length > 0) { @@ -866,7 +866,7 @@ export const clearOldDeploymentsByApplicationId = async ( if (deployment.rollbackId) { await removeRollbackById(deployment.rollbackId); } - + // Remove log file if it exists const logPath = deployment.logPath; if (logPath && logPath !== "." && existsSync(logPath)) { @@ -876,7 +876,7 @@ export const clearOldDeploymentsByApplicationId = async ( console.error(`Error removing log file ${logPath}:`, error); } } - + // Delete deployment from database await removeDeployment(deployment.deploymentId); } @@ -902,7 +902,7 @@ export const clearOldDeploymentsByComposeId = async (composeId: string) => { // If there's an active deployment, keep it and remove all others // If there's no active deployment, keep the most recent one and remove the rest let deploymentsToKeep: string[] = []; - + if (activeDeployment) { deploymentsToKeep.push(activeDeployment.deploymentId); } else if (deploymentsList.length > 0) { @@ -919,7 +919,7 @@ export const clearOldDeploymentsByComposeId = async (composeId: string) => { if (deployment.rollbackId) { await removeRollbackById(deployment.rollbackId); } - + // Remove log file if it exists const logPath = deployment.logPath; if (logPath && logPath !== "." && existsSync(logPath)) { @@ -929,7 +929,7 @@ export const clearOldDeploymentsByComposeId = async (composeId: string) => { console.error(`Error removing log file ${logPath}:`, error); } } - + // Delete deployment from database await removeDeployment(deployment.deploymentId); } From 2be938a695ae6297d97639a9a9644d0b4d21b4a0 Mon Sep 17 00:00:00 2001 From: Marc Fernandez Date: Sun, 21 Dec 2025 16:29:14 +0100 Subject: [PATCH 3/7] feat: add individual deployment deletion --- .../deployments/show-deployments.tsx | 40 ++++++++++++++++++- apps/dokploy/server/api/routers/deployment.ts | 11 +++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 159b89442..ffe35cd34 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -6,6 +6,7 @@ import { RefreshCcw, RocketIcon, Settings, + Trash2, } from "lucide-react"; import React, { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; @@ -78,6 +79,8 @@ export const ShowDeployments = ({ api.rollback.rollback.useMutation(); const { mutateAsync: killProcess, isLoading: isKillingProcess } = api.deployment.killProcess.useMutation(); + const { mutateAsync: removeDeployment, isLoading: isRemovingDeployment } = + api.deployment.removeDeployment.useMutation(); // Cancel deployment mutations const { @@ -256,7 +259,15 @@ export const ShowDeployments = ({ const isExpanded = expandedDescriptions.has( deployment.deploymentId, ); - + const lastSuccessfulDeployment = deployments?.find( + (d) => d.status === "done" + ); + const isLastSuccessfulDeployment = + lastSuccessfulDeployment?.deploymentId === deployment.deploymentId; + const canDelete = + deployments && + deployments.length > 1 && + !isLastSuccessfulDeployment; return (
+ {canDelete && ( + { + try { + await removeDeployment({ + deploymentId: deployment.deploymentId, + }); + toast.success("Deployment deleted successfully"); + } catch (error) { + toast.error("Error deleting deployment"); + } + }} + > + + + )} + {deployment?.rollback && deployment.status === "done" && type === "application" && ( diff --git a/apps/dokploy/server/api/routers/deployment.ts b/apps/dokploy/server/api/routers/deployment.ts index 9004a0a05..d9fab404f 100644 --- a/apps/dokploy/server/api/routers/deployment.ts +++ b/apps/dokploy/server/api/routers/deployment.ts @@ -8,6 +8,7 @@ import { findComposeById, findDeploymentById, findServerById, + removeDeployment, updateDeploymentStatus, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; @@ -107,4 +108,14 @@ export const deploymentRouter = createTRPCRouter({ await updateDeploymentStatus(deployment.deploymentId, "error"); }), + + removeDeployment: protectedProcedure + .input( + z.object({ + deploymentId: z.string().min(1), + }), + ) + .mutation(async ({ input }) => { + return await removeDeployment(input.deploymentId); + }), }); From 2be92d20bb43fd78f164bf39865069548441c444 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:15:41 +0000 Subject: [PATCH 4/7] [autofix.ci] apply automated fixes --- .../application/deployments/show-deployments.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index ffe35cd34..294772228 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -260,13 +260,14 @@ export const ShowDeployments = ({ deployment.deploymentId, ); const lastSuccessfulDeployment = deployments?.find( - (d) => d.status === "done" + (d) => d.status === "done", ); - const isLastSuccessfulDeployment = - lastSuccessfulDeployment?.deploymentId === deployment.deploymentId; - const canDelete = - deployments && - deployments.length > 1 && + const isLastSuccessfulDeployment = + lastSuccessfulDeployment?.deploymentId === + deployment.deploymentId; + const canDelete = + deployments && + deployments.length > 1 && !isLastSuccessfulDeployment; return (
Date: Mon, 16 Feb 2026 22:12:56 -0600 Subject: [PATCH 5/7] feat(deployments): enhance deployment deletion logic and improve error handling - Updated the deployment deletion process to include error handling for non-existent deployments. - Refactored the command execution to handle both remote and local execution based on server availability. - Simplified the logic for determining deletable deployments in the ShowDeployments component. --- .../deployments/show-deployments.tsx | 11 ++----- packages/server/src/services/deployment.ts | 30 +++++++++++++++---- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 294772228..3f674aa3c 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -259,16 +259,9 @@ export const ShowDeployments = ({ const isExpanded = expandedDescriptions.has( deployment.deploymentId, ); - const lastSuccessfulDeployment = deployments?.find( - (d) => d.status === "done", - ); - const isLastSuccessfulDeployment = - lastSuccessfulDeployment?.deploymentId === - deployment.deploymentId; const canDelete = - deployments && - deployments.length > 1 && - !isLastSuccessfulDeployment; + deployment.status === "done" || deployment.status === "error"; + return (
{ const deployment = await db .delete(deployments) .where(eq(deployments.deploymentId, deploymentId)) - .returning(); - return deployment[0]; + .returning() + .then((result) => result[0]); + + if (!deployment) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Deployment not found", + }); + } + const command = ` + rm -f ${deployment.logPath}; + `; + if (deployment.serverId) { + await execAsyncRemote(deployment.serverId, command); + } else { + await execAsync(command); + } + + return deployment; } catch (error) { const message = error instanceof Error ? error.message : "Error creating the deployment"; @@ -848,7 +868,7 @@ export const clearOldDeploymentsByApplicationId = async ( // If there's an active deployment, keep it and remove all others // If there's no active deployment, keep the most recent one and remove the rest - let deploymentsToKeep: string[] = []; + const deploymentsToKeep: string[] = []; if (activeDeployment) { deploymentsToKeep.push(activeDeployment.deploymentId); @@ -901,7 +921,7 @@ export const clearOldDeploymentsByComposeId = async (composeId: string) => { // If there's an active deployment, keep it and remove all others // If there's no active deployment, keep the most recent one and remove the rest - let deploymentsToKeep: string[] = []; + const deploymentsToKeep: string[] = []; if (activeDeployment) { deploymentsToKeep.push(activeDeployment.deploymentId); From a511f4db4093b714d01f88538471228a2d5fb9aa Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 16 Feb 2026 22:15:39 -0600 Subject: [PATCH 6/7] refactor(deployments): unify old deployment clearing logic for applications and composes - Renamed and consolidated the functions for clearing old deployments to a single method, `clearOldDeployments`, which now accepts an ID and type (application or compose). - Updated the logic to filter deployments based on status and type, improving code maintainability and reducing redundancy. --- .../dokploy/server/api/routers/application.ts | 6 +- apps/dokploy/server/api/routers/compose.ts | 4 +- packages/server/src/services/deployment.ts | 106 ++---------------- 3 files changed, 14 insertions(+), 102 deletions(-) diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index f08f6d555..157a89630 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -1,7 +1,7 @@ import { addNewService, checkServiceAccess, - clearOldDeploymentsByApplicationId, + clearOldDeployments, createApplication, deleteAllMiddlewares, findApplicationById, @@ -761,9 +761,7 @@ export const applicationRouter = createTRPCRouter({ "You are not authorized to clear deployments for this application", }); } - const result = await clearOldDeploymentsByApplicationId( - input.applicationId, - ); + const result = await clearOldDeployments(input.applicationId); return { success: true, message: `${result.deletedCount} old deployments cleared successfully`, diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index fb5ba9b1c..de99e4d7f 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -2,7 +2,7 @@ import { addDomainToCompose, addNewService, checkServiceAccess, - clearOldDeploymentsByComposeId, + clearOldDeployments, cloneCompose, createCommand, createCompose, @@ -278,7 +278,7 @@ export const composeRouter = createTRPCRouter({ "You are not authorized to clear deployments for this compose", }); } - const result = await clearOldDeploymentsByComposeId(input.composeId); + const result = await clearOldDeployments(input.composeId, "compose"); return { success: true, message: `${result.deletedCount} old deployments cleared successfully`, diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts index 18f3d96aa..5af7ebc80 100644 --- a/packages/server/src/services/deployment.ts +++ b/packages/server/src/services/deployment.ts @@ -19,7 +19,7 @@ import { } from "@dokploy/server/utils/process/execAsync"; import { TRPCError } from "@trpc/server"; import { format } from "date-fns"; -import { desc, eq } from "drizzle-orm"; +import { and, desc, eq, or } from "drizzle-orm"; import { type Application, findApplicationById, @@ -852,110 +852,24 @@ export const findAllDeploymentsByServerId = async (serverId: string) => { return deploymentsList; }; -export const clearOldDeploymentsByApplicationId = async ( - applicationId: string, +export const clearOldDeployments = async ( + id: string, + type: "application" | "compose" = "application", ) => { // Get all deployments ordered by creation date (newest first) const deploymentsList = await db.query.deployments.findMany({ - where: eq(deployments.applicationId, applicationId), + where: and( + eq(deployments[`${type}Id`], id), + or(eq(deployments.status, "done"), eq(deployments.status, "error")), + ), orderBy: desc(deployments.createdAt), }); - // Find the most recent successful deployment (status "done") - const activeDeployment = deploymentsList.find( - (deployment) => deployment.status === "done", - ); - - // If there's an active deployment, keep it and remove all others - // If there's no active deployment, keep the most recent one and remove the rest - const deploymentsToKeep: string[] = []; - - if (activeDeployment) { - deploymentsToKeep.push(activeDeployment.deploymentId); - } else if (deploymentsList.length > 0) { - // Keep the most recent deployment even if it's not "done" - deploymentsToKeep.push(deploymentsList[0]!.deploymentId); - } - - const deploymentsToDelete = deploymentsList.filter( - (deployment) => !deploymentsToKeep.includes(deployment.deploymentId), - ); - - // Delete old deployments and their log files - for (const deployment of deploymentsToDelete) { - if (deployment.rollbackId) { - await removeRollbackById(deployment.rollbackId); - } - - // Remove log file if it exists - const logPath = deployment.logPath; - if (logPath && logPath !== "." && existsSync(logPath)) { - try { - await fsPromises.unlink(logPath); - } catch (error) { - console.error(`Error removing log file ${logPath}:`, error); - } - } - - // Delete deployment from database + for (const deployment of deploymentsList) { await removeDeployment(deployment.deploymentId); } return { - deletedCount: deploymentsToDelete.length, - keptDeployment: deploymentsToKeep[0] || null, - }; -}; - -export const clearOldDeploymentsByComposeId = async (composeId: string) => { - // Get all deployments ordered by creation date (newest first) - const deploymentsList = await db.query.deployments.findMany({ - where: eq(deployments.composeId, composeId), - orderBy: desc(deployments.createdAt), - }); - - // Find the most recent successful deployment (status "done") - const activeDeployment = deploymentsList.find( - (deployment) => deployment.status === "done", - ); - - // If there's an active deployment, keep it and remove all others - // If there's no active deployment, keep the most recent one and remove the rest - const deploymentsToKeep: string[] = []; - - if (activeDeployment) { - deploymentsToKeep.push(activeDeployment.deploymentId); - } else if (deploymentsList.length > 0) { - // Keep the most recent deployment even if it's not "done" - deploymentsToKeep.push(deploymentsList[0]!.deploymentId); - } - - const deploymentsToDelete = deploymentsList.filter( - (deployment) => !deploymentsToKeep.includes(deployment.deploymentId), - ); - - // Delete old deployments and their log files - for (const deployment of deploymentsToDelete) { - if (deployment.rollbackId) { - await removeRollbackById(deployment.rollbackId); - } - - // Remove log file if it exists - const logPath = deployment.logPath; - if (logPath && logPath !== "." && existsSync(logPath)) { - try { - await fsPromises.unlink(logPath); - } catch (error) { - console.error(`Error removing log file ${logPath}:`, error); - } - } - - // Delete deployment from database - await removeDeployment(deployment.deploymentId); - } - - return { - deletedCount: deploymentsToDelete.length, - keptDeployment: deploymentsToKeep[0] || null, + deletedCount: deploymentsList.length, }; }; From 76038f6db60a0b7688b19cdb882c6b36923f1dd7 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 16 Feb 2026 22:19:57 -0600 Subject: [PATCH 7/7] refactor(deployments): streamline deployment clearing process and remove cloud check - Removed the cloud check from the ClearDeployments component, simplifying the logic. - Updated the clearOldDeployments function to accept appName and serverId, enhancing its flexibility. - Adjusted the return values in the application and compose routers to return a boolean instead of a detailed message, improving consistency. --- .../deployments/clear-deployments.tsx | 14 ++------- .../dokploy/server/api/routers/application.ts | 8 ++--- apps/dokploy/server/api/routers/compose.ts | 8 ++--- packages/server/src/services/deployment.ts | 30 ++++++++----------- 4 files changed, 19 insertions(+), 41 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx index 70f9d2554..75862dfff 100644 --- a/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/clear-deployments.tsx @@ -25,11 +25,6 @@ export const ClearDeployments = ({ id, type }: Props) => { type === "application" ? api.application.clearDeployments.useMutation() : api.compose.clearDeployments.useMutation(); - const { data: isCloud } = api.settings.isCloud.useQuery(); - - if (isCloud) { - return null; - } return ( @@ -57,14 +52,11 @@ export const ClearDeployments = ({ id, type }: Props) => { applicationId: id || "", composeId: id || "", }) - .then(async (result) => { - toast.success( - `${result.deletedCount} old deployments cleared successfully`, - ); - // Invalidate deployment queries to refresh the list + .then(async () => { + toast.success("Old deployments cleared successfully"); await utils.deployment.allByType.invalidate({ id, - type, + type: type as "application" | "compose", }); }) .catch((err) => { diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 157a89630..391191ce3 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -761,12 +761,8 @@ export const applicationRouter = createTRPCRouter({ "You are not authorized to clear deployments for this application", }); } - const result = await clearOldDeployments(input.applicationId); - return { - success: true, - message: `${result.deletedCount} old deployments cleared successfully`, - deletedCount: result.deletedCount, - }; + await clearOldDeployments(application.appName, application.serverId); + return true; }), killBuild: protectedProcedure .input(apiFindOneApplication) diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index de99e4d7f..665b6770b 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -278,12 +278,8 @@ export const composeRouter = createTRPCRouter({ "You are not authorized to clear deployments for this compose", }); } - const result = await clearOldDeployments(input.composeId, "compose"); - return { - success: true, - message: `${result.deletedCount} old deployments cleared successfully`, - deletedCount: result.deletedCount, - }; + await clearOldDeployments(compose.appName, compose.serverId); + return true; }), killBuild: protectedProcedure .input(apiFindCompose) diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts index 5af7ebc80..24e1590a9 100644 --- a/packages/server/src/services/deployment.ts +++ b/packages/server/src/services/deployment.ts @@ -19,7 +19,7 @@ import { } from "@dokploy/server/utils/process/execAsync"; import { TRPCError } from "@trpc/server"; import { format } from "date-fns"; -import { and, desc, eq, or } from "drizzle-orm"; +import { desc, eq } from "drizzle-orm"; import { type Application, findApplicationById, @@ -853,23 +853,17 @@ export const findAllDeploymentsByServerId = async (serverId: string) => { }; export const clearOldDeployments = async ( - id: string, - type: "application" | "compose" = "application", + appName: string, + serverId: string | null, ) => { - // Get all deployments ordered by creation date (newest first) - const deploymentsList = await db.query.deployments.findMany({ - where: and( - eq(deployments[`${type}Id`], id), - or(eq(deployments.status, "done"), eq(deployments.status, "error")), - ), - orderBy: desc(deployments.createdAt), - }); - - for (const deployment of deploymentsList) { - await removeDeployment(deployment.deploymentId); + const { LOGS_PATH } = paths(!!serverId); + const folder = path.join(LOGS_PATH, appName); + const command = ` + rm -rf ${folder}; + `; + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command); } - - return { - deletedCount: deploymentsList.length, - }; };