Skip to content

Commit 3d48b25

Browse files
authored
Merge pull request #4065 from Dokploy/2779-implement-removing-unsuedexited-containers
feat(docker): implement container removal functionality
2 parents 3846e41 + b7e30d7 commit 3d48b25

File tree

4 files changed

+113
-0
lines changed

4 files changed

+113
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { toast } from "sonner";
2+
import {
3+
AlertDialog,
4+
AlertDialogAction,
5+
AlertDialogCancel,
6+
AlertDialogContent,
7+
AlertDialogDescription,
8+
AlertDialogFooter,
9+
AlertDialogHeader,
10+
AlertDialogTitle,
11+
AlertDialogTrigger,
12+
} from "@/components/ui/alert-dialog";
13+
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
14+
import { api } from "@/utils/api";
15+
16+
interface Props {
17+
containerId: string;
18+
serverId?: string;
19+
}
20+
21+
export const RemoveContainerDialog = ({ containerId, serverId }: Props) => {
22+
const utils = api.useUtils();
23+
const { mutateAsync, isPending } = api.docker.removeContainer.useMutation();
24+
25+
return (
26+
<AlertDialog>
27+
<AlertDialogTrigger asChild>
28+
<DropdownMenuItem
29+
className="w-full cursor-pointer text-red-500 hover:!text-red-600"
30+
onSelect={(e) => e.preventDefault()}
31+
>
32+
Remove Container
33+
</DropdownMenuItem>
34+
</AlertDialogTrigger>
35+
<AlertDialogContent>
36+
<AlertDialogHeader>
37+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
38+
<AlertDialogDescription>
39+
This will permanently remove the container{" "}
40+
<span className="font-semibold">{containerId}</span>. If the
41+
container is running, it will be forcefully stopped and removed.
42+
This action cannot be undone.
43+
</AlertDialogDescription>
44+
</AlertDialogHeader>
45+
<AlertDialogFooter>
46+
<AlertDialogCancel>Cancel</AlertDialogCancel>
47+
<AlertDialogAction
48+
disabled={isPending}
49+
onClick={async () => {
50+
await mutateAsync({ containerId, serverId })
51+
.then(async () => {
52+
toast.success("Container removed successfully");
53+
await utils.docker.getContainers.invalidate();
54+
})
55+
.catch((err) => {
56+
toast.error(err.message);
57+
});
58+
}}
59+
>
60+
Confirm
61+
</AlertDialogAction>
62+
</AlertDialogFooter>
63+
</AlertDialogContent>
64+
</AlertDialog>
65+
);
66+
};

apps/dokploy/components/dashboard/docker/show/colums.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "@/components/ui/dropdown-menu";
1111
import { ShowContainerConfig } from "../config/show-container-config";
1212
import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs";
13+
import { RemoveContainerDialog } from "../remove/remove-container";
1314
import { DockerTerminalModal } from "../terminal/docker-terminal-modal";
1415
import type { Container } from "./show-containers";
1516

@@ -127,6 +128,10 @@ export const columns: ColumnDef<Container>[] = [
127128
>
128129
Terminal
129130
</DockerTerminalModal>
131+
<RemoveContainerDialog
132+
containerId={container.containerId}
133+
serverId={container.serverId}
134+
/>
130135
</DropdownMenuContent>
131136
</DropdownMenu>
132137
);

apps/dokploy/server/api/routers/docker.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
containerRemove,
23
containerRestart,
34
findServerById,
45
getConfig,
@@ -52,6 +53,32 @@ export const dockerRouter = createTRPCRouter({
5253
return result;
5354
}),
5455

56+
removeContainer: withPermission("docker", "read")
57+
.input(
58+
z.object({
59+
containerId: z
60+
.string()
61+
.min(1)
62+
.regex(containerIdRegex, "Invalid container id."),
63+
serverId: z.string().optional(),
64+
}),
65+
)
66+
.mutation(async ({ input, ctx }) => {
67+
if (input.serverId) {
68+
const server = await findServerById(input.serverId);
69+
if (server.organizationId !== ctx.session?.activeOrganizationId) {
70+
throw new TRPCError({ code: "UNAUTHORIZED" });
71+
}
72+
}
73+
await containerRemove(input.containerId, input.serverId);
74+
await audit(ctx, {
75+
action: "delete",
76+
resourceType: "docker",
77+
resourceId: input.containerId,
78+
resourceName: input.containerId,
79+
});
80+
}),
81+
5582
getConfig: withPermission("docker", "read")
5683
.input(
5784
z.object({

packages/server/src/services/docker.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,21 @@ export const containerRestart = async (containerId: string) => {
371371
} catch {}
372372
};
373373

374+
export const containerRemove = async (
375+
containerId: string,
376+
serverId?: string,
377+
) => {
378+
const command = `docker rm -f ${containerId}`;
379+
const { stderr } = serverId
380+
? await execAsyncRemote(serverId, command)
381+
: await execAsync(command);
382+
383+
if (stderr) {
384+
console.error(`Error: ${stderr}`);
385+
throw new Error(stderr);
386+
}
387+
};
388+
374389
export const getSwarmNodes = async (serverId?: string) => {
375390
try {
376391
let stdout = "";

0 commit comments

Comments
 (0)