+ {(type === "application" || type === "compose") && (
+
+ )}
{(type === "application" || type === "compose") && (
)}
@@ -252,6 +259,8 @@ export const ShowDeployments = ({
const isExpanded = expandedDescriptions.has(
deployment.deploymentId,
);
+ const canDelete =
+ deployment.status === "done" || deployment.status === "error";
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/application.ts b/apps/dokploy/server/api/routers/application.ts
index 9240a1cb3..391191ce3 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -1,6 +1,7 @@
import {
addNewService,
checkServiceAccess,
+ clearOldDeployments,
createApplication,
deleteAllMiddlewares,
findApplicationById,
@@ -746,6 +747,23 @@ 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",
+ });
+ }
+ await clearOldDeployments(application.appName, application.serverId);
+ return true;
+ }),
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 01f9c8b04..665b6770b 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,
+ clearOldDeployments,
cloneCompose,
createCommand,
createCompose,
@@ -263,6 +264,23 @@ 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",
+ });
+ }
+ await clearOldDeployments(compose.appName, compose.serverId);
+ return true;
+ }),
killBuild: protectedProcedure
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
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);
+ }),
});
diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts
index 6244ec8eb..24e1590a9 100644
--- a/packages/server/src/services/deployment.ts
+++ b/packages/server/src/services/deployment.ts
@@ -13,7 +13,10 @@ import {
deployments,
} from "@dokploy/server/db/schema";
import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory";
-import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
+import {
+ execAsync,
+ execAsyncRemote,
+} from "@dokploy/server/utils/process/execAsync";
import { TRPCError } from "@trpc/server";
import { format } from "date-fns";
import { desc, eq } from "drizzle-orm";
@@ -554,8 +557,25 @@ export const removeDeployment = async (deploymentId: string) => {
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";
@@ -831,3 +851,19 @@ export const findAllDeploymentsByServerId = async (serverId: string) => {
});
return deploymentsList;
};
+
+export const clearOldDeployments = async (
+ appName: string,
+ serverId: string | null,
+) => {
+ 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);
+ }
+};