Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion apps/dokploy/server/api/routers/preview-deployment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
createPreviewDeploymentFromImage,
findApplicationById,
findPreviewDeploymentById,
findPreviewDeploymentsByApplicationId,
Expand All @@ -7,7 +8,10 @@ import {
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { apiFindAllByApplication } from "@/server/db/schema";
import {
apiFindAllByApplication,
apiCreatePreviewDeploymentFromImage,
} from "@/server/db/schema";
import type { DeploymentJob } from "@/server/queues/queue-types";
import { myQueue } from "@/server/queues/queueSetup";
import { deploy } from "@/server/utils/deploy";
Expand Down Expand Up @@ -115,4 +119,47 @@ export const previewDeploymentRouter = createTRPCRouter({
);
return true;
}),
deployFromImage: protectedProcedure
.input(apiCreatePreviewDeploymentFromImage)
.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 access this application",
});
}

const previewDeployment = await createPreviewDeploymentFromImage(input);

const jobData: DeploymentJob = {
applicationId: input.applicationId,
titleLog: `Deploy Docker image: ${input.dockerImage}`,
descriptionLog: "",
type: "deploy",
applicationType: "application-preview",
previewDeploymentId: previewDeployment.previewDeploymentId,
server: !!application.serverId,
};

if (IS_CLOUD && application.serverId) {
jobData.serverId = application.serverId;
deploy(jobData).catch((error) => {
console.error("Background deployment failed:", error);
});
return previewDeployment;
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
return previewDeployment;
}),
});
11 changes: 10 additions & 1 deletion packages/server/src/db/schema/preview-deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const previewDeployments = pgTable("preview_deployments", {
domainId: text("domainId").references(() => domains.domainId, {
onDelete: "cascade",
}),
dockerImage: text("dockerImage"),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
Expand Down Expand Up @@ -67,8 +68,16 @@ export const apiCreatePreviewDeployment = createSchema
pullRequestNumber: true,
pullRequestURL: true,
pullRequestTitle: true,
dockerImage: true,
})
.extend({
applicationId: z.string().min(1),
// deploymentId: z.string().min(1),
});

export const apiCreatePreviewDeploymentFromImage = z.object({
applicationId: z.string().min(1),
dockerImage: z.string().min(1),
pullRequestNumber: z.string().optional(),
pullRequestTitle: z.string().optional(),
pullRequestURL: z.string().optional(),
});
33 changes: 26 additions & 7 deletions packages/server/src/services/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,14 @@ export const deployPreviewApplication = async ({
application.buildArgs = `${application.previewBuildArgs}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
application.buildSecrets = `${application.previewBuildSecrets}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
application.rollbackActive = false;
application.buildRegistry = null;
if (!application.buildServerId || application.buildServerId === application.serverId) {
application.buildRegistry = null;
}
application.rollbackRegistry = null;
application.registry = null;

const buildServerId =
application.buildServerId || application.serverId;
let command = "set -e;";
if (application.sourceType === "github") {
command += await cloneGithubRepository({
Expand All @@ -430,10 +434,17 @@ export const deployPreviewApplication = async ({
branch: previewDeployment.branch,
});
command += await getBuildCommand(application);
} else if (application.sourceType === "docker") {
if (previewDeployment.dockerImage) {
application.dockerImage = previewDeployment.dockerImage;
}
command += await buildRemoteDocker(application);
}

if (application.sourceType === "github" || application.sourceType === "docker") {
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (application.serverId) {
await execAsyncRemote(application.serverId, commandWithLog);
if (buildServerId) {
await execAsyncRemote(buildServerId, commandWithLog);
} else {
await execAsync(commandWithLog);
}
Expand Down Expand Up @@ -537,14 +548,22 @@ export const rebuildPreviewApplication = async ({
application.buildArgs = `${application.previewBuildArgs}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
application.buildSecrets = `${application.previewBuildSecrets}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
application.rollbackActive = false;
application.buildRegistry = null;
if (!application.buildServerId || application.buildServerId === application.serverId) {
application.buildRegistry = null;
}
application.rollbackRegistry = null;
application.registry = null;

const serverId = application.serverId;
const serverId = application.buildServerId || application.serverId;
let command = "set -e;";
// Only rebuild, don't clone repository
command += await getBuildCommand(application);
if (application.sourceType === "docker") {
if (previewDeployment.dockerImage) {
application.dockerImage = previewDeployment.dockerImage;
}
command += await buildRemoteDocker(application);
} else {
command += await getBuildCommand(application);
}
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (serverId) {
await execAsyncRemote(serverId, commandWithLog);
Expand Down
16 changes: 11 additions & 5 deletions packages/server/src/services/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,24 @@ export const createDeploymentPreview = async (
deployment.previewDeploymentId,
);
try {
const buildServerId =
previewDeployment?.application?.buildServerId ||
previewDeployment?.application?.serverId;

await removeLastTenDeployments(
deployment.previewDeploymentId,
"previewDeployment",
previewDeployment?.application?.serverId,
);
Comment on lines 168 to 172
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still passing application.serverId instead of buildServerId to cleanup old logs

Suggested change
await removeLastTenDeployments(
deployment.previewDeploymentId,
"previewDeployment",
previewDeployment?.application?.serverId,
);
await removeLastTenDeployments(
deployment.previewDeploymentId,
"previewDeployment",
buildServerId,
);


const appName = `${previewDeployment.appName}`;
const { LOGS_PATH } = paths(!!previewDeployment?.application?.serverId);
const { LOGS_PATH } = paths(!!buildServerId);
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
const fileName = `${appName}-${formattedDateTime}.log`;
const logFilePath = path.join(LOGS_PATH, appName, fileName);

if (previewDeployment?.application?.serverId) {
const server = await findServerById(
previewDeployment?.application?.serverId,
);
if (buildServerId) {
const server = await findServerById(buildServerId);

const command = `
mkdir -p ${LOGS_PATH}/${appName};
Expand All @@ -200,6 +202,10 @@ export const createDeploymentPreview = async (
description: deployment.description || "",
previewDeploymentId: deployment.previewDeploymentId,
startedAt: new Date().toISOString(),
...(previewDeployment?.application?.buildServerId && {
buildServerId:
previewDeployment.application.buildServerId,
}),
})
.returning();
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
Expand Down
103 changes: 73 additions & 30 deletions packages/server/src/services/preview-deployment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { db } from "@dokploy/server/db";
import {
type apiCreatePreviewDeployment,
type apiCreatePreviewDeploymentFromImage,
deployments,
organization,
previewDeployments,
Expand Down Expand Up @@ -97,7 +98,7 @@ export const removePreviewDeployment = async (previewDeploymentId: string) => {
});
}
};
// testing-tesoitnmg-ddq0ul-preview-ihl44o

export const updatePreviewDeployment = async (
previewDeploymentId: string,
previewDeploymentData: Partial<PreviewDeployment>,
Expand Down Expand Up @@ -129,44 +130,39 @@ export const findPreviewDeploymentsByApplicationId = async (
return deploymentsList;
};

export const createPreviewDeployment = async (
schema: typeof apiCreatePreviewDeployment._type,
) => {
const application = await findApplicationById(schema.applicationId);
/**
* Generates appName, resolves organization and wildcard domain for a preview.
* Returns all data needed before DB insert (callers may need `host` for GitHub comments).
*/
const preparePreview = async (applicationId: string) => {
const application = await findApplicationById(applicationId);
const appName = `preview-${application.appName}-${generatePassword(6)}`;

const org = await db.query.organization.findFirst({
where: eq(organization.id, application.environment.project.organizationId),
});
const generateDomain = await generateWildcardDomain(
const host = await generateWildcardDomain(
application.previewWildcard || "*.traefik.me",
appName,
application.server?.ipAddress || "",
org?.ownerId || "",
);

const octokit = authGithub(application?.github as Github);

const runningComment = getIssueComment(
application.name,
"initializing",
`${application.previewHttps ? "https" : "http"}://${generateDomain}`,
);

const issue = await octokit.rest.issues.createComment({
owner: application?.owner || "",
repo: application?.repository || "",
issue_number: Number.parseInt(schema.pullRequestNumber),
body: `### Dokploy Preview Deployment\n\n${runningComment}`,
});
return { application, appName, host };
};

/**
* Inserts a preview deployment record and sets up domain + Traefik routing.
*/
const insertAndConfigurePreview = async (
application: Awaited<ReturnType<typeof findApplicationById>>,
appName: string,
host: string,
values: typeof previewDeployments.$inferInsert,
) => {
const previewDeployment = await db
.insert(previewDeployments)
.values({
...schema,
appName: appName,
pullRequestCommentId: `${issue.data.id}`,
})
.values(values)
.returning()
.then((value) => value[0]);

Expand All @@ -178,7 +174,7 @@ export const createPreviewDeployment = async (
}

const newDomain = await createDomain({
host: generateDomain,
host,
path: application.previewPath,
port: application.previewPort,
https: application.previewHttps,
Expand All @@ -189,14 +185,11 @@ export const createPreviewDeployment = async (
});

application.appName = appName;

await manageDomain(application, newDomain);

await db
.update(previewDeployments)
.set({
domainId: newDomain.domainId,
})
.set({ domainId: newDomain.domainId })
.where(
eq(
previewDeployments.previewDeploymentId,
Expand All @@ -207,6 +200,56 @@ export const createPreviewDeployment = async (
return previewDeployment;
};

export const createPreviewDeployment = async (
schema: typeof apiCreatePreviewDeployment._type,
) => {
const { application, appName, host } = await preparePreview(
schema.applicationId,
);

const octokit = authGithub(application?.github as Github);

const runningComment = getIssueComment(
application.name,
"initializing",
`${application.previewHttps ? "https" : "http"}://${host}`,
);

const issue = await octokit.rest.issues.createComment({
owner: application?.owner || "",
repo: application?.repository || "",
issue_number: Number.parseInt(schema.pullRequestNumber),
body: `### Dokploy Preview Deployment\n\n${runningComment}`,
});

return insertAndConfigurePreview(application, appName, host, {
...schema,
appName,
pullRequestCommentId: `${issue.data.id}`,
});
};

export const createPreviewDeploymentFromImage = async (
schema: typeof apiCreatePreviewDeploymentFromImage._type,
) => {
const { application, appName, host } = await preparePreview(
schema.applicationId,
);

return insertAndConfigurePreview(application, appName, host, {
applicationId: schema.applicationId,
appName,
branch: "main",
pullRequestId: schema.pullRequestNumber || "",
pullRequestNumber: schema.pullRequestNumber || "",
pullRequestURL: schema.pullRequestURL || "",
pullRequestTitle:
schema.pullRequestTitle || `Docker image: ${schema.dockerImage}`,
pullRequestCommentId: "",
dockerImage: schema.dockerImage,
});
};

export const findPreviewDeploymentsByPullRequestId = async (
pullRequestId: string,
) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/utils/builders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export const mechanizeDockerContainer = async (
});
} catch (error) {
console.log(error);
await docker.createService(settings);
await docker.createService(settings.authconfig, settings);
}
};

Expand Down