diff --git a/apps/dokploy/components/dashboard/application/deployments/kill-build.tsx b/apps/dokploy/components/dashboard/application/deployments/kill-build.tsx
new file mode 100644
index 000000000..784534dd6
--- /dev/null
+++ b/apps/dokploy/components/dashboard/application/deployments/kill-build.tsx
@@ -0,0 +1,65 @@
+import { Scissors } 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 KillBuild = ({ id, type }: Props) => {
+ const { mutateAsync, isLoading } =
+ type === "application"
+ ? api.application.killBuild.useMutation()
+ : api.compose.killBuild.useMutation();
+
+ return (
+
+
+
+
+
+
+ Are you sure to kill the build?
+
+ This will kill the build process
+
+
+
+ Cancel
+ {
+ await mutateAsync({
+ applicationId: id || "",
+ composeId: id || "",
+ })
+ .then(() => {
+ toast.success("Build killed successfully");
+ })
+ .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 1885ffc3a..7f3bc82b4 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 { KillBuild } from "./kill-build";
import { RefreshToken } from "./refresh-token";
import { ShowDeployment } from "./show-deployment";
@@ -143,6 +144,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 006d024c4..c713fd7eb 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -58,7 +58,11 @@ import {
applications,
} from "@/server/db/schema";
import type { DeploymentJob } from "@/server/queues/queue-types";
-import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup";
+import {
+ cleanQueuesByApplication,
+ killDockerBuild,
+ myQueue,
+} from "@/server/queues/queueSetup";
import { cancelDeployment, deploy } from "@/server/utils/deploy";
import { uploadFileSchema } from "@/utils/schema";
@@ -725,7 +729,21 @@ export const applicationRouter = createTRPCRouter({
}
await cleanQueuesByApplication(input.applicationId);
}),
-
+ killBuild: 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 kill this build",
+ });
+ }
+ await killDockerBuild("application", application.serverId);
+ }),
readTraefikConfig: protectedProcedure
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index 026b6e8ad..e233dc6ca 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -59,7 +59,11 @@ import {
compose as composeTable,
} from "@/server/db/schema";
import type { DeploymentJob } from "@/server/queues/queue-types";
-import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
+import {
+ cleanQueuesByCompose,
+ killDockerBuild,
+ myQueue,
+} from "@/server/queues/queueSetup";
import { cancelDeployment, deploy } from "@/server/utils/deploy";
import { generatePassword } from "@/templates/utils";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
@@ -248,6 +252,21 @@ export const composeRouter = createTRPCRouter({
await cleanQueuesByCompose(input.composeId);
return { success: true, message: "Queues cleaned successfully" };
}),
+ killBuild: 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 kill this build",
+ });
+ }
+ await killDockerBuild("compose", compose.serverId);
+ }),
loadServices: protectedProcedure
.input(apiFetchServices)
diff --git a/apps/dokploy/server/queues/queueSetup.ts b/apps/dokploy/server/queues/queueSetup.ts
index 1577273c8..351f5d1c0 100644
--- a/apps/dokploy/server/queues/queueSetup.ts
+++ b/apps/dokploy/server/queues/queueSetup.ts
@@ -1,3 +1,7 @@
+import {
+ execAsync,
+ execAsyncRemote,
+} from "@dokploy/server/utils/process/execAsync";
import { Queue } from "bullmq";
import { redisConfig } from "./redis-connection";
@@ -41,4 +45,31 @@ export const cleanQueuesByCompose = async (composeId: string) => {
}
};
+export const killDockerBuild = async (
+ type: "application" | "compose",
+ serverId: string | null,
+) => {
+ try {
+ if (type === "application") {
+ const command = `pkill -2 -f "docker build"`;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } else if (type === "compose") {
+ const command = `pkill -2 -f "docker compose"`;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ }
+};
+
export { myQueue };