diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx
index 3cb18f98..a0f28dbb 100644
--- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx
+++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx
@@ -10,11 +10,20 @@ import {
CardTitle,
} from "@/components/ui/card";
import { type RouterOutputs, api } from "@/utils/api";
-import { Clock, Loader2, RocketIcon } from "lucide-react";
+import {
+ Clock,
+ Loader2,
+ RocketIcon,
+ Settings,
+ ArrowDownToLine,
+} from "lucide-react";
import React, { useEffect, useState } from "react";
import { CancelQueues } from "./cancel-queues";
import { RefreshToken } from "./refresh-token";
import { ShowDeployment } from "./show-deployment";
+import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
+import { DialogAction } from "@/components/shared/dialog-action";
+import { toast } from "sonner";
interface Props {
id: string;
@@ -57,6 +66,9 @@ export const ShowDeployments = ({
},
);
+ const { mutateAsync: rollback, isLoading: isRollingBack } =
+ api.rollback.rollback.useMutation();
+
const [url, setUrl] = React.useState("");
useEffect(() => {
setUrl(document.location.origin);
@@ -71,9 +83,18 @@ export const ShowDeployments = ({
See all the 10 last deployments for this {type}
- {(type === "application" || type === "compose") && (
-
- )}
+
+ {(type === "application" || type === "compose") && (
+
+ )}
+ {type === "application" && (
+
+
+
+ )}
+
{refreshToken && (
@@ -154,13 +175,43 @@ export const ShowDeployments = ({
)}
-
+
+
+
+ {deployment?.rollback && (
+
{
+ await rollback({
+ rollbackId: deployment.rollback.rollbackId,
+ })
+ .then(() => {
+ toast.success("Rollback initiated successfully");
+ })
+ .catch(() => {
+ toast.error("Error initiating rollback");
+ });
+ }}
+ >
+
+
+ )}
+
))}
diff --git a/apps/dokploy/drizzle/0093_funny_leper_queen.sql b/apps/dokploy/drizzle/0093_funny_leper_queen.sql
new file mode 100644
index 00000000..38b0bb5f
--- /dev/null
+++ b/apps/dokploy/drizzle/0093_funny_leper_queen.sql
@@ -0,0 +1,14 @@
+CREATE TABLE "rollback" (
+ "rollbackId" text PRIMARY KEY NOT NULL,
+ "env" text,
+ "deploymentId" text NOT NULL,
+ "version" serial NOT NULL,
+ "image" text,
+ "createdAt" text NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "application" ADD COLUMN "rollbackActive" boolean DEFAULT false;--> statement-breakpoint
+ALTER TABLE "application" ADD COLUMN "limitRollback" integer DEFAULT 5;--> statement-breakpoint
+ALTER TABLE "deployment" ADD COLUMN "rollbackId" text;--> statement-breakpoint
+ALTER TABLE "rollback" ADD CONSTRAINT "rollback_deploymentId_deployment_deploymentId_fk" FOREIGN KEY ("deploymentId") REFERENCES "public"."deployment"("deploymentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "deployment" ADD CONSTRAINT "deployment_rollbackId_rollback_rollbackId_fk" FOREIGN KEY ("rollbackId") REFERENCES "public"."rollback"("rollbackId") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/0093_yielding_typhoid_mary.sql b/apps/dokploy/drizzle/0093_yielding_typhoid_mary.sql
deleted file mode 100644
index a66b6de9..00000000
--- a/apps/dokploy/drizzle/0093_yielding_typhoid_mary.sql
+++ /dev/null
@@ -1,12 +0,0 @@
-CREATE TABLE "rollback" (
- "rollbackId" text PRIMARY KEY NOT NULL,
- "env" text,
- "applicationId" text NOT NULL,
- "version" serial NOT NULL,
- "image" text,
- "createdAt" text NOT NULL
-);
---> statement-breakpoint
-ALTER TABLE "application" ADD COLUMN "rollbackActive" boolean DEFAULT false;--> statement-breakpoint
-ALTER TABLE "application" ADD COLUMN "limitRollback" integer DEFAULT 5;--> statement-breakpoint
-ALTER TABLE "rollback" ADD CONSTRAINT "rollback_applicationId_application_applicationId_fk" FOREIGN KEY ("applicationId") REFERENCES "public"."application"("applicationId") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/0093_snapshot.json b/apps/dokploy/drizzle/meta/0093_snapshot.json
index 8f047f53..aeb870b5 100644
--- a/apps/dokploy/drizzle/meta/0093_snapshot.json
+++ b/apps/dokploy/drizzle/meta/0093_snapshot.json
@@ -1,5 +1,5 @@
{
- "id": "f8bab14b-cdab-492b-9fe3-b9fc9e56239d",
+ "id": "7a6a6383-d13c-421b-b186-d49ced92153a",
"prevId": "76eb8fe3-21c0-4544-962c-1ae18e8e6730",
"version": "7",
"dialect": "postgresql",
@@ -2184,6 +2184,12 @@
"type": "text",
"primaryKey": false,
"notNull": false
+ },
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
}
},
"indexes": {},
@@ -2265,6 +2271,19 @@
],
"onDelete": "cascade",
"onUpdate": "no action"
+ },
+ "deployment_rollbackId_rollback_rollbackId_fk": {
+ "name": "deployment_rollbackId_rollback_rollbackId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "rollback",
+ "columnsFrom": [
+ "rollbackId"
+ ],
+ "columnsTo": [
+ "rollbackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
@@ -5537,8 +5556,8 @@
"primaryKey": false,
"notNull": false
},
- "applicationId": {
- "name": "applicationId",
+ "deploymentId": {
+ "name": "deploymentId",
"type": "text",
"primaryKey": false,
"notNull": true
@@ -5564,15 +5583,15 @@
},
"indexes": {},
"foreignKeys": {
- "rollback_applicationId_application_applicationId_fk": {
- "name": "rollback_applicationId_application_applicationId_fk",
+ "rollback_deploymentId_deployment_deploymentId_fk": {
+ "name": "rollback_deploymentId_deployment_deploymentId_fk",
"tableFrom": "rollback",
- "tableTo": "application",
+ "tableTo": "deployment",
"columnsFrom": [
- "applicationId"
+ "deploymentId"
],
"columnsTo": [
- "applicationId"
+ "deploymentId"
],
"onDelete": "cascade",
"onUpdate": "no action"
diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json
index 9c91ba8e..fff18b2f 100644
--- a/apps/dokploy/drizzle/meta/_journal.json
+++ b/apps/dokploy/drizzle/meta/_journal.json
@@ -656,8 +656,8 @@
{
"idx": 93,
"version": "7",
- "when": 1748828655755,
- "tag": "0093_yielding_typhoid_mary",
+ "when": 1748835784658,
+ "tag": "0093_funny_leper_queen",
"breakpoints": true
}
]
diff --git a/apps/dokploy/server/api/routers/deployment.ts b/apps/dokploy/server/api/routers/deployment.ts
index d04f6454..129c1e4a 100644
--- a/apps/dokploy/server/api/routers/deployment.ts
+++ b/apps/dokploy/server/api/routers/deployment.ts
@@ -65,7 +65,11 @@ export const deploymentRouter = createTRPCRouter({
const deploymentsList = await db.query.deployments.findMany({
where: eq(deployments[`${input.type}Id`], input.id),
orderBy: desc(deployments.createdAt),
+ with: {
+ rollback: true,
+ },
});
+
return deploymentsList;
}),
});
diff --git a/apps/dokploy/server/api/routers/rollbacks.ts b/apps/dokploy/server/api/routers/rollbacks.ts
index b68aa669..6fb1f741 100644
--- a/apps/dokploy/server/api/routers/rollbacks.ts
+++ b/apps/dokploy/server/api/routers/rollbacks.ts
@@ -1,10 +1,10 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
-import { apiFindOneRollback, rollbacks } from "@/server/db/schema";
+import { apiFindOneRollback } from "@/server/db/schema";
import { removeRollbackById, rollback } from "@dokploy/server";
import { TRPCError } from "@trpc/server";
-import { eq, desc } from "drizzle-orm";
-import { db } from "@/server/db";
-import { z } from "zod";
+// import { eq, desc } from "drizzle-orm";
+// import { db } from "@/server/db";
+// import { z } from "zod";
export const rollbackRouter = createTRPCRouter({
delete: protectedProcedure
@@ -23,26 +23,26 @@ export const rollbackRouter = createTRPCRouter({
});
}
}),
- all: protectedProcedure
- .input(
- z.object({
- applicationId: z.string(),
- }),
- )
- .query(async ({ input }) => {
- try {
- return await db.query.rollbacks.findMany({
- where: eq(rollbacks.applicationId, input.applicationId),
- orderBy: desc(rollbacks.createdAt),
- });
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Fetching rollbacks",
- cause: error,
- });
- }
- }),
+ // all: protectedProcedure
+ // .input(
+ // z.object({
+ // applicationId: z.string(),
+ // }),
+ // )
+ // .query(async ({ input }) => {
+ // try {
+ // return await db.query.rollbacks.findMany({
+ // where: eq(rollbacks.applicationId, input.applicationId),
+ // orderBy: desc(rollbacks.createdAt),
+ // });
+ // } catch (error) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Error input: Fetching rollbacks",
+ // cause: error,
+ // });
+ // }
+ // }),
rollback: protectedProcedure
.input(apiFindOneRollback)
.mutation(async ({ input }) => {
diff --git a/apps/dokploy/tailwind.config.ts b/apps/dokploy/tailwind.config.ts
index be1f8986..4a7ec8ac 100644
--- a/apps/dokploy/tailwind.config.ts
+++ b/apps/dokploy/tailwind.config.ts
@@ -15,7 +15,7 @@ const config = {
center: true,
padding: "2rem",
screens: {
- "2xl": "1400px",
+ "2xl": "87.5rem",
},
},
extend: {
diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts
index 33c81f8f..099f1da4 100644
--- a/packages/server/src/db/schema/application.ts
+++ b/packages/server/src/db/schema/application.ts
@@ -27,7 +27,6 @@ import { server } from "./server";
import { applicationStatus, certificateType, triggerType } from "./shared";
import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils";
-import { rollbacks } from "./rollbacks";
export const sourceType = pgEnum("sourceType", [
"docker",
"git",
@@ -277,7 +276,6 @@ export const applicationsRelations = relations(
references: [server.serverId],
}),
previewDeployments: many(previewDeployments),
- rollbacks: many(rollbacks),
}),
);
diff --git a/packages/server/src/db/schema/deployment.ts b/packages/server/src/db/schema/deployment.ts
index 5712d928..1e96806d 100644
--- a/packages/server/src/db/schema/deployment.ts
+++ b/packages/server/src/db/schema/deployment.ts
@@ -15,6 +15,7 @@ import { compose } from "./compose";
import { previewDeployments } from "./preview-deployments";
import { schedules } from "./schedule";
import { server } from "./server";
+import { rollbacks } from "./rollbacks";
export const deploymentStatus = pgEnum("deploymentStatus", [
"running",
"done",
@@ -58,6 +59,10 @@ export const deployments = pgTable("deployment", {
backupId: text("backupId").references((): AnyPgColumn => backups.backupId, {
onDelete: "cascade",
}),
+ rollbackId: text("rollbackId").references(
+ (): AnyPgColumn => rollbacks.rollbackId,
+ { onDelete: "cascade" },
+ ),
});
export const deploymentsRelations = relations(deployments, ({ one }) => ({
@@ -85,6 +90,10 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({
fields: [deployments.backupId],
references: [backups.backupId],
}),
+ rollback: one(rollbacks, {
+ fields: [deployments.deploymentId],
+ references: [rollbacks.deploymentId],
+ }),
}));
const schema = createInsertSchema(deployments, {
diff --git a/packages/server/src/db/schema/rollbacks.ts b/packages/server/src/db/schema/rollbacks.ts
index 6775c72c..a91262b0 100644
--- a/packages/server/src/db/schema/rollbacks.ts
+++ b/packages/server/src/db/schema/rollbacks.ts
@@ -3,7 +3,7 @@ import { pgTable, serial, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { applications } from "./application";
+import { deployments } from "./deployment";
export const rollbacks = pgTable("rollback", {
rollbackId: text("rollbackId")
@@ -11,9 +11,9 @@ export const rollbacks = pgTable("rollback", {
.primaryKey()
.$defaultFn(() => nanoid()),
env: text("env"),
- applicationId: text("applicationId")
+ deploymentId: text("deploymentId")
.notNull()
- .references(() => applications.applicationId, {
+ .references(() => deployments.deploymentId, {
onDelete: "cascade",
}),
version: serial(),
@@ -26,9 +26,9 @@ export const rollbacks = pgTable("rollback", {
export type Rollback = typeof rollbacks.$inferSelect;
export const rollbacksRelations = relations(rollbacks, ({ one }) => ({
- application: one(applications, {
- fields: [rollbacks.applicationId],
- references: [applications.applicationId],
+ deployment: one(deployments, {
+ fields: [rollbacks.deploymentId],
+ references: [deployments.deploymentId],
}),
}));
diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts
index 7a690db1..a4661eb9 100644
--- a/packages/server/src/services/application.ts
+++ b/packages/server/src/services/application.ts
@@ -224,12 +224,10 @@ export const deployApplication = async ({
application.project.env,
);
- console.log(resolveEnvs);
-
await createRollback({
appName: application.appName,
env: resolveEnvs.join("\n"),
- applicationId: applicationId,
+ deploymentId: deployment.deploymentId,
});
}
diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts
index 6a76fcaa..d74a1e26 100644
--- a/packages/server/src/services/deployment.ts
+++ b/packages/server/src/services/deployment.ts
@@ -34,20 +34,34 @@ import { findScheduleById } from "./schedule";
export type Deployment = typeof deployments.$inferSelect;
-export const findDeploymentById = async (applicationId: string) => {
- const application = await db.query.deployments.findFirst({
- where: eq(deployments.applicationId, applicationId),
+export const findDeploymentById = async (deploymentId: string) => {
+ const deployment = await db.query.deployments.findFirst({
+ where: eq(deployments.deploymentId, deploymentId),
with: {
application: true,
},
});
- if (!application) {
+ if (!deployment) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Deployment not found",
});
}
- return application;
+ return deployment;
+};
+
+export const findDeploymentByApplicationId = async (applicationId: string) => {
+ const deployment = await db.query.deployments.findFirst({
+ where: eq(deployments.applicationId, applicationId),
+ });
+
+ if (!deployment) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Deployment not found",
+ });
+ }
+ return deployment;
};
export const createDeployment = async (
diff --git a/packages/server/src/services/rollbacks.ts b/packages/server/src/services/rollbacks.ts
index f70f83f0..e32e5ba0 100644
--- a/packages/server/src/services/rollbacks.ts
+++ b/packages/server/src/services/rollbacks.ts
@@ -7,6 +7,7 @@ import { getRemoteDocker } from "../utils/servers/remote-docker";
import type { ApplicationNested } from "../utils/builders";
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
import type { CreateServiceOptions } from "dockerode";
+import { findDeploymentById } from "./deployment";
export const createRollback = async (
input: z.infer,
@@ -31,7 +32,13 @@ export const createRollback = async (
})
.where(eq(rollbacks.rollbackId, rollback.rollbackId));
- const application = await findApplicationById(input.applicationId);
+ const deployment = await findDeploymentById(rollback.deploymentId);
+
+ if (!deployment?.applicationId) {
+ throw new Error("Deployment not found");
+ }
+
+ const application = await findApplicationById(deployment.applicationId);
await createRollbackImage(application, tagImage);
@@ -86,7 +93,13 @@ export const removeRollbackById = async (rollbackId: string) => {
if (result?.image) {
try {
- const application = await findApplicationById(result.applicationId);
+ const deployment = await findDeploymentById(result.deploymentId);
+
+ if (!deployment?.applicationId) {
+ throw new Error("Deployment not found");
+ }
+
+ const application = await findApplicationById(deployment.applicationId);
await deleteRollbackImage(result.image, application.serverId);
} catch (error) {
console.error(error);
@@ -99,7 +112,13 @@ export const removeRollbackById = async (rollbackId: string) => {
export const rollback = async (rollbackId: string) => {
const result = await findRollbackById(rollbackId);
- const application = await findApplicationById(result.applicationId);
+ const deployment = await findDeploymentById(result.deploymentId);
+
+ if (!deployment?.applicationId) {
+ throw new Error("Deployment not found");
+ }
+
+ const application = await findApplicationById(deployment.applicationId);
await rollbackApplication(
application.appName,
@@ -122,7 +141,7 @@ const rollbackApplication = async (
TaskTemplate: {
ContainerSpec: {
Image: image,
- Env: env.split("\n"),
+ // Env: env.split("\n"),
},
},
};