diff --git a/apps/dokploy/components/dashboard/application/advanced/cluster/swarm-forms/health-check-form.tsx b/apps/dokploy/components/dashboard/application/advanced/cluster/swarm-forms/health-check-form.tsx index 0f640cc37..761f9a864 100644 --- a/apps/dokploy/components/dashboard/application/advanced/cluster/swarm-forms/health-check-form.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/cluster/swarm-forms/health-check-form.tsx @@ -200,7 +200,12 @@ export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => { Time between health checks (e.g., 10000000000 for 10 seconds) - + @@ -217,7 +222,12 @@ export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => { Maximum time to wait for health check response - + @@ -234,7 +244,12 @@ export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => { Initial grace period before health checks begin - + @@ -252,7 +267,12 @@ export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => { unhealthy - + diff --git a/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx index 44d73fb98..d2c158f91 100644 --- a/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx @@ -36,6 +36,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; @@ -53,6 +54,7 @@ const Schema = z.object({ message: "SSH Key is required", }), serverType: z.enum(["deploy", "build"]).default("deploy"), + enableDockerCleanup: z.boolean().default(true), }); type Schema = z.infer; @@ -90,6 +92,7 @@ export const HandleServers = ({ serverId, asButton = false }: Props) => { username: "root", sshKeyId: "", serverType: "deploy", + enableDockerCleanup: true, }, resolver: zodResolver(Schema), }); @@ -103,6 +106,7 @@ export const HandleServers = ({ serverId, asButton = false }: Props) => { username: data?.username || "root", sshKeyId: data?.sshKeyId || "", serverType: data?.serverType || "deploy", + enableDockerCleanup: data?.enableDockerCleanup ?? true, }); }, [form, form.reset, form.formState.isSubmitSuccessful, data]); @@ -119,6 +123,7 @@ export const HandleServers = ({ serverId, asButton = false }: Props) => { username: data.username || "root", sshKeyId: data.sshKeyId || "", serverType: data.serverType || "deploy", + enableDockerCleanup: data.enableDockerCleanup, serverId: serverId || "", }) .then(async (_data) => { @@ -418,6 +423,27 @@ export const HandleServers = ({ serverId, asButton = false }: Props) => { )} /> + ( + +
+ Enable Docker Cleanup + + Automatically prune unused Docker images daily. Keeps disk + usage in check on this remote server. + +
+ + + +
+ )} + /> diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts index 310363efd..56f851647 100644 --- a/apps/dokploy/server/api/routers/server.ts +++ b/apps/dokploy/server/api/routers/server.ts @@ -45,6 +45,7 @@ import { redis, server, } from "@/server/db/schema"; +import { applyDockerCleanupSchedule } from "@/server/utils/docker-cleanup"; export const serverRouter = createTRPCRouter({ create: withPermission("server", "create") @@ -63,6 +64,11 @@ export const serverRouter = createTRPCRouter({ input, ctx.session.activeOrganizationId, ); + await applyDockerCleanupSchedule( + project.serverId, + ctx.session.activeOrganizationId, + input.enableDockerCleanup, + ); await audit(ctx, { action: "create", resourceType: "server", @@ -456,6 +462,12 @@ export const serverRouter = createTRPCRouter({ ...input, }); + await applyDockerCleanupSchedule( + input.serverId, + ctx.session.activeOrganizationId, + input.enableDockerCleanup, + ); + await audit(ctx, { action: "update", resourceType: "server", diff --git a/apps/dokploy/server/utils/docker-cleanup.ts b/apps/dokploy/server/utils/docker-cleanup.ts new file mode 100644 index 000000000..46a4f8569 --- /dev/null +++ b/apps/dokploy/server/utils/docker-cleanup.ts @@ -0,0 +1,39 @@ +import { + CLEANUP_CRON_JOB, + cleanupAll, + IS_CLOUD, + sendDockerCleanupNotifications, +} from "@dokploy/server"; +import { scheduledJobs, scheduleJob } from "node-schedule"; +import { removeJob, schedule } from "./backup"; + +export const applyDockerCleanupSchedule = async ( + serverId: string, + organizationId: string, + enable: boolean, +) => { + if (enable) { + if (IS_CLOUD) { + await schedule({ + cronSchedule: CLEANUP_CRON_JOB, + serverId, + type: "server", + }); + } else { + scheduleJob(serverId, CLEANUP_CRON_JOB, async () => { + await cleanupAll(serverId); + await sendDockerCleanupNotifications(organizationId); + }); + } + } else { + if (IS_CLOUD) { + await removeJob({ + cronSchedule: CLEANUP_CRON_JOB, + serverId, + type: "server", + }); + } else { + scheduledJobs[serverId]?.cancel(); + } + } +}; diff --git a/packages/server/src/db/schema/server.ts b/packages/server/src/db/schema/server.ts index 4c8f1fc94..5a239f00d 100644 --- a/packages/server/src/db/schema/server.ts +++ b/packages/server/src/db/schema/server.ts @@ -147,8 +147,12 @@ export const apiCreateServer = createSchema username: true, sshKeyId: true, serverType: true, + enableDockerCleanup: true, }) - .required(); + .required() + .extend({ + enableDockerCleanup: z.boolean().default(true), + }); export const apiFindOneServer = z.object({ serverId: z.string().min(1), @@ -170,10 +174,12 @@ export const apiUpdateServer = createSchema username: true, sshKeyId: true, serverType: true, + enableDockerCleanup: true, }) .required() .extend({ command: z.string().optional(), + enableDockerCleanup: z.boolean().default(true), }); export const apiUpdateServerMonitoring = createSchema