diff --git a/apps/dokploy/pages/dashboard/settings/index.tsx b/apps/dokploy/pages/dashboard/settings/index.tsx
new file mode 100644
index 00000000..bf76607b
--- /dev/null
+++ b/apps/dokploy/pages/dashboard/settings/index.tsx
@@ -0,0 +1,220 @@
+import { DashboardLayout } from "@/components/layouts/dashboard-layout";
+
+import { AlertBlock } from "@/components/shared/alert-block";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { DialogFooter } from "@/components/ui/dialog";
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Switch } from "@/components/ui/switch";
+import { appRouter } from "@/server/api/root";
+import { api } from "@/utils/api";
+import { validateRequest } from "@dokploy/server";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { createServerSideHelpers } from "@trpc/react-query/server";
+import { Settings } from "lucide-react";
+import type { GetServerSidePropsContext } from "next";
+import React, { useEffect, type ReactElement } from "react";
+import { useForm } from "react-hook-form";
+import { toast } from "sonner";
+import superjson from "superjson";
+import { z } from "zod";
+
+const settings = z.object({
+ cleanCacheOnApplications: z.boolean(),
+ cleanCacheOnCompose: z.boolean(),
+ cleanCacheOnPreviews: z.boolean(),
+});
+
+type SettingsType = z.infer
;
+
+const Page = () => {
+ const { data, refetch } = api.admin.one.useQuery();
+ const { mutateAsync, isLoading, isError, error } =
+ api.admin.update.useMutation();
+ const form = useForm({
+ defaultValues: {
+ cleanCacheOnApplications: false,
+ cleanCacheOnCompose: false,
+ cleanCacheOnPreviews: false,
+ },
+ resolver: zodResolver(settings),
+ });
+ useEffect(() => {
+ form.reset({
+ cleanCacheOnApplications: data?.cleanupCacheApplications || false,
+ cleanCacheOnCompose: data?.cleanupCacheOnCompose || false,
+ cleanCacheOnPreviews: data?.cleanupCacheOnPreviews || false,
+ });
+ }, [form, form.reset, form.formState.isSubmitSuccessful, data]);
+
+ const onSubmit = async (values: SettingsType) => {
+ await mutateAsync({
+ cleanupCacheApplications: values.cleanCacheOnApplications,
+ cleanupCacheOnCompose: values.cleanCacheOnCompose,
+ cleanupCacheOnPreviews: values.cleanCacheOnPreviews,
+ })
+ .then(() => {
+ toast.success("Settings updated");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Something went wrong");
+ });
+ };
+ return (
+
+
+
+
+
+
+ Settings
+
+ Manage your Dokploy settings
+ {isError && {error?.message}}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Page;
+
+Page.getLayout = (page: ReactElement) => {
+ return {page};
+};
+export async function getServerSideProps(
+ ctx: GetServerSidePropsContext<{ serviceId: string }>,
+) {
+ const { req, res } = ctx;
+ const { user, session } = await validateRequest(ctx.req, ctx.res);
+ if (!user) {
+ return {
+ redirect: {
+ permanent: true,
+ destination: "/",
+ },
+ };
+ }
+ if (user.rol === "user") {
+ return {
+ redirect: {
+ permanent: true,
+ destination: "/dashboard/settings/profile",
+ },
+ };
+ }
+
+ const helpers = createServerSideHelpers({
+ router: appRouter,
+ ctx: {
+ req: req as any,
+ res: res as any,
+ db: null as any,
+ session: session,
+ user: user,
+ },
+ transformer: superjson,
+ });
+ await helpers.auth.get.prefetch();
+
+ return {
+ props: {
+ trpcState: helpers.dehydrate(),
+ },
+ };
+}
diff --git a/apps/dokploy/pages/dashboard/swarm.tsx b/apps/dokploy/pages/dashboard/swarm.tsx
index f40a0a83..3a8a60b2 100644
--- a/apps/dokploy/pages/dashboard/swarm.tsx
+++ b/apps/dokploy/pages/dashboard/swarm.tsx
@@ -8,11 +8,7 @@ import type { ReactElement } from "react";
import superjson from "superjson";
const Dashboard = () => {
- return (
- <>
-
- >
- );
+ return ;
};
export default Dashboard;
diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts
index 19acb9a3..9c2608cc 100644
--- a/apps/dokploy/server/api/routers/project.ts
+++ b/apps/dokploy/server/api/routers/project.ts
@@ -239,7 +239,10 @@ export const projectRouter = createTRPCRouter({
}
}),
});
-function buildServiceFilter(fieldName: AnyPgColumn, accessedServices: string[]) {
+function buildServiceFilter(
+ fieldName: AnyPgColumn,
+ accessedServices: string[],
+) {
return accessedServices.length > 0
? sql`${fieldName} IN (${sql.join(
accessedServices.map((serviceId) => sql`${serviceId}`),
diff --git a/apps/dokploy/styles/globals.css b/apps/dokploy/styles/globals.css
index 3910d69a..7b7977b9 100644
--- a/apps/dokploy/styles/globals.css
+++ b/apps/dokploy/styles/globals.css
@@ -101,7 +101,7 @@
* {
@apply border-border;
}
-
+
body {
@apply bg-background text-foreground;
}
@@ -110,16 +110,16 @@
::-webkit-scrollbar {
width: 0.3125rem;
}
-
+
::-webkit-scrollbar-track {
background: transparent;
}
-
+
::-webkit-scrollbar-thumb {
background: hsl(var(--border));
border-radius: 0.3125rem;
}
-
+
* {
scrollbar-width: thin;
scrollbar-color: hsl(var(--border)) transparent;
diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts
index 6238a19c..9531eb7a 100644
--- a/apps/dokploy/templates/templates.ts
+++ b/apps/dokploy/templates/templates.ts
@@ -1269,7 +1269,8 @@ export const templates: TemplateData[] = [
},
tags: ["cloud", "networking", "security", "tunnel"],
load: () => import("./cloudflared/index").then((m) => m.generate),
- },{
+ },
+ {
id: "couchdb",
name: "CouchDB",
version: "latest",
diff --git a/packages/server/src/db/schema/admin.ts b/packages/server/src/db/schema/admin.ts
index 222fb16c..e9c73bcc 100644
--- a/packages/server/src/db/schema/admin.ts
+++ b/packages/server/src/db/schema/admin.ts
@@ -31,6 +31,15 @@ export const admins = pgTable("admin", {
stripeCustomerId: text("stripeCustomerId"),
stripeSubscriptionId: text("stripeSubscriptionId"),
serversQuantity: integer("serversQuantity").notNull().default(0),
+ cleanupCacheApplications: boolean("cleanupCacheApplications")
+ .notNull()
+ .default(true),
+ cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
+ .notNull()
+ .default(false),
+ cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
+ .notNull()
+ .default(false),
});
export const adminsRelations = relations(admins, ({ one, many }) => ({
diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts
index 068e69b3..ccadebf7 100644
--- a/packages/server/src/services/application.ts
+++ b/packages/server/src/services/application.ts
@@ -40,7 +40,7 @@ import { createTraefikConfig } from "@dokploy/server/utils/traefik/application";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils";
-import { getDokployUrl } from "./admin";
+import { findAdminById, getDokployUrl } from "./admin";
import {
createDeployment,
createDeploymentPreview,
@@ -58,6 +58,7 @@ import {
updatePreviewDeployment,
} from "./preview-deployment";
import { validUniqueServerAppName } from "./project";
+import { cleanupFullDocker } from "./settings";
export type Application = typeof applications.$inferSelect;
export const createApplication = async (
@@ -213,7 +214,7 @@ export const deployApplication = async ({
applicationType: "application",
buildLink,
adminId: application.project.adminId,
- domains: application.domains
+ domains: application.domains,
});
} catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error");
@@ -229,6 +230,12 @@ export const deployApplication = async ({
});
throw error;
+ } finally {
+ const admin = await findAdminById(application.project.adminId);
+
+ if (admin.cleanupCacheApplications) {
+ await cleanupFullDocker(application?.serverId);
+ }
}
return true;
@@ -270,6 +277,12 @@ export const rebuildApplication = async ({
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error");
throw error;
+ } finally {
+ const admin = await findAdminById(application.project.adminId);
+
+ if (admin.cleanupCacheApplications) {
+ await cleanupFullDocker(application?.serverId);
+ }
}
return true;
@@ -333,7 +346,7 @@ export const deployRemoteApplication = async ({
applicationType: "application",
buildLink,
adminId: application.project.adminId,
- domains: application.domains
+ domains: application.domains,
});
} catch (error) {
// @ts-ignore
@@ -359,15 +372,13 @@ export const deployRemoteApplication = async ({
adminId: application.project.adminId,
});
- console.log(
- "Error on ",
- application.buildType,
- "/",
- application.sourceType,
- error,
- );
-
throw error;
+ } finally {
+ const admin = await findAdminById(application.project.adminId);
+
+ if (admin.cleanupCacheApplications) {
+ await cleanupFullDocker(application?.serverId);
+ }
}
return true;
@@ -475,6 +486,12 @@ export const deployPreviewApplication = async ({
previewStatus: "error",
});
throw error;
+ } finally {
+ const admin = await findAdminById(application.project.adminId);
+
+ if (admin.cleanupCacheOnPreviews) {
+ await cleanupFullDocker(application?.serverId);
+ }
}
return true;
@@ -587,6 +604,12 @@ export const deployRemotePreviewApplication = async ({
previewStatus: "error",
});
throw error;
+ } finally {
+ const admin = await findAdminById(application.project.adminId);
+
+ if (admin.cleanupCacheOnPreviews) {
+ await cleanupFullDocker(application?.serverId);
+ }
}
return true;
@@ -634,6 +657,12 @@ export const rebuildRemoteApplication = async ({
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error");
throw error;
+ } finally {
+ const admin = await findAdminById(application.project.adminId);
+
+ if (admin.cleanupCacheApplications) {
+ await cleanupFullDocker(application?.serverId);
+ }
}
return true;
diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts
index 8561dd37..7f6a5954 100644
--- a/packages/server/src/services/compose.ts
+++ b/packages/server/src/services/compose.ts
@@ -3,7 +3,6 @@ import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db";
import { type apiCreateCompose, compose } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
-import { generatePassword } from "@dokploy/server/templates/utils";
import {
buildCompose,
getBuildComposeCommand,
@@ -45,9 +44,10 @@ import {
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils";
-import { getDokployUrl } from "./admin";
+import { findAdminById, getDokployUrl } from "./admin";
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
import { validUniqueServerAppName } from "./project";
+import { cleanupFullDocker } from "./settings";
export type Compose = typeof compose.$inferSelect;
@@ -260,6 +260,11 @@ export const deployCompose = async ({
adminId: compose.project.adminId,
});
throw error;
+ } finally {
+ const admin = await findAdminById(compose.project.adminId);
+ if (admin.cleanupCacheOnCompose) {
+ await cleanupFullDocker(compose?.serverId);
+ }
}
};
@@ -296,6 +301,11 @@ export const rebuildCompose = async ({
composeStatus: "error",
});
throw error;
+ } finally {
+ const admin = await findAdminById(compose.project.adminId);
+ if (admin.cleanupCacheOnCompose) {
+ await cleanupFullDocker(compose?.serverId);
+ }
}
return true;
@@ -394,6 +404,11 @@ export const deployRemoteCompose = async ({
adminId: compose.project.adminId,
});
throw error;
+ } finally {
+ const admin = await findAdminById(compose.project.adminId);
+ if (admin.cleanupCacheOnCompose) {
+ await cleanupFullDocker(compose?.serverId);
+ }
}
};
@@ -438,6 +453,11 @@ export const rebuildRemoteCompose = async ({
composeStatus: "error",
});
throw error;
+ } finally {
+ const admin = await findAdminById(compose.project.adminId);
+ if (admin.cleanupCacheOnCompose) {
+ await cleanupFullDocker(compose?.serverId);
+ }
}
return true;
diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts
index 37f7b2ee..d22780c9 100644
--- a/packages/server/src/services/settings.ts
+++ b/packages/server/src/services/settings.ts
@@ -5,6 +5,7 @@ import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
+import { findAdminById } from "./admin";
// import packageInfo from "../../../package.json";
export interface IUpdateData {
@@ -213,3 +214,35 @@ echo "$json_output"
}
return result;
};
+
+export const cleanupFullDocker = async (serverId?: string | null) => {
+ const cleanupImages = "docker image prune --all --force";
+ const cleanupVolumes = "docker volume prune --all --force";
+ const cleanupContainers = "docker container prune --force";
+ const cleanupSystem = "docker system prune --all --force --volumes";
+ const cleanupBuilder = "docker builder prune --all --force";
+
+ try {
+ if (serverId) {
+ await execAsyncRemote(
+ serverId,
+ `
+ ${cleanupImages}
+ ${cleanupVolumes}
+ ${cleanupContainers}
+ ${cleanupSystem}
+ ${cleanupBuilder}
+ `,
+ );
+ }
+ await execAsync(`
+ ${cleanupImages}
+ ${cleanupVolumes}
+ ${cleanupContainers}
+ ${cleanupSystem}
+ ${cleanupBuilder}
+ `);
+ } catch (error) {
+ console.log(error);
+ }
+};
diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts
index c8a9849c..d8d9862c 100644
--- a/packages/server/src/services/user.ts
+++ b/packages/server/src/services/user.ts
@@ -73,7 +73,8 @@ export const canPerformCreationService = async (
userId: string,
projectId: string,
) => {
- const { accessedProjects, canCreateServices } = await findUserByAuthId(userId);
+ const { accessedProjects, canCreateServices } =
+ await findUserByAuthId(userId);
const haveAccessToProject = accessedProjects.includes(projectId);
if (canCreateServices && haveAccessToProject) {
@@ -101,7 +102,8 @@ export const canPeformDeleteService = async (
authId: string,
serviceId: string,
) => {
- const { accessedServices, canDeleteServices } = await findUserByAuthId(authId);
+ const { accessedServices, canDeleteServices } =
+ await findUserByAuthId(authId);
const haveAccessToService = accessedServices.includes(serviceId);
if (canDeleteServices && haveAccessToService) {
diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts
index 2ab2125c..95936652 100644
--- a/packages/server/src/utils/notifications/build-error.ts
+++ b/packages/server/src/utils/notifications/build-error.ts
@@ -2,8 +2,8 @@ import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed";
import { renderAsync } from "@react-email/components";
-import { and, eq } from "drizzle-orm";
import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
@@ -139,11 +139,11 @@ export const sendBuildErrorNotifications = async ({
},
],
];
-
+
await sendTelegramNotification(
telegram,
`⚠️ Build Failed\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}\n\nError:\n${errorMessage}`,
- inlineButton
+ inlineButton,
);
}
diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts
index 19b17811..960f7a6a 100644
--- a/packages/server/src/utils/notifications/build-success.ts
+++ b/packages/server/src/utils/notifications/build-success.ts
@@ -1,10 +1,10 @@
import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import BuildSuccessEmail from "@dokploy/server/emails/emails/build-success";
-import { Domain } from "@dokploy/server/services/domain";
+import type { Domain } from "@dokploy/server/services/domain";
import { renderAsync } from "@react-email/components";
-import { and, eq } from "drizzle-orm";
import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
@@ -28,7 +28,7 @@ export const sendBuildSuccessNotifications = async ({
applicationType,
buildLink,
adminId,
- domains
+ domains,
}: Props) => {
const date = new Date();
const unixDate = ~~(Number(date) / 1000);
@@ -128,9 +128,10 @@ export const sendBuildSuccessNotifications = async ({
if (telegram) {
const chunkArray = (array: T[], chunkSize: number): T[][] =>
- Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) => array.slice(i * chunkSize, i * chunkSize + chunkSize)
- );
-
+ Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
+ array.slice(i * chunkSize, i * chunkSize + chunkSize),
+ );
+
const inlineButton = [
[
{
@@ -142,14 +143,14 @@ export const sendBuildSuccessNotifications = async ({
chunk.map((data) => ({
text: data.host,
url: `${data.https ? "https" : "http"}://${data.host}`,
- }))
+ })),
),
];
-
+
await sendTelegramNotification(
telegram,
- `✅ Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
- inlineButton
+ `✅ Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
+ inlineButton,
);
}
diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts
index 1460964d..0b1d61f7 100644
--- a/packages/server/src/utils/notifications/database-backup.ts
+++ b/packages/server/src/utils/notifications/database-backup.ts
@@ -3,8 +3,8 @@ import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup";
import { renderAsync } from "@react-email/components";
-import { and, eq } from "drizzle-orm";
import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
@@ -144,13 +144,15 @@ export const sendDatabaseBackupNotifications = async ({
if (telegram) {
const isError = type === "error" && errorMessage;
-
+
const statusEmoji = type === "success" ? "✅" : "❌";
const typeStatus = type === "success" ? "Successful" : "Failed";
- const errorMsg = isError ? `\n\nError:\n${errorMessage}` : "";
-
+ const errorMsg = isError
+ ? `\n\nError:\n${errorMessage}`
+ : "";
+
const messageText = `${statusEmoji} Database Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${databaseType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`;
-
+
await sendTelegramNotification(telegram, messageText);
}
diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts
index 05624e49..b60e3b0a 100644
--- a/packages/server/src/utils/notifications/docker-cleanup.ts
+++ b/packages/server/src/utils/notifications/docker-cleanup.ts
@@ -2,8 +2,8 @@ import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup";
import { renderAsync } from "@react-email/components";
-import { and, eq } from "drizzle-orm";
import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
@@ -96,7 +96,7 @@ export const sendDockerCleanupNotifications = async (
if (telegram) {
await sendTelegramNotification(
telegram,
- `✅ Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`
+ `✅ Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
);
}
diff --git a/packages/server/src/utils/notifications/dokploy-restart.ts b/packages/server/src/utils/notifications/dokploy-restart.ts
index 6debb7a7..5a156aff 100644
--- a/packages/server/src/utils/notifications/dokploy-restart.ts
+++ b/packages/server/src/utils/notifications/dokploy-restart.ts
@@ -2,6 +2,7 @@ import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import DokployRestartEmail from "@dokploy/server/emails/emails/dokploy-restart";
import { renderAsync } from "@react-email/components";
+import { format } from "date-fns";
import { eq } from "drizzle-orm";
import {
sendDiscordNotification,
@@ -10,7 +11,6 @@ import {
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
-import { format } from "date-fns";
export const sendDokployRestartNotifications = async () => {
const date = new Date();
@@ -80,7 +80,7 @@ export const sendDokployRestartNotifications = async () => {
if (telegram) {
await sendTelegramNotification(
telegram,
- `✅ Dokploy Server Restarted\n\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`
+ `✅ Dokploy Server Restarted\n\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
);
}
diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts
index ede46034..4f8bb1a5 100644
--- a/packages/server/src/utils/notifications/utils.ts
+++ b/packages/server/src/utils/notifications/utils.ts
@@ -59,7 +59,7 @@ export const sendTelegramNotification = async (
inlineButton?: {
text: string;
url: string;
- }[][]
+ }[][],
) => {
try {
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;