(activeTab);
const { data } = api.redis.one.useQuery({ redisId });
@@ -68,18 +68,20 @@ const Redis = (
list={[
{ name: "Projects", href: "/dashboard/projects" },
{
- name: data?.project?.name || "",
- href: `/dashboard/project/${projectId}`,
+ name: data?.environment?.project?.name || "",
+ },
+ {
+ name: data?.environment?.name || "",
+ href: `/dashboard/project/${projectId}/environment/${environmentId}`,
},
{
name: data?.name || "",
- href: `/dashboard/project/${projectId}/services/redis/${redisId}`,
},
]}
/>
- Database: {data?.name} - {data?.project.name} | Dokploy
+ Database: {data?.name} - {data?.environment?.project?.name} | Dokploy
@@ -179,7 +181,7 @@ const Redis = (
className="w-full"
onValueChange={(e) => {
setSab(e as TabState);
- const newPath = `/dashboard/project/${projectId}/services/redis/${redisId}?tab=${e}`;
+ const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/redis/${redisId}?tab=${e}`;
router.push(newPath, undefined, { shallow: true });
}}
@@ -291,7 +293,11 @@ Redis.getLayout = (page: ReactElement) => {
};
export async function getServerSideProps(
- ctx: GetServerSidePropsContext<{ redisId: string; activeTab: TabState }>,
+ ctx: GetServerSidePropsContext<{
+ redisId: string;
+ activeTab: TabState;
+ environmentId: string;
+ }>,
) {
const { query, params, req, res } = ctx;
const activeTab = query.tab;
diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts
index e930f2264..63ce38d10 100644
--- a/apps/dokploy/server/api/root.ts
+++ b/apps/dokploy/server/api/root.ts
@@ -11,6 +11,7 @@ import { deploymentRouter } from "./routers/deployment";
import { destinationRouter } from "./routers/destination";
import { dockerRouter } from "./routers/docker";
import { domainRouter } from "./routers/domain";
+import { environmentRouter } from "./routers/environment";
import { gitProviderRouter } from "./routers/git-provider";
import { giteaRouter } from "./routers/gitea";
import { githubRouter } from "./routers/github";
@@ -84,6 +85,7 @@ export const appRouter = createTRPCRouter({
schedule: scheduleRouter,
rollback: rollbackRouter,
volumeBackups: volumeBackupsRouter,
+ environment: environmentRouter,
});
// export type definition of API
diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts
index 51b9ba95c..6fa2e0cdc 100644
--- a/apps/dokploy/server/api/routers/ai.ts
+++ b/apps/dokploy/server/api/routers/ai.ts
@@ -4,7 +4,11 @@ import {
apiUpdateAi,
deploySuggestionSchema,
} from "@dokploy/server/db/schema/ai";
-import { createDomain, createMount } from "@dokploy/server/index";
+import {
+ createDomain,
+ createMount,
+ findEnvironmentById,
+} from "@dokploy/server/index";
import {
deleteAiSettings,
getAiSettingById,
@@ -177,10 +181,12 @@ export const aiRouter = createTRPCRouter({
deploy: protectedProcedure
.input(deploySuggestionSchema)
.mutation(async ({ ctx, input }) => {
+ const environment = await findEnvironmentById(input.environmentId);
+ const project = await findProjectById(environment.projectId);
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.session.activeOrganizationId,
- input.projectId,
+ environment.projectId,
"create",
);
}
@@ -192,8 +198,6 @@ export const aiRouter = createTRPCRouter({
});
}
- const project = await findProjectById(input.projectId);
-
const projectName = slugify(`${project.name} ${input.id}`);
const compose = await createComposeByTemplate({
@@ -205,6 +209,7 @@ export const aiRouter = createTRPCRouter({
sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`,
isolatedDeployment: true,
+ environmentId: input.environmentId,
});
if (input.domains && input.domains?.length > 0) {
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts
index 5299ba6c2..bddae6887 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -4,6 +4,7 @@ import {
createApplication,
deleteAllMiddlewares,
findApplicationById,
+ findEnvironmentById,
findGitProviderById,
findProjectById,
getApplicationStats,
@@ -39,8 +40,10 @@ import {
import { db } from "@/server/db";
import {
apiCreateApplication,
+ apiDeployApplication,
apiFindMonitoringStats,
apiFindOneApplication,
+ apiRedeployApplication,
apiReloadApplication,
apiSaveBitbucketProvider,
apiSaveBuildType,
@@ -63,10 +66,14 @@ export const applicationRouter = createTRPCRouter({
.input(apiCreateApplication)
.mutation(async ({ input, ctx }) => {
try {
+ // Get project from environment
+ const environment = await findEnvironmentById(input.environmentId);
+ const project = await findProjectById(environment.projectId);
+
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.user.id,
- input.projectId,
+ project.projectId,
ctx.session.activeOrganizationId,
"create",
);
@@ -79,13 +86,13 @@ export const applicationRouter = createTRPCRouter({
});
}
- const project = await findProjectById(input.projectId);
if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
+
const newApplication = await createApplication(input);
if (ctx.user.role === "member") {
@@ -97,6 +104,7 @@ export const applicationRouter = createTRPCRouter({
}
return newApplication;
} catch (error: unknown) {
+ console.log("error", error);
if (error instanceof TRPCError) {
throw error;
}
@@ -120,7 +128,8 @@ export const applicationRouter = createTRPCRouter({
}
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -175,7 +184,7 @@ export const applicationRouter = createTRPCRouter({
try {
if (
- application.project.organizationId !==
+ application.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
@@ -212,7 +221,8 @@ export const applicationRouter = createTRPCRouter({
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -247,14 +257,17 @@ export const applicationRouter = createTRPCRouter({
} catch (_) {}
}
- return result[0];
+ return application;
}),
stop: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const service = await findApplicationById(input.applicationId);
- if (service.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ service.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this application",
@@ -274,7 +287,10 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const service = await findApplicationById(input.applicationId);
- if (service.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ service.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this application",
@@ -292,11 +308,12 @@ export const applicationRouter = createTRPCRouter({
}),
redeploy: protectedProcedure
- .input(apiFindOneApplication)
+ .input(apiRedeployApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -305,8 +322,8 @@ export const applicationRouter = createTRPCRouter({
}
const jobData: DeploymentJob = {
applicationId: input.applicationId,
- titleLog: "Rebuild deployment",
- descriptionLog: "",
+ titleLog: input.title || "Rebuild deployment",
+ descriptionLog: input.description || "",
type: "redeploy",
applicationType: "application",
server: !!application.serverId,
@@ -331,7 +348,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -349,7 +367,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -374,7 +393,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -401,7 +421,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -429,7 +450,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -455,7 +477,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -481,7 +504,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -504,7 +528,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -529,7 +554,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -590,7 +616,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -604,7 +631,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -630,7 +658,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -643,11 +672,12 @@ export const applicationRouter = createTRPCRouter({
return true;
}),
deploy: protectedProcedure
- .input(apiFindOneApplication)
+ .input(apiDeployApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -656,8 +686,8 @@ export const applicationRouter = createTRPCRouter({
}
const jobData: DeploymentJob = {
applicationId: input.applicationId,
- titleLog: "Manual deployment",
- descriptionLog: "",
+ titleLog: input.title || "Manual deployment",
+ descriptionLog: input.description || "",
type: "deploy",
applicationType: "application",
server: !!application.serverId,
@@ -683,7 +713,8 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -698,7 +729,8 @@ export const applicationRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -734,7 +766,10 @@ export const applicationRouter = createTRPCRouter({
const app = await findApplicationById(input.applicationId as string);
- if (app.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ app.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this application",
@@ -777,7 +812,8 @@ export const applicationRouter = createTRPCRouter({
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -813,13 +849,14 @@ export const applicationRouter = createTRPCRouter({
.input(
z.object({
applicationId: z.string(),
- targetProjectId: z.string(),
+ targetEnvironmentId: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -827,11 +864,16 @@ export const applicationRouter = createTRPCRouter({
});
}
- const targetProject = await findProjectById(input.targetProjectId);
- if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
+ const targetEnvironment = await findEnvironmentById(
+ input.targetEnvironmentId,
+ );
+ if (
+ targetEnvironment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not authorized to move to this project",
+ message: "You are not authorized to move to this environment",
});
}
@@ -839,7 +881,7 @@ export const applicationRouter = createTRPCRouter({
const updatedApplication = await db
.update(applications)
.set({
- projectId: input.targetProjectId,
+ environmentId: input.targetEnvironmentId,
})
.where(eq(applications.applicationId, input.applicationId))
.returning()
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index 6d6b8b55d..2f9984183 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -12,6 +12,7 @@ import {
deleteMount,
findComposeById,
findDomainsByComposeId,
+ findEnvironmentById,
findGitProviderById,
findProjectById,
findServerById,
@@ -47,9 +48,11 @@ import { db } from "@/server/db";
import {
apiCreateCompose,
apiDeleteCompose,
+ apiDeployCompose,
apiFetchServices,
apiFindCompose,
apiRandomizeCompose,
+ apiRedeployCompose,
apiUpdateCompose,
compose as composeTable,
} from "@/server/db/schema";
@@ -64,10 +67,14 @@ export const composeRouter = createTRPCRouter({
.input(apiCreateCompose)
.mutation(async ({ ctx, input }) => {
try {
+ // Get project from environment
+ const environment = await findEnvironmentById(input.environmentId);
+ const project = await findProjectById(environment.projectId);
+
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.user.id,
- input.projectId,
+ project.projectId,
ctx.session.activeOrganizationId,
"create",
);
@@ -79,14 +86,15 @@ export const composeRouter = createTRPCRouter({
message: "You need to use a server to create a compose",
});
}
- const project = await findProjectById(input.projectId);
if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
- const newService = await createCompose(input);
+ const newService = await createCompose({
+ ...input,
+ });
if (ctx.user.role === "member") {
await addNewService(
@@ -115,7 +123,10 @@ export const composeRouter = createTRPCRouter({
}
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -166,7 +177,10 @@ export const composeRouter = createTRPCRouter({
.input(apiUpdateCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this compose",
@@ -188,7 +202,7 @@ export const composeRouter = createTRPCRouter({
const composeResult = await findComposeById(input.composeId);
if (
- composeResult.project.organizationId !==
+ composeResult.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
@@ -196,7 +210,6 @@ export const composeRouter = createTRPCRouter({
message: "You are not authorized to delete this compose",
});
}
- 4;
const result = await db
.delete(composeTable)
@@ -215,13 +228,16 @@ export const composeRouter = createTRPCRouter({
} catch (_) {}
}
- return result[0];
+ return composeResult;
}),
cleanQueues: protectedProcedure
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to clean this compose",
@@ -234,7 +250,10 @@ export const composeRouter = createTRPCRouter({
.input(apiFetchServices)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to load this compose",
@@ -251,7 +270,10 @@ export const composeRouter = createTRPCRouter({
)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to load this compose",
@@ -270,7 +292,8 @@ export const composeRouter = createTRPCRouter({
const compose = await findComposeById(input.composeId);
if (
- compose.project.organizationId !== ctx.session.activeOrganizationId
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -296,7 +319,10 @@ export const composeRouter = createTRPCRouter({
.input(apiRandomizeCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to randomize this compose",
@@ -308,7 +334,10 @@ export const composeRouter = createTRPCRouter({
.input(apiRandomizeCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to randomize this compose",
@@ -323,7 +352,10 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to get this compose",
@@ -337,11 +369,14 @@ export const composeRouter = createTRPCRouter({
}),
deploy: protectedProcedure
- .input(apiFindCompose)
+ .input(apiDeployCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this compose",
@@ -349,10 +384,10 @@ export const composeRouter = createTRPCRouter({
}
const jobData: DeploymentJob = {
composeId: input.composeId,
- titleLog: "Manual deployment",
+ titleLog: input.title || "Manual deployment",
type: "deploy",
applicationType: "compose",
- descriptionLog: "",
+ descriptionLog: input.description || "",
server: !!compose.serverId,
};
@@ -371,10 +406,13 @@ export const composeRouter = createTRPCRouter({
);
}),
redeploy: protectedProcedure
- .input(apiFindCompose)
+ .input(apiRedeployCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to redeploy this compose",
@@ -382,10 +420,10 @@ export const composeRouter = createTRPCRouter({
}
const jobData: DeploymentJob = {
composeId: input.composeId,
- titleLog: "Rebuild deployment",
+ titleLog: input.title || "Rebuild deployment",
type: "redeploy",
applicationType: "compose",
- descriptionLog: "",
+ descriptionLog: input.description || "",
server: !!compose.serverId,
};
if (IS_CLOUD && compose.serverId) {
@@ -406,7 +444,10 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this compose",
@@ -420,7 +461,10 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this compose",
@@ -435,7 +479,10 @@ export const composeRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to get this compose",
@@ -448,7 +495,10 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to refresh this compose",
@@ -462,17 +512,19 @@ export const composeRouter = createTRPCRouter({
deployTemplate: protectedProcedure
.input(
z.object({
- projectId: z.string(),
+ environmentId: z.string(),
serverId: z.string().optional(),
id: z.string(),
baseUrl: z.string().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
+ const environment = await findEnvironmentById(input.environmentId);
+
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.user.id,
- input.projectId,
+ environment.projectId,
ctx.session.activeOrganizationId,
"create",
);
@@ -490,7 +542,7 @@ export const composeRouter = createTRPCRouter({
const admin = await findUserById(ctx.user.ownerId);
let serverIp = admin.serverIp || "127.0.0.1";
- const project = await findProjectById(input.projectId);
+ const project = await findProjectById(environment.projectId);
if (input.serverId) {
const server = await findServerById(input.serverId);
@@ -591,7 +643,10 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to disconnect this git provider",
@@ -647,30 +702,38 @@ export const composeRouter = createTRPCRouter({
.input(
z.object({
composeId: z.string(),
- targetProjectId: z.string(),
+ targetEnvironmentId: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to move this compose",
});
}
- const targetProject = await findProjectById(input.targetProjectId);
- if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
+ const targetEnvironment = await findEnvironmentById(
+ input.targetEnvironmentId,
+ );
+ if (
+ targetEnvironment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not authorized to move to this project",
+ message: "You are not authorized to move to this environment",
});
}
const updatedCompose = await db
.update(composeTable)
.set({
- projectId: input.targetProjectId,
+ environmentId: input.targetEnvironmentId,
})
.where(eq(composeTable.composeId, input.composeId))
.returning()
@@ -698,7 +761,8 @@ export const composeRouter = createTRPCRouter({
const compose = await findComposeById(input.composeId);
if (
- compose.project.organizationId !== ctx.session.activeOrganizationId
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -769,7 +833,8 @@ export const composeRouter = createTRPCRouter({
);
if (
- compose.project.organizationId !== ctx.session.activeOrganizationId
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
diff --git a/apps/dokploy/server/api/routers/deployment.ts b/apps/dokploy/server/api/routers/deployment.ts
index 40a0834ff..9004a0a05 100644
--- a/apps/dokploy/server/api/routers/deployment.ts
+++ b/apps/dokploy/server/api/routers/deployment.ts
@@ -29,7 +29,8 @@ export const deploymentRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -43,7 +44,10 @@ export const deploymentRouter = createTRPCRouter({
.input(apiFindAllByCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts
index 7c16baf34..1f6264351 100644
--- a/apps/dokploy/server/api/routers/domain.ts
+++ b/apps/dokploy/server/api/routers/domain.ts
@@ -34,7 +34,8 @@ export const domainRouter = createTRPCRouter({
if (input.domainType === "compose" && input.composeId) {
const compose = await findComposeById(input.composeId);
if (
- compose.project.organizationId !== ctx.session.activeOrganizationId
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -44,7 +45,7 @@ export const domainRouter = createTRPCRouter({
} else if (input.domainType === "application" && input.applicationId) {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !==
+ application.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
@@ -70,7 +71,8 @@ export const domainRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -83,7 +85,10 @@ export const domainRouter = createTRPCRouter({
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -122,7 +127,8 @@ export const domainRouter = createTRPCRouter({
if (currentDomain.applicationId) {
const newApp = await findApplicationById(currentDomain.applicationId);
if (
- newApp.project.organizationId !== ctx.session.activeOrganizationId
+ newApp.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -132,7 +138,8 @@ export const domainRouter = createTRPCRouter({
} else if (currentDomain.composeId) {
const newCompose = await findComposeById(currentDomain.composeId);
if (
- newCompose.project.organizationId !== ctx.session.activeOrganizationId
+ newCompose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -144,8 +151,8 @@ export const domainRouter = createTRPCRouter({
currentDomain.previewDeploymentId,
);
if (
- newPreviewDeployment.application.project.organizationId !==
- ctx.session.activeOrganizationId
+ newPreviewDeployment.application.environment.project
+ .organizationId !== ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -175,7 +182,8 @@ export const domainRouter = createTRPCRouter({
if (domain.applicationId) {
const application = await findApplicationById(domain.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -184,7 +192,10 @@ export const domainRouter = createTRPCRouter({
}
} else if (domain.composeId) {
const compose = await findComposeById(domain.composeId);
- if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -200,7 +211,7 @@ export const domainRouter = createTRPCRouter({
if (domain.applicationId) {
const application = await findApplicationById(domain.applicationId);
if (
- application.project.organizationId !==
+ application.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
@@ -211,7 +222,8 @@ export const domainRouter = createTRPCRouter({
} else if (domain.composeId) {
const compose = await findComposeById(domain.composeId);
if (
- compose.project.organizationId !== ctx.session.activeOrganizationId
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
diff --git a/apps/dokploy/server/api/routers/environment.ts b/apps/dokploy/server/api/routers/environment.ts
new file mode 100644
index 000000000..98c565134
--- /dev/null
+++ b/apps/dokploy/server/api/routers/environment.ts
@@ -0,0 +1,343 @@
+import {
+ addNewEnvironment,
+ checkEnvironmentAccess,
+ createEnvironment,
+ deleteEnvironment,
+ duplicateEnvironment,
+ findEnvironmentById,
+ findEnvironmentsByProjectId,
+ findMemberById,
+ updateEnvironmentById,
+} from "@dokploy/server";
+import { TRPCError } from "@trpc/server";
+import { z } from "zod";
+import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
+import {
+ apiCreateEnvironment,
+ apiDuplicateEnvironment,
+ apiFindOneEnvironment,
+ apiRemoveEnvironment,
+ apiUpdateEnvironment,
+} from "@/server/db/schema";
+
+// Helper function to filter services within an environment based on user permissions
+const filterEnvironmentServices = (
+ environment: any,
+ accessedServices: string[],
+) => ({
+ ...environment,
+ applications: environment.applications.filter((app: any) =>
+ accessedServices.includes(app.applicationId),
+ ),
+ mariadb: environment.mariadb.filter((db: any) =>
+ accessedServices.includes(db.mariadbId),
+ ),
+ mongo: environment.mongo.filter((db: any) =>
+ accessedServices.includes(db.mongoId),
+ ),
+ mysql: environment.mysql.filter((db: any) =>
+ accessedServices.includes(db.mysqlId),
+ ),
+ postgres: environment.postgres.filter((db: any) =>
+ accessedServices.includes(db.postgresId),
+ ),
+ redis: environment.redis.filter((db: any) =>
+ accessedServices.includes(db.redisId),
+ ),
+ compose: environment.compose.filter((comp: any) =>
+ accessedServices.includes(comp.composeId),
+ ),
+});
+
+export const environmentRouter = createTRPCRouter({
+ create: protectedProcedure
+ .input(apiCreateEnvironment)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ // Check if user has access to the project
+ // This would typically involve checking project ownership/membership
+ // For now, we'll use a basic organization check
+
+ if (input.name === "production") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Environment name cannot be production",
+ });
+ }
+
+ const environment = await createEnvironment(input);
+
+ if (ctx.user.role === "member") {
+ await addNewEnvironment(
+ ctx.user.id,
+ environment.environmentId,
+ ctx.session.activeOrganizationId,
+ );
+ }
+ return environment;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `Error creating the environment: ${error instanceof Error ? error.message : error}`,
+ cause: error,
+ });
+ }
+ }),
+
+ one: protectedProcedure
+ .input(apiFindOneEnvironment)
+ .query(async ({ input, ctx }) => {
+ try {
+ if (ctx.user.role === "member") {
+ await checkEnvironmentAccess(
+ ctx.user.id,
+ input.environmentId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
+ }
+ const environment = await findEnvironmentById(input.environmentId);
+ if (
+ environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to access this environment",
+ });
+ }
+
+ // Check environment access and filter services for members
+ if (ctx.user.role === "member") {
+ const { accessedEnvironments, accessedServices } =
+ await findMemberById(ctx.user.id, ctx.session.activeOrganizationId);
+
+ if (!accessedEnvironments.includes(environment.environmentId)) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to access this environment",
+ });
+ }
+
+ // Filter services based on member permissions
+ const filteredEnvironment = filterEnvironmentServices(
+ environment,
+ accessedServices,
+ );
+
+ return filteredEnvironment;
+ }
+
+ return environment;
+ } catch (error) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Environment not found",
+ });
+ }
+ }),
+
+ byProjectId: protectedProcedure
+ .input(z.object({ projectId: z.string() }))
+ .query(async ({ input, ctx }) => {
+ try {
+ const environments = await findEnvironmentsByProjectId(input.projectId);
+
+ // Check organization access
+ if (
+ environments.some(
+ (environment) =>
+ environment.project.organizationId !==
+ ctx.session.activeOrganizationId,
+ )
+ ) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to access this environment",
+ });
+ }
+
+ // Filter environments for members based on their permissions
+ if (ctx.user.role === "member") {
+ const { accessedEnvironments, accessedServices } =
+ await findMemberById(ctx.user.id, ctx.session.activeOrganizationId);
+
+ // Filter environments to only show those the member has access to
+ const filteredEnvironments = environments
+ .filter((environment) =>
+ accessedEnvironments.includes(environment.environmentId),
+ )
+ .map((environment) =>
+ filterEnvironmentServices(environment, accessedServices),
+ );
+
+ return filteredEnvironments;
+ }
+
+ return environments;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `Error fetching environments: ${error instanceof Error ? error.message : error}`,
+ });
+ }
+ }),
+
+ remove: protectedProcedure
+ .input(apiRemoveEnvironment)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ if (ctx.user.role === "member") {
+ await checkEnvironmentAccess(
+ ctx.user.id,
+ input.environmentId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
+ }
+ const environment = await findEnvironmentById(input.environmentId);
+ if (
+ environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to access this environment",
+ });
+ }
+
+ // Check environment access for members
+ if (ctx.user.role === "member") {
+ const { accessedEnvironments } = await findMemberById(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
+
+ if (!accessedEnvironments.includes(environment.environmentId)) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to delete this environment",
+ });
+ }
+ }
+
+ const deletedEnvironment = await deleteEnvironment(input.environmentId);
+ return deletedEnvironment;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `Error deleting the environment: ${error instanceof Error ? error.message : error}`,
+ });
+ }
+ }),
+
+ update: protectedProcedure
+ .input(apiUpdateEnvironment)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const { environmentId, ...updateData } = input;
+
+ if (updateData.name === "production") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Environment name cannot be production",
+ });
+ }
+
+ if (ctx.user.role === "member") {
+ await checkEnvironmentAccess(
+ ctx.user.id,
+ environmentId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
+ }
+ const currentEnvironment = await findEnvironmentById(environmentId);
+ if (
+ currentEnvironment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to access this environment",
+ });
+ }
+
+ // Check environment access for members
+ if (ctx.user.role === "member") {
+ const { accessedEnvironments } = await findMemberById(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
+
+ if (
+ !accessedEnvironments.includes(currentEnvironment.environmentId)
+ ) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to update this environment",
+ });
+ }
+ }
+
+ const environment = await updateEnvironmentById(
+ environmentId,
+ updateData,
+ );
+ return environment;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `Error updating the environment: ${error instanceof Error ? error.message : error}`,
+ });
+ }
+ }),
+
+ duplicate: protectedProcedure
+ .input(apiDuplicateEnvironment)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ if (ctx.user.role === "member") {
+ await checkEnvironmentAccess(
+ ctx.user.id,
+ input.environmentId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
+ }
+ const environment = await findEnvironmentById(input.environmentId);
+ if (
+ environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to access this environment",
+ });
+ }
+
+ // Check environment access for members
+ if (ctx.user.role === "member") {
+ const { accessedEnvironments } = await findMemberById(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
+
+ if (!accessedEnvironments.includes(environment.environmentId)) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "You are not allowed to duplicate this environment",
+ });
+ }
+ }
+
+ const duplicatedEnvironment = await duplicateEnvironment(input);
+ return duplicatedEnvironment;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `Error duplicating the environment: ${error instanceof Error ? error.message : error}`,
+ });
+ }
+ }),
+});
diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts
index 9fc95a35e..dc811e0ca 100644
--- a/apps/dokploy/server/api/routers/mariadb.ts
+++ b/apps/dokploy/server/api/routers/mariadb.ts
@@ -6,6 +6,7 @@ import {
deployMariadb,
findBackupsByDbId,
findMariadbById,
+ findEnvironmentById,
findProjectById,
IS_CLOUD,
rebuildDatabase,
@@ -41,10 +42,14 @@ export const mariadbRouter = createTRPCRouter({
.input(apiCreateMariaDB)
.mutation(async ({ input, ctx }) => {
try {
+ // Get project from environment
+ const environment = await findEnvironmentById(input.environmentId);
+ const project = await findProjectById(environment.projectId);
+
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.user.id,
- input.projectId,
+ project.projectId,
ctx.session.activeOrganizationId,
"create",
);
@@ -57,14 +62,15 @@ export const mariadbRouter = createTRPCRouter({
});
}
- const project = await findProjectById(input.projectId);
if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
- const newMariadb = await createMariadb(input);
+ const newMariadb = await createMariadb({
+ ...input,
+ });
if (ctx.user.role === "member") {
await addNewService(
ctx.user.id,
@@ -101,7 +107,10 @@ export const mariadbRouter = createTRPCRouter({
);
}
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mariadb.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this Mariadb",
@@ -114,7 +123,10 @@ export const mariadbRouter = createTRPCRouter({
.input(apiFindOneMariaDB)
.mutation(async ({ input, ctx }) => {
const service = await findMariadbById(input.mariadbId);
- if (service.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ service.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Mariadb",
@@ -151,7 +163,10 @@ export const mariadbRouter = createTRPCRouter({
.input(apiSaveExternalPortMariaDB)
.mutation(async ({ input, ctx }) => {
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -167,7 +182,10 @@ export const mariadbRouter = createTRPCRouter({
.input(apiDeployMariaDB)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mariadb.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Mariadb",
@@ -188,7 +206,10 @@ export const mariadbRouter = createTRPCRouter({
.input(apiDeployMariaDB)
.subscription(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mariadb.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Mariadb",
@@ -205,7 +226,10 @@ export const mariadbRouter = createTRPCRouter({
.input(apiChangeMariaDBStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this Mariadb status",
@@ -229,7 +253,10 @@ export const mariadbRouter = createTRPCRouter({
}
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this Mariadb",
@@ -255,7 +282,10 @@ export const mariadbRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMariaDB)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mariadb.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -278,7 +308,10 @@ export const mariadbRouter = createTRPCRouter({
.input(apiResetMariadb)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mariadb.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this Mariadb",
@@ -308,7 +341,10 @@ export const mariadbRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mariadbId, ...rest } = input;
const mariadb = await findMariadbById(mariadbId);
- if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mariadb.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this Mariadb",
@@ -331,23 +367,31 @@ export const mariadbRouter = createTRPCRouter({
.input(
z.object({
mariadbId: z.string(),
- targetProjectId: z.string(),
+ targetEnvironmentId: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mariadb.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to move this mariadb",
});
}
- const targetProject = await findProjectById(input.targetProjectId);
- if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
+ const targetEnvironment = await findEnvironmentById(
+ input.targetEnvironmentId,
+ );
+ if (
+ targetEnvironment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not authorized to move to this project",
+ message: "You are not authorized to move to this environment",
});
}
@@ -355,7 +399,7 @@ export const mariadbRouter = createTRPCRouter({
const updatedMariadb = await db
.update(mariadbTable)
.set({
- projectId: input.targetProjectId,
+ environmentId: input.targetEnvironmentId,
})
.where(eq(mariadbTable.mariadbId, input.mariadbId))
.returning()
@@ -374,7 +418,10 @@ export const mariadbRouter = createTRPCRouter({
.input(apiRebuildMariadb)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mariadb.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to rebuild this MariaDB database",
diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts
index 026a7fe20..1f054a1c9 100644
--- a/apps/dokploy/server/api/routers/mongo.ts
+++ b/apps/dokploy/server/api/routers/mongo.ts
@@ -6,6 +6,7 @@ import {
deployMongo,
findBackupsByDbId,
findMongoById,
+ findEnvironmentById,
findProjectById,
IS_CLOUD,
rebuildDatabase,
@@ -41,10 +42,14 @@ export const mongoRouter = createTRPCRouter({
.input(apiCreateMongo)
.mutation(async ({ input, ctx }) => {
try {
+ // Get project from environment
+ const environment = await findEnvironmentById(input.environmentId);
+ const project = await findProjectById(environment.projectId);
+
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.user.id,
- input.projectId,
+ project.projectId,
ctx.session.activeOrganizationId,
"create",
);
@@ -57,14 +62,15 @@ export const mongoRouter = createTRPCRouter({
});
}
- const project = await findProjectById(input.projectId);
if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
- const newMongo = await createMongo(input);
+ const newMongo = await createMongo({
+ ...input,
+ });
if (ctx.user.role === "member") {
await addNewService(
ctx.user.id,
@@ -106,7 +112,10 @@ export const mongoRouter = createTRPCRouter({
}
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this mongo",
@@ -120,7 +129,10 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const service = await findMongoById(input.mongoId);
- if (service.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ service.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this mongo",
@@ -143,7 +155,10 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this mongo",
@@ -165,7 +180,10 @@ export const mongoRouter = createTRPCRouter({
.input(apiSaveExternalPortMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -181,7 +199,10 @@ export const mongoRouter = createTRPCRouter({
.input(apiDeployMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this mongo",
@@ -201,7 +222,10 @@ export const mongoRouter = createTRPCRouter({
.input(apiDeployMongo)
.subscription(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this mongo",
@@ -218,7 +242,10 @@ export const mongoRouter = createTRPCRouter({
.input(apiChangeMongoStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this mongo status",
@@ -233,7 +260,10 @@ export const mongoRouter = createTRPCRouter({
.input(apiResetMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this mongo",
@@ -272,7 +302,10 @@ export const mongoRouter = createTRPCRouter({
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this mongo",
@@ -298,7 +331,10 @@ export const mongoRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -322,7 +358,10 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mongoId, ...rest } = input;
const mongo = await findMongoById(mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this mongo",
@@ -345,23 +384,31 @@ export const mongoRouter = createTRPCRouter({
.input(
z.object({
mongoId: z.string(),
- targetProjectId: z.string(),
+ targetEnvironmentId: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to move this mongo",
});
}
- const targetProject = await findProjectById(input.targetProjectId);
- if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
+ const targetEnvironment = await findEnvironmentById(
+ input.targetEnvironmentId,
+ );
+ if (
+ targetEnvironment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not authorized to move to this project",
+ message: "You are not authorized to move to this environment",
});
}
@@ -369,7 +416,7 @@ export const mongoRouter = createTRPCRouter({
const updatedMongo = await db
.update(mongoTable)
.set({
- projectId: input.targetProjectId,
+ environmentId: input.targetEnvironmentId,
})
.where(eq(mongoTable.mongoId, input.mongoId))
.returning()
@@ -388,7 +435,10 @@ export const mongoRouter = createTRPCRouter({
.input(apiRebuildMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to rebuild this MongoDB database",
diff --git a/apps/dokploy/server/api/routers/mount.ts b/apps/dokploy/server/api/routers/mount.ts
index 4ffec8c19..814d3d392 100644
--- a/apps/dokploy/server/api/routers/mount.ts
+++ b/apps/dokploy/server/api/routers/mount.ts
@@ -3,9 +3,11 @@ import {
deleteMount,
findApplicationById,
findMountById,
+ findMountOrganizationId,
getServiceContainer,
updateMount,
} from "@dokploy/server";
+import { TRPCError } from "@trpc/server";
import { z } from "zod";
import {
apiCreateMount,
@@ -24,16 +26,39 @@ export const mountRouter = createTRPCRouter({
}),
remove: protectedProcedure
.input(apiRemoveMount)
- .mutation(async ({ input }) => {
+ .mutation(async ({ input, ctx }) => {
+ const organizationId = await findMountOrganizationId(input.mountId);
+ if (organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to delete this mount",
+ });
+ }
return await deleteMount(input.mountId);
}),
- one: protectedProcedure.input(apiFindOneMount).query(async ({ input }) => {
- return await findMountById(input.mountId);
- }),
+ one: protectedProcedure
+ .input(apiFindOneMount)
+ .query(async ({ input, ctx }) => {
+ const organizationId = await findMountOrganizationId(input.mountId);
+ if (organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to access this mount",
+ });
+ }
+ return await findMountById(input.mountId);
+ }),
update: protectedProcedure
.input(apiUpdateMount)
- .mutation(async ({ input }) => {
+ .mutation(async ({ input, ctx }) => {
+ const organizationId = await findMountOrganizationId(input.mountId);
+ if (organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this mount",
+ });
+ }
return await updateMount(input.mountId, input);
}),
allNamedByApplicationId: protectedProcedure
diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts
index 254bd836a..5edb27da4 100644
--- a/apps/dokploy/server/api/routers/mysql.ts
+++ b/apps/dokploy/server/api/routers/mysql.ts
@@ -5,6 +5,7 @@ import {
createMysql,
deployMySql,
findBackupsByDbId,
+ findEnvironmentById,
findMySqlById,
findProjectById,
IS_CLOUD,
@@ -42,10 +43,14 @@ export const mysqlRouter = createTRPCRouter({
.input(apiCreateMySql)
.mutation(async ({ input, ctx }) => {
try {
+ // Get project from environment
+ const environment = await findEnvironmentById(input.environmentId);
+ const project = await findProjectById(environment.projectId);
+
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.user.id,
- input.projectId,
+ project.projectId,
ctx.session.activeOrganizationId,
"create",
);
@@ -57,8 +62,7 @@ export const mysqlRouter = createTRPCRouter({
message: "You need to use a server to create a MySQL",
});
}
- 1;
- const project = await findProjectById(input.projectId);
+
if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -66,7 +70,9 @@ export const mysqlRouter = createTRPCRouter({
});
}
- const newMysql = await createMysql(input);
+ const newMysql = await createMysql({
+ ...input,
+ });
if (ctx.user.role === "member") {
await addNewService(
ctx.user.id,
@@ -107,7 +113,10 @@ export const mysqlRouter = createTRPCRouter({
);
}
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mysql.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this MySQL",
@@ -120,7 +129,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
const service = await findMySqlById(input.mysqlId);
- if (service.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ service.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this MySQL",
@@ -142,7 +154,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this MySQL",
@@ -163,7 +178,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiSaveExternalPortMySql)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -179,7 +197,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiDeployMySql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mysql.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this MySQL",
@@ -199,7 +220,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiDeployMySql)
.subscription(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mysql.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this MySQL",
@@ -216,7 +240,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiChangeMySqlStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this MySQL status",
@@ -231,7 +258,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiResetMysql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mysql.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this MySQL",
@@ -267,7 +297,10 @@ export const mysqlRouter = createTRPCRouter({
);
}
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this MySQL",
@@ -293,7 +326,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMySql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mysql.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -317,7 +353,10 @@ export const mysqlRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mysqlId, ...rest } = input;
const mysql = await findMySqlById(mysqlId);
- if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mysql.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this MySQL",
@@ -340,23 +379,31 @@ export const mysqlRouter = createTRPCRouter({
.input(
z.object({
mysqlId: z.string(),
- targetProjectId: z.string(),
+ targetEnvironmentId: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mysql.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to move this mysql",
});
}
- const targetProject = await findProjectById(input.targetProjectId);
- if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
+ const targetEnvironment = await findEnvironmentById(
+ input.targetEnvironmentId,
+ );
+ if (
+ targetEnvironment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not authorized to move to this project",
+ message: "You are not authorized to move to this environment",
});
}
@@ -364,7 +411,7 @@ export const mysqlRouter = createTRPCRouter({
const updatedMysql = await db
.update(mysqlTable)
.set({
- projectId: input.targetProjectId,
+ environmentId: input.targetEnvironmentId,
})
.where(eq(mysqlTable.mysqlId, input.mysqlId))
.returning()
@@ -383,7 +430,10 @@ export const mysqlRouter = createTRPCRouter({
.input(apiRebuildMysql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mysql.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to rebuild this MySQL database",
diff --git a/apps/dokploy/server/api/routers/port.ts b/apps/dokploy/server/api/routers/port.ts
index 40ba23d91..bbd949804 100644
--- a/apps/dokploy/server/api/routers/port.ts
+++ b/apps/dokploy/server/api/routers/port.ts
@@ -27,22 +27,44 @@ export const portRouter = createTRPCRouter({
});
}
}),
- one: protectedProcedure.input(apiFindOnePort).query(async ({ input }) => {
- try {
- return await finPortById(input.portId);
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Port not found",
- cause: error,
- });
- }
- }),
+ one: protectedProcedure
+ .input(apiFindOnePort)
+ .query(async ({ input, ctx }) => {
+ try {
+ const port = await finPortById(input.portId);
+ if (
+ port.application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to access this port",
+ });
+ }
+ return port;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Port not found",
+ cause: error,
+ });
+ }
+ }),
delete: protectedProcedure
.input(apiFindOnePort)
- .mutation(async ({ input }) => {
+ .mutation(async ({ input, ctx }) => {
+ const port = await finPortById(input.portId);
+ if (
+ port.application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to delete this port",
+ });
+ }
try {
- return removePortById(input.portId);
+ return await removePortById(input.portId);
} catch (error) {
const message =
error instanceof Error ? error.message : "Error input: Deleting port";
@@ -54,9 +76,19 @@ export const portRouter = createTRPCRouter({
}),
update: protectedProcedure
.input(apiUpdatePort)
- .mutation(async ({ input }) => {
+ .mutation(async ({ input, ctx }) => {
+ const port = await finPortById(input.portId);
+ if (
+ port.application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this port",
+ });
+ }
try {
- return updatePortById(input.portId, input);
+ return await updatePortById(input.portId, input);
} catch (error) {
const message =
error instanceof Error ? error.message : "Error updating the port";
diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts
index 12d244241..a05072ab7 100644
--- a/apps/dokploy/server/api/routers/postgres.ts
+++ b/apps/dokploy/server/api/routers/postgres.ts
@@ -5,6 +5,7 @@ import {
createPostgres,
deployPostgres,
findBackupsByDbId,
+ findEnvironmentById,
findPostgresById,
findProjectById,
IS_CLOUD,
@@ -41,10 +42,14 @@ export const postgresRouter = createTRPCRouter({
.input(apiCreatePostgres)
.mutation(async ({ input, ctx }) => {
try {
+ // Get project from environment
+ const environment = await findEnvironmentById(input.environmentId);
+ const project = await findProjectById(environment.projectId);
+
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.user.id,
- input.projectId,
+ project.projectId,
ctx.session.activeOrganizationId,
"create",
);
@@ -57,14 +62,15 @@ export const postgresRouter = createTRPCRouter({
});
}
- const project = await findProjectById(input.projectId);
if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
- const newPostgres = await createPostgres(input);
+ const newPostgres = await createPostgres({
+ ...input,
+ });
if (ctx.user.role === "member") {
await addNewService(
ctx.user.id,
@@ -107,7 +113,8 @@ export const postgresRouter = createTRPCRouter({
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -122,7 +129,10 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const service = await findPostgresById(input.postgresId);
- if (service.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ service.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Postgres",
@@ -145,7 +155,8 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -169,7 +180,8 @@ export const postgresRouter = createTRPCRouter({
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -187,7 +199,8 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -210,7 +223,8 @@ export const postgresRouter = createTRPCRouter({
.subscription(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -229,7 +243,8 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -255,7 +270,8 @@ export const postgresRouter = createTRPCRouter({
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -280,7 +296,8 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -305,7 +322,8 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -337,7 +355,8 @@ export const postgresRouter = createTRPCRouter({
const { postgresId, ...rest } = input;
const postgres = await findPostgresById(postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -361,13 +380,14 @@ export const postgresRouter = createTRPCRouter({
.input(
z.object({
postgresId: z.string(),
- targetProjectId: z.string(),
+ targetEnvironmentId: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -375,11 +395,16 @@ export const postgresRouter = createTRPCRouter({
});
}
- const targetProject = await findProjectById(input.targetProjectId);
- if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
+ const targetEnvironment = await findEnvironmentById(
+ input.targetEnvironmentId,
+ );
+ if (
+ targetEnvironment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not authorized to move to this project",
+ message: "You are not authorized to move to this environment",
});
}
@@ -387,7 +412,7 @@ export const postgresRouter = createTRPCRouter({
const updatedPostgres = await db
.update(postgresTable)
.set({
- projectId: input.targetProjectId,
+ environmentId: input.targetEnvironmentId,
})
.where(eq(postgresTable.postgresId, input.postgresId))
.returning()
@@ -407,7 +432,8 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
if (
- postgres.project.organizationId !== ctx.session.activeOrganizationId
+ postgres.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
diff --git a/apps/dokploy/server/api/routers/preview-deployment.ts b/apps/dokploy/server/api/routers/preview-deployment.ts
index d4cf16f4e..49b781101 100644
--- a/apps/dokploy/server/api/routers/preview-deployment.ts
+++ b/apps/dokploy/server/api/routers/preview-deployment.ts
@@ -15,7 +15,8 @@ export const previewDeploymentRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -31,7 +32,7 @@ export const previewDeploymentRouter = createTRPCRouter({
input.previewDeploymentId,
);
if (
- previewDeployment.application.project.organizationId !==
+ previewDeployment.application.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
@@ -49,7 +50,7 @@ export const previewDeploymentRouter = createTRPCRouter({
input.previewDeploymentId,
);
if (
- previewDeployment.application.project.organizationId !==
+ previewDeployment.application.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts
index 2d1bd5af1..5dd2ee695 100644
--- a/apps/dokploy/server/api/routers/project.ts
+++ b/apps/dokploy/server/api/routers/project.ts
@@ -19,6 +19,7 @@ import {
deleteProject,
findApplicationById,
findComposeById,
+ findEnvironmentById,
findMariadbById,
findMemberById,
findMongoById,
@@ -43,6 +44,7 @@ import {
apiUpdateProject,
applications,
compose,
+ environments,
mariadb,
mongo,
mysql,
@@ -80,7 +82,7 @@ export const projectRouter = createTRPCRouter({
if (ctx.user.role === "member") {
await addNewProject(
ctx.user.id,
- project.projectId,
+ project.project.projectId,
ctx.session.activeOrganizationId,
);
}
@@ -117,29 +119,42 @@ export const projectRouter = createTRPCRouter({
eq(projects.organizationId, ctx.session.activeOrganizationId),
),
with: {
- applications: {
- where: buildServiceFilter(
- applications.applicationId,
- accessedServices,
- ),
- },
- compose: {
- where: buildServiceFilter(compose.composeId, accessedServices),
- },
- mariadb: {
- where: buildServiceFilter(mariadb.mariadbId, accessedServices),
- },
- mongo: {
- where: buildServiceFilter(mongo.mongoId, accessedServices),
- },
- mysql: {
- where: buildServiceFilter(mysql.mysqlId, accessedServices),
- },
- postgres: {
- where: buildServiceFilter(postgres.postgresId, accessedServices),
- },
- redis: {
- where: buildServiceFilter(redis.redisId, accessedServices),
+ environments: {
+ with: {
+ applications: {
+ where: buildServiceFilter(
+ applications.applicationId,
+ accessedServices,
+ ),
+ },
+ compose: {
+ where: buildServiceFilter(
+ compose.composeId,
+ accessedServices,
+ ),
+ },
+ mariadb: {
+ where: buildServiceFilter(
+ mariadb.mariadbId,
+ accessedServices,
+ ),
+ },
+ mongo: {
+ where: buildServiceFilter(mongo.mongoId, accessedServices),
+ },
+ mysql: {
+ where: buildServiceFilter(mysql.mysqlId, accessedServices),
+ },
+ postgres: {
+ where: buildServiceFilter(
+ postgres.postgresId,
+ accessedServices,
+ ),
+ },
+ redis: {
+ where: buildServiceFilter(redis.redisId, accessedServices),
+ },
+ },
},
},
});
@@ -164,15 +179,22 @@ export const projectRouter = createTRPCRouter({
}),
all: protectedProcedure.query(async ({ ctx }) => {
if (ctx.user.role === "member") {
- const { accessedProjects, accessedServices } = await findMemberById(
- ctx.user.id,
- ctx.session.activeOrganizationId,
- );
+ const { accessedProjects, accessedEnvironments, accessedServices } =
+ await findMemberById(ctx.user.id, ctx.session.activeOrganizationId);
if (accessedProjects.length === 0) {
return [];
}
+ // Build environment filter
+ const environmentFilter =
+ accessedEnvironments.length === 0
+ ? sql`false`
+ : sql`${environments.environmentId} IN (${sql.join(
+ accessedEnvironments.map((envId) => sql`${envId}`),
+ sql`, `,
+ )})`;
+
return await db.query.projects.findMany({
where: and(
sql`${projects.projectId} IN (${sql.join(
@@ -182,31 +204,39 @@ export const projectRouter = createTRPCRouter({
eq(projects.organizationId, ctx.session.activeOrganizationId),
),
with: {
- applications: {
- where: buildServiceFilter(
- applications.applicationId,
- accessedServices,
- ),
- with: { domains: true },
- },
- mariadb: {
- where: buildServiceFilter(mariadb.mariadbId, accessedServices),
- },
- mongo: {
- where: buildServiceFilter(mongo.mongoId, accessedServices),
- },
- mysql: {
- where: buildServiceFilter(mysql.mysqlId, accessedServices),
- },
- postgres: {
- where: buildServiceFilter(postgres.postgresId, accessedServices),
- },
- redis: {
- where: buildServiceFilter(redis.redisId, accessedServices),
- },
- compose: {
- where: buildServiceFilter(compose.composeId, accessedServices),
- with: { domains: true },
+ environments: {
+ where: environmentFilter,
+ with: {
+ applications: {
+ where: buildServiceFilter(
+ applications.applicationId,
+ accessedServices,
+ ),
+ with: { domains: true },
+ },
+ mariadb: {
+ where: buildServiceFilter(mariadb.mariadbId, accessedServices),
+ },
+ mongo: {
+ where: buildServiceFilter(mongo.mongoId, accessedServices),
+ },
+ mysql: {
+ where: buildServiceFilter(mysql.mysqlId, accessedServices),
+ },
+ postgres: {
+ where: buildServiceFilter(
+ postgres.postgresId,
+ accessedServices,
+ ),
+ },
+ redis: {
+ where: buildServiceFilter(redis.redisId, accessedServices),
+ },
+ compose: {
+ where: buildServiceFilter(compose.composeId, accessedServices),
+ with: { domains: true },
+ },
+ },
},
},
orderBy: desc(projects.createdAt),
@@ -215,19 +245,23 @@ export const projectRouter = createTRPCRouter({
return await db.query.projects.findMany({
with: {
- applications: {
+ environments: {
with: {
- domains: true,
- },
- },
- mariadb: true,
- mongo: true,
- mysql: true,
- postgres: true,
- redis: true,
- compose: {
- with: {
- domains: true,
+ applications: {
+ with: {
+ domains: true,
+ },
+ },
+ mariadb: true,
+ mongo: true,
+ mysql: true,
+ postgres: true,
+ redis: true,
+ compose: {
+ with: {
+ domains: true,
+ },
+ },
},
},
},
@@ -288,7 +322,7 @@ export const projectRouter = createTRPCRouter({
duplicate: protectedProcedure
.input(
z.object({
- sourceProjectId: z.string(),
+ sourceEnvironmentId: z.string(),
name: z.string(),
description: z.string().optional(),
includeServices: z.boolean().default(true),
@@ -322,9 +356,15 @@ export const projectRouter = createTRPCRouter({
}
// Get source project
- const sourceProject = await findProjectById(input.sourceProjectId);
+ const sourceEnvironment = input.duplicateInSameProject
+ ? await findEnvironmentById(input.sourceEnvironmentId)
+ : null;
- if (sourceProject.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ input.duplicateInSameProject &&
+ sourceEnvironment?.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -333,15 +373,17 @@ export const projectRouter = createTRPCRouter({
// Create new project or use existing one
const targetProject = input.duplicateInSameProject
- ? sourceProject
+ ? sourceEnvironment
: await createProject(
{
name: input.name,
description: input.description,
- env: sourceProject.env,
+ env: sourceEnvironment?.project.env,
},
ctx.session.activeOrganizationId,
- );
+ ).then((value) => value.environment);
+
+ console.log("targetProject", targetProject);
if (input.includeServices) {
const servicesToDuplicate = input.selectedServices || [];
@@ -374,7 +416,7 @@ export const projectRouter = createTRPCRouter({
name: input.duplicateInSameProject
? `${application.name} (copy)`
: application.name,
- projectId: targetProject.projectId,
+ environmentId: targetProject?.environmentId || "",
});
for (const domain of domains) {
@@ -444,7 +486,7 @@ export const projectRouter = createTRPCRouter({
name: input.duplicateInSameProject
? `${postgres.name} (copy)`
: postgres.name,
- projectId: targetProject.projectId,
+ environmentId: targetProject?.environmentId || "",
});
for (const mount of mounts) {
@@ -480,7 +522,7 @@ export const projectRouter = createTRPCRouter({
name: input.duplicateInSameProject
? `${mariadb.name} (copy)`
: mariadb.name,
- projectId: targetProject.projectId,
+ environmentId: targetProject?.environmentId || "",
});
for (const mount of mounts) {
@@ -516,7 +558,7 @@ export const projectRouter = createTRPCRouter({
name: input.duplicateInSameProject
? `${mongo.name} (copy)`
: mongo.name,
- projectId: targetProject.projectId,
+ environmentId: targetProject?.environmentId || "",
});
for (const mount of mounts) {
@@ -552,7 +594,7 @@ export const projectRouter = createTRPCRouter({
name: input.duplicateInSameProject
? `${mysql.name} (copy)`
: mysql.name,
- projectId: targetProject.projectId,
+ environmentId: targetProject?.environmentId || "",
});
for (const mount of mounts) {
@@ -588,7 +630,7 @@ export const projectRouter = createTRPCRouter({
name: input.duplicateInSameProject
? `${redis.name} (copy)`
: redis.name,
- projectId: targetProject.projectId,
+ environmentId: targetProject?.environmentId || "",
});
for (const mount of mounts) {
@@ -623,7 +665,7 @@ export const projectRouter = createTRPCRouter({
name: input.duplicateInSameProject
? `${compose.name} (copy)`
: compose.name,
- projectId: targetProject.projectId,
+ environmentId: targetProject?.environmentId || "",
});
for (const mount of mounts) {
@@ -658,7 +700,7 @@ export const projectRouter = createTRPCRouter({
if (!input.duplicateInSameProject && ctx.user.role === "member") {
await addNewProject(
ctx.user.id,
- targetProject.projectId,
+ targetProject?.projectId || "",
ctx.session.activeOrganizationId,
);
}
diff --git a/apps/dokploy/server/api/routers/redirects.ts b/apps/dokploy/server/api/routers/redirects.ts
index 6c002262d..f8b7014f2 100644
--- a/apps/dokploy/server/api/routers/redirects.ts
+++ b/apps/dokploy/server/api/routers/redirects.ts
@@ -19,7 +19,8 @@ export const redirectsRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -34,7 +35,8 @@ export const redirectsRouter = createTRPCRouter({
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -49,7 +51,8 @@ export const redirectsRouter = createTRPCRouter({
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -64,7 +67,8 @@ export const redirectsRouter = createTRPCRouter({
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts
index ad1ade43d..d377b1707 100644
--- a/apps/dokploy/server/api/routers/redis.ts
+++ b/apps/dokploy/server/api/routers/redis.ts
@@ -4,6 +4,7 @@ import {
createMount,
createRedis,
deployRedis,
+ findEnvironmentById,
findProjectById,
findRedisById,
IS_CLOUD,
@@ -40,10 +41,14 @@ export const redisRouter = createTRPCRouter({
.input(apiCreateRedis)
.mutation(async ({ input, ctx }) => {
try {
+ // Get project from environment
+ const environment = await findEnvironmentById(input.environmentId);
+ const project = await findProjectById(environment.projectId);
+
if (ctx.user.role === "member") {
await checkServiceAccess(
ctx.user.id,
- input.projectId,
+ project.projectId,
ctx.session.activeOrganizationId,
"create",
);
@@ -56,14 +61,15 @@ export const redisRouter = createTRPCRouter({
});
}
- const project = await findProjectById(input.projectId);
if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
- const newRedis = await createRedis(input);
+ const newRedis = await createRedis({
+ ...input,
+ });
if (ctx.user.role === "member") {
await addNewService(
ctx.user.id,
@@ -98,7 +104,10 @@ export const redisRouter = createTRPCRouter({
}
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this Redis",
@@ -111,7 +120,10 @@ export const redisRouter = createTRPCRouter({
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Redis",
@@ -133,7 +145,10 @@ export const redisRouter = createTRPCRouter({
.input(apiResetRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this Redis",
@@ -163,7 +178,10 @@ export const redisRouter = createTRPCRouter({
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this Redis",
@@ -184,7 +202,10 @@ export const redisRouter = createTRPCRouter({
.input(apiSaveExternalPortRedis)
.mutation(async ({ input, ctx }) => {
const mongo = await findRedisById(input.redisId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -200,7 +221,10 @@ export const redisRouter = createTRPCRouter({
.input(apiDeployRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Redis",
@@ -220,7 +244,10 @@ export const redisRouter = createTRPCRouter({
.input(apiDeployRedis)
.subscription(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Redis",
@@ -236,7 +263,10 @@ export const redisRouter = createTRPCRouter({
.input(apiChangeRedisStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findRedisById(input.redisId);
- if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ mongo.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this Redis status",
@@ -261,7 +291,10 @@ export const redisRouter = createTRPCRouter({
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this Redis",
@@ -284,7 +317,10 @@ export const redisRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -324,23 +360,31 @@ export const redisRouter = createTRPCRouter({
.input(
z.object({
redisId: z.string(),
- targetProjectId: z.string(),
+ targetEnvironmentId: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to move this redis",
});
}
- const targetProject = await findProjectById(input.targetProjectId);
- if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
+ const targetEnvironment = await findEnvironmentById(
+ input.targetEnvironmentId,
+ );
+ if (
+ targetEnvironment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not authorized to move to this project",
+ message: "You are not authorized to move to this environment",
});
}
@@ -348,7 +392,7 @@ export const redisRouter = createTRPCRouter({
const updatedRedis = await db
.update(redisTable)
.set({
- projectId: input.targetProjectId,
+ environmentId: input.targetEnvironmentId,
})
.where(eq(redisTable.redisId, input.redisId))
.returning()
@@ -367,7 +411,10 @@ export const redisRouter = createTRPCRouter({
.input(apiRebuildRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
+ if (
+ redis.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to rebuild this Redis database",
diff --git a/apps/dokploy/server/api/routers/rollbacks.ts b/apps/dokploy/server/api/routers/rollbacks.ts
index b8b904172..d9e6180fb 100644
--- a/apps/dokploy/server/api/routers/rollbacks.ts
+++ b/apps/dokploy/server/api/routers/rollbacks.ts
@@ -1,4 +1,8 @@
-import { removeRollbackById, rollback } from "@dokploy/server";
+import {
+ findRollbackById,
+ removeRollbackById,
+ rollback,
+} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { apiFindOneRollback } from "@/server/db/schema";
@@ -22,8 +26,18 @@ export const rollbackRouter = createTRPCRouter({
}),
rollback: protectedProcedure
.input(apiFindOneRollback)
- .mutation(async ({ input }) => {
+ .mutation(async ({ input, ctx }) => {
try {
+ const currentRollback = await findRollbackById(input.rollbackId);
+ if (
+ currentRollback?.deployment?.application?.environment?.project
+ .organizationId !== ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to rollback this deployment",
+ });
+ }
return await rollback(input.rollbackId);
} catch (error) {
console.error(error);
diff --git a/apps/dokploy/server/api/routers/security.ts b/apps/dokploy/server/api/routers/security.ts
index d973ecc8d..3d8374b4c 100644
--- a/apps/dokploy/server/api/routers/security.ts
+++ b/apps/dokploy/server/api/routers/security.ts
@@ -19,7 +19,8 @@ export const securityRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -34,7 +35,8 @@ export const securityRouter = createTRPCRouter({
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -49,7 +51,8 @@ export const securityRouter = createTRPCRouter({
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -64,7 +67,8 @@ export const securityRouter = createTRPCRouter({
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
if (
- application.project.organizationId !== ctx.session.activeOrganizationId
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
diff --git a/apps/dokploy/server/db/index.ts b/apps/dokploy/server/db/index.ts
index 3ac6e3940..55d6d3a46 100644
--- a/apps/dokploy/server/db/index.ts
+++ b/apps/dokploy/server/db/index.ts
@@ -6,14 +6,18 @@ declare global {
var db: PostgresJsDatabase | undefined;
}
+const dbUrl =
+ process.env.DATABASE_URL ||
+ "postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy";
+
export let db: PostgresJsDatabase;
if (process.env.NODE_ENV === "production") {
- db = drizzle(postgres(process.env.DATABASE_URL!), {
+ db = drizzle(postgres(dbUrl!), {
schema,
});
} else {
if (!global.db)
- global.db = drizzle(postgres(process.env.DATABASE_URL!), {
+ global.db = drizzle(postgres(dbUrl!), {
schema,
});
diff --git a/apps/dokploy/server/db/seed.ts b/apps/dokploy/server/db/seed.ts
deleted file mode 100644
index 5b3eb6c62..000000000
--- a/apps/dokploy/server/db/seed.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { drizzle } from "drizzle-orm/postgres-js";
-import postgres from "postgres";
-
-const connectionString = process.env.DATABASE_URL!;
-
-const pg = postgres(connectionString, { max: 1 });
-const _db = drizzle(pg);
-
-async function seed() {
- console.log("> Seed:", process.env.DATABASE_PATH, "\n");
-
- // const authenticationR = await db
- // .insert(users)
- // .values([
- // {
- // email: "user1@hotmail.com",
- // password: password("12345671"),
- // },
- // ])
- // .onConflictDoNothing()
- // .returning();
-
- // console.log("\nSemillas Update:", authenticationR.length);
-}
-
-seed().catch((e) => {
- console.error(e);
- process.exit(1);
-});
diff --git a/packages/server/package.json b/packages/server/package.json
index dbf7d3c65..3b249a65b 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -111,4 +111,4 @@
"node": "^20.16.0",
"pnpm": ">=9.12.0"
}
-}
+}
\ No newline at end of file
diff --git a/packages/server/src/db/drizzle.config.ts b/packages/server/src/db/drizzle.config.ts
deleted file mode 100644
index 60a3bb937..000000000
--- a/packages/server/src/db/drizzle.config.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { defineConfig } from "drizzle-kit";
-
-export default defineConfig({
- schema: "./server/db/schema/index.ts",
- dialect: "postgresql",
- dbCredentials: {
- url: process.env.DATABASE_URL!,
- },
- out: "drizzle",
- migrations: {
- table: "migrations",
- schema: "public",
- },
-});
diff --git a/packages/server/src/db/migration.ts b/packages/server/src/db/migration.ts
deleted file mode 100644
index 6fada0833..000000000
--- a/packages/server/src/db/migration.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// import { drizzle } from "drizzle-orm/postgres-js";
-// import { migrate } from "drizzle-orm/postgres-js/migrator";
-// import postgres from "postgres";
-
-// const connectionString = process.env.DATABASE_URL!;
-
-// const sql = postgres(connectionString, { max: 1 });
-// const db = drizzle(sql);
-
-// export const migration = async () =>
-// await migrate(db, { migrationsFolder: "drizzle" })
-// .then(() => {
-// console.log("Migration complete");
-// sql.end();
-// })
-// .catch((error) => {
-// console.log("Migration failed", error);
-// })
-// .finally(() => {
-// sql.end();
-// });
diff --git a/packages/server/src/db/reset.ts b/packages/server/src/db/reset.ts
deleted file mode 100644
index c22291478..000000000
--- a/packages/server/src/db/reset.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { sql } from "drizzle-orm";
-// Credits to Louistiti from Drizzle Discord: https://discord.com/channels/1043890932593987624/1130802621750448160/1143083373535973406
-import { drizzle } from "drizzle-orm/postgres-js";
-import postgres from "postgres";
-
-const connectionString = process.env.DATABASE_URL!;
-
-const pg = postgres(connectionString, { max: 1 });
-const db = drizzle(pg);
-
-const clearDb = async (): Promise => {
- try {
- const tablesQuery = sql`DROP SCHEMA public CASCADE; CREATE SCHEMA public; DROP schema drizzle CASCADE;`;
- const tables = await db.execute(tablesQuery);
- console.log(tables);
- await pg.end();
- } catch (error) {
- console.error("Error cleaning database", error);
- } finally {
- }
-};
-
-clearDb();
diff --git a/packages/server/src/db/schema/account.ts b/packages/server/src/db/schema/account.ts
index 8291ea4d6..3eb57b552 100644
--- a/packages/server/src/db/schema/account.ts
+++ b/packages/server/src/db/schema/account.ts
@@ -112,6 +112,10 @@ export const member = pgTable("member", {
.array()
.notNull()
.default(sql`ARRAY[]::text[]`),
+ accessedEnvironments: text("accessedEnvironments")
+ .array()
+ .notNull()
+ .default(sql`ARRAY[]::text[]`),
accessedServices: text("accesedServices")
.array()
.notNull()
diff --git a/packages/server/src/db/schema/ai.ts b/packages/server/src/db/schema/ai.ts
index 0ee5af4bd..558f2648e 100644
--- a/packages/server/src/db/schema/ai.ts
+++ b/packages/server/src/db/schema/ai.ts
@@ -55,7 +55,7 @@ export const apiUpdateAi = createSchema
.omit({ organizationId: true });
export const deploySuggestionSchema = z.object({
- projectId: z.string().min(1),
+ environmentId: z.string().min(1),
id: z.string().min(1),
dockerCompose: z.string().min(1),
envVariables: z.string(),
diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts
index 198611a77..6d176e737 100644
--- a/packages/server/src/db/schema/application.ts
+++ b/packages/server/src/db/schema/application.ts
@@ -13,6 +13,7 @@ import { z } from "zod";
import { bitbucket } from "./bitbucket";
import { deployments } from "./deployment";
import { domains } from "./domain";
+import { environments } from "./environment";
import { gitea } from "./gitea";
import { github } from "./github";
import { gitlab } from "./gitlab";
@@ -179,9 +180,9 @@ export const applications = pgTable("application", {
registryId: text("registryId").references(() => registry.registryId, {
onDelete: "set null",
}),
- projectId: text("projectId")
+ environmentId: text("environmentId")
.notNull()
- .references(() => projects.projectId, { onDelete: "cascade" }),
+ .references(() => environments.environmentId, { onDelete: "cascade" }),
githubId: text("githubId").references(() => github.githubId, {
onDelete: "set null",
}),
@@ -202,9 +203,9 @@ export const applications = pgTable("application", {
export const applicationsRelations = relations(
applications,
({ one, many }) => ({
- project: one(projects, {
- fields: [applications.projectId],
- references: [projects.projectId],
+ environment: one(environments, {
+ fields: [applications.environmentId],
+ references: [environments.environmentId],
}),
deployments: many(deployments),
customGitSSHKey: one(sshKeys, {
@@ -273,7 +274,7 @@ const createSchema = createInsertSchema(applications, {
customGitBuildPath: z.string().optional(),
customGitUrl: z.string().optional(),
buildPath: z.string().optional(),
- projectId: z.string(),
+ environmentId: z.string(),
sourceType: z
.enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"])
.optional(),
@@ -317,7 +318,7 @@ export const apiCreateApplication = createSchema.pick({
name: true,
appName: true,
description: true,
- projectId: true,
+ environmentId: true,
serverId: true,
});
@@ -327,6 +328,26 @@ export const apiFindOneApplication = createSchema
})
.required();
+export const apiDeployApplication = createSchema
+ .pick({
+ applicationId: true,
+ })
+ .extend({
+ applicationId: z.string().min(1),
+ title: z.string().optional(),
+ description: z.string().optional(),
+ });
+
+export const apiRedeployApplication = createSchema
+ .pick({
+ applicationId: true,
+ })
+ .extend({
+ applicationId: z.string().min(1),
+ title: z.string().optional(),
+ description: z.string().optional(),
+ });
+
export const apiReloadApplication = createSchema
.pick({
appName: true,
diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts
index 57d8d9f1e..958c2c32c 100644
--- a/packages/server/src/db/schema/compose.ts
+++ b/packages/server/src/db/schema/compose.ts
@@ -7,6 +7,7 @@ import { backups } from "./backups";
import { bitbucket } from "./bitbucket";
import { deployments } from "./deployment";
import { domains } from "./domain";
+import { environments } from "./environment";
import { gitea } from "./gitea";
import { github } from "./github";
import { gitlab } from "./gitlab";
@@ -84,9 +85,9 @@ export const compose = pgTable("compose", {
.default(false),
triggerType: triggerType("triggerType").default("push"),
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
- projectId: text("projectId")
+ environmentId: text("environmentId")
.notNull()
- .references(() => projects.projectId, { onDelete: "cascade" }),
+ .references(() => environments.environmentId, { onDelete: "cascade" }),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@@ -109,9 +110,9 @@ export const compose = pgTable("compose", {
});
export const composeRelations = relations(compose, ({ one, many }) => ({
- project: one(projects, {
- fields: [compose.projectId],
- references: [projects.projectId],
+ environment: one(environments, {
+ fields: [compose.environmentId],
+ references: [environments.environmentId],
}),
deployments: many(deployments),
mounts: many(mounts),
@@ -149,7 +150,7 @@ const createSchema = createInsertSchema(compose, {
description: z.string(),
env: z.string().optional(),
composeFile: z.string().optional(),
- projectId: z.string(),
+ environmentId: z.string(),
customGitSSHKeyId: z.string().optional(),
command: z.string().optional(),
composePath: z.string().min(1),
@@ -160,7 +161,7 @@ const createSchema = createInsertSchema(compose, {
export const apiCreateCompose = createSchema.pick({
name: true,
description: true,
- projectId: true,
+ environmentId: true,
composeType: true,
appName: true,
serverId: true,
@@ -169,7 +170,7 @@ export const apiCreateCompose = createSchema.pick({
export const apiCreateComposeByTemplate = createSchema
.pick({
- projectId: true,
+ environmentId: true,
})
.extend({
id: z.string().min(1),
@@ -180,6 +181,18 @@ export const apiFindCompose = z.object({
composeId: z.string().min(1),
});
+export const apiDeployCompose = z.object({
+ composeId: z.string().min(1),
+ title: z.string().optional(),
+ description: z.string().optional(),
+});
+
+export const apiRedeployCompose = z.object({
+ composeId: z.string().min(1),
+ title: z.string().optional(),
+ description: z.string().optional(),
+});
+
export const apiDeleteCompose = z.object({
composeId: z.string().min(1),
deleteVolumes: z.boolean(),
diff --git a/packages/server/src/db/schema/environment.ts b/packages/server/src/db/schema/environment.ts
new file mode 100644
index 000000000..a284a2264
--- /dev/null
+++ b/packages/server/src/db/schema/environment.ts
@@ -0,0 +1,85 @@
+import { relations } from "drizzle-orm";
+import { pgTable, text } from "drizzle-orm/pg-core";
+import { createInsertSchema } from "drizzle-zod";
+import { nanoid } from "nanoid";
+import { z } from "zod";
+import { applications } from "./application";
+import { compose } from "./compose";
+import { mariadb } from "./mariadb";
+import { mongo } from "./mongo";
+import { mysql } from "./mysql";
+import { postgres } from "./postgres";
+import { projects } from "./project";
+import { redis } from "./redis";
+
+export const environments = pgTable("environment", {
+ environmentId: text("environmentId")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ name: text("name").notNull(),
+ description: text("description"),
+ createdAt: text("createdAt")
+ .notNull()
+ .$defaultFn(() => new Date().toISOString()),
+ env: text("env").notNull().default(""),
+ projectId: text("projectId")
+ .notNull()
+ .references(() => projects.projectId, { onDelete: "cascade" }),
+});
+
+export const environmentRelations = relations(
+ environments,
+ ({ one, many }) => ({
+ project: one(projects, {
+ fields: [environments.projectId],
+ references: [projects.projectId],
+ }),
+ applications: many(applications),
+ mariadb: many(mariadb),
+ postgres: many(postgres),
+ mysql: many(mysql),
+ redis: many(redis),
+ mongo: many(mongo),
+ compose: many(compose),
+ }),
+);
+
+const createSchema = createInsertSchema(environments, {
+ environmentId: z.string().min(1),
+ name: z.string().min(1),
+ description: z.string().optional(),
+});
+
+export const apiCreateEnvironment = createSchema.pick({
+ name: true,
+ description: true,
+ projectId: true,
+});
+
+export const apiFindOneEnvironment = createSchema
+ .pick({
+ environmentId: true,
+ })
+ .required();
+
+export const apiRemoveEnvironment = createSchema
+ .pick({
+ environmentId: true,
+ })
+ .required();
+
+export const apiUpdateEnvironment = createSchema.partial().extend({
+ environmentId: z.string().min(1),
+});
+
+export const apiDuplicateEnvironment = createSchema
+ .pick({
+ environmentId: true,
+ name: true,
+ description: true,
+ })
+ .required({
+ environmentId: true,
+ name: true,
+ });
diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts
index 67f145c19..c16ef1452 100644
--- a/packages/server/src/db/schema/index.ts
+++ b/packages/server/src/db/schema/index.ts
@@ -8,6 +8,7 @@ export * from "./compose";
export * from "./deployment";
export * from "./destination";
export * from "./domain";
+export * from "./environment";
export * from "./git-provider";
export * from "./gitea";
export * from "./github";
diff --git a/packages/server/src/db/schema/mariadb.ts b/packages/server/src/db/schema/mariadb.ts
index 039836d77..416c66e1c 100644
--- a/packages/server/src/db/schema/mariadb.ts
+++ b/packages/server/src/db/schema/mariadb.ts
@@ -4,8 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { backups } from "./backups";
+import { environments } from "./environment";
import { mounts } from "./mount";
-import { projects } from "./project";
import { server } from "./server";
import {
applicationStatus,
@@ -66,18 +66,19 @@ export const mariadb = pgTable("mariadb", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- projectId: text("projectId")
+
+ environmentId: text("environmentId")
.notNull()
- .references(() => projects.projectId, { onDelete: "cascade" }),
+ .references(() => environments.environmentId, { onDelete: "cascade" }),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
- project: one(projects, {
- fields: [mariadb.projectId],
- references: [projects.projectId],
+ environment: one(environments, {
+ fields: [mariadb.environmentId],
+ references: [environments.environmentId],
}),
backups: many(backups),
mounts: many(mounts),
@@ -94,8 +95,19 @@ const createSchema = createInsertSchema(mariadb, {
createdAt: z.string(),
databaseName: z.string().min(1),
databaseUser: z.string().min(1),
- databasePassword: z.string(),
- databaseRootPassword: z.string().optional(),
+ databasePassword: z
+ .string()
+ .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
+ message:
+ "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
+ }),
+ databaseRootPassword: z
+ .string()
+ .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
+ message:
+ "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
+ })
+ .optional(),
dockerImage: z.string().default("mariadb:6"),
command: z.string().optional(),
env: z.string().optional(),
@@ -103,7 +115,7 @@ const createSchema = createInsertSchema(mariadb, {
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
- projectId: z.string(),
+ environmentId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
description: z.string().optional(),
@@ -124,7 +136,7 @@ export const apiCreateMariaDB = createSchema
appName: true,
dockerImage: true,
databaseRootPassword: true,
- projectId: true,
+ environmentId: true,
description: true,
databaseName: true,
databaseUser: true,
diff --git a/packages/server/src/db/schema/mongo.ts b/packages/server/src/db/schema/mongo.ts
index eb6103a36..eb4661066 100644
--- a/packages/server/src/db/schema/mongo.ts
+++ b/packages/server/src/db/schema/mongo.ts
@@ -4,8 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { backups } from "./backups";
+import { environments } from "./environment";
import { mounts } from "./mount";
-import { projects } from "./project";
import { server } from "./server";
import {
applicationStatus,
@@ -62,9 +62,10 @@ export const mongo = pgTable("mongo", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- projectId: text("projectId")
+
+ environmentId: text("environmentId")
.notNull()
- .references(() => projects.projectId, { onDelete: "cascade" }),
+ .references(() => environments.environmentId, { onDelete: "cascade" }),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
@@ -72,9 +73,9 @@ export const mongo = pgTable("mongo", {
});
export const mongoRelations = relations(mongo, ({ one, many }) => ({
- project: one(projects, {
- fields: [mongo.projectId],
- references: [projects.projectId],
+ environment: one(environments, {
+ fields: [mongo.environmentId],
+ references: [environments.environmentId],
}),
backups: many(backups),
mounts: many(mounts),
@@ -89,7 +90,12 @@ const createSchema = createInsertSchema(mongo, {
createdAt: z.string(),
mongoId: z.string(),
name: z.string().min(1),
- databasePassword: z.string(),
+ databasePassword: z
+ .string()
+ .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
+ message:
+ "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
+ }),
databaseUser: z.string().min(1),
dockerImage: z.string().default("mongo:15"),
command: z.string().optional(),
@@ -98,7 +104,7 @@ const createSchema = createInsertSchema(mongo, {
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
- projectId: z.string(),
+ environmentId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
description: z.string().optional(),
@@ -119,7 +125,7 @@ export const apiCreateMongo = createSchema
name: true,
appName: true,
dockerImage: true,
- projectId: true,
+ environmentId: true,
description: true,
databaseUser: true,
databasePassword: true,
diff --git a/packages/server/src/db/schema/mysql.ts b/packages/server/src/db/schema/mysql.ts
index 03d360b3d..8f87bff1e 100644
--- a/packages/server/src/db/schema/mysql.ts
+++ b/packages/server/src/db/schema/mysql.ts
@@ -4,8 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { backups } from "./backups";
+import { environments } from "./environment";
import { mounts } from "./mount";
-import { projects } from "./project";
import { server } from "./server";
import {
applicationStatus,
@@ -64,18 +64,19 @@ export const mysql = pgTable("mysql", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- projectId: text("projectId")
+
+ environmentId: text("environmentId")
.notNull()
- .references(() => projects.projectId, { onDelete: "cascade" }),
+ .references(() => environments.environmentId, { onDelete: "cascade" }),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
export const mysqlRelations = relations(mysql, ({ one, many }) => ({
- project: one(projects, {
- fields: [mysql.projectId],
- references: [projects.projectId],
+ environment: one(environments, {
+ fields: [mysql.environmentId],
+ references: [environments.environmentId],
}),
backups: many(backups),
mounts: many(mounts),
@@ -92,8 +93,19 @@ const createSchema = createInsertSchema(mysql, {
name: z.string().min(1),
databaseName: z.string().min(1),
databaseUser: z.string().min(1),
- databasePassword: z.string(),
- databaseRootPassword: z.string().optional(),
+ databasePassword: z
+ .string()
+ .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
+ message:
+ "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
+ }),
+ databaseRootPassword: z
+ .string()
+ .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
+ message:
+ "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
+ })
+ .optional(),
dockerImage: z.string().default("mysql:8"),
command: z.string().optional(),
env: z.string().optional(),
@@ -101,7 +113,6 @@ const createSchema = createInsertSchema(mysql, {
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
- projectId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
description: z.string().optional(),
@@ -121,7 +132,7 @@ export const apiCreateMySql = createSchema
name: true,
appName: true,
dockerImage: true,
- projectId: true,
+ environmentId: true,
description: true,
databaseName: true,
databaseUser: true,
diff --git a/packages/server/src/db/schema/postgres.ts b/packages/server/src/db/schema/postgres.ts
index df0202094..961371b5c 100644
--- a/packages/server/src/db/schema/postgres.ts
+++ b/packages/server/src/db/schema/postgres.ts
@@ -4,8 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { backups } from "./backups";
+import { environments } from "./environment";
import { mounts } from "./mount";
-import { projects } from "./project";
import { server } from "./server";
import {
applicationStatus,
@@ -64,18 +64,19 @@ export const postgres = pgTable("postgres", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- projectId: text("projectId")
+
+ environmentId: text("environmentId")
.notNull()
- .references(() => projects.projectId, { onDelete: "cascade" }),
+ .references(() => environments.environmentId, { onDelete: "cascade" }),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
export const postgresRelations = relations(postgres, ({ one, many }) => ({
- project: one(projects, {
- fields: [postgres.projectId],
- references: [projects.projectId],
+ environment: one(environments, {
+ fields: [postgres.environmentId],
+ references: [environments.environmentId],
}),
backups: many(backups),
mounts: many(mounts),
@@ -88,7 +89,12 @@ export const postgresRelations = relations(postgres, ({ one, many }) => ({
const createSchema = createInsertSchema(postgres, {
postgresId: z.string(),
name: z.string().min(1),
- databasePassword: z.string(),
+ databasePassword: z
+ .string()
+ .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
+ message:
+ "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
+ }),
databaseName: z.string().min(1),
databaseUser: z.string().min(1),
dockerImage: z.string().default("postgres:15"),
@@ -98,7 +104,7 @@ const createSchema = createInsertSchema(postgres, {
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
- projectId: z.string(),
+ environmentId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
createdAt: z.string(),
@@ -122,7 +128,7 @@ export const apiCreatePostgres = createSchema
databaseUser: true,
databasePassword: true,
dockerImage: true,
- projectId: true,
+ environmentId: true,
description: true,
serverId: true,
})
diff --git a/packages/server/src/db/schema/project.ts b/packages/server/src/db/schema/project.ts
index e40b362f9..abba26a7d 100644
--- a/packages/server/src/db/schema/project.ts
+++ b/packages/server/src/db/schema/project.ts
@@ -4,13 +4,7 @@ import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
-import { applications } from "./application";
-import { compose } from "./compose";
-import { mariadb } from "./mariadb";
-import { mongo } from "./mongo";
-import { mysql } from "./mysql";
-import { postgres } from "./postgres";
-import { redis } from "./redis";
+import { environments } from "./environment";
export const projects = pgTable("project", {
projectId: text("projectId")
@@ -30,13 +24,7 @@ export const projects = pgTable("project", {
});
export const projectRelations = relations(projects, ({ many, one }) => ({
- mysql: many(mysql),
- postgres: many(postgres),
- mariadb: many(mariadb),
- applications: many(applications),
- mongo: many(mongo),
- redis: many(redis),
- compose: many(compose),
+ environments: many(environments),
organization: one(organization, {
fields: [projects.organizationId],
references: [organization.id],
diff --git a/packages/server/src/db/schema/redis.ts b/packages/server/src/db/schema/redis.ts
index 1bfa53510..29e40282d 100644
--- a/packages/server/src/db/schema/redis.ts
+++ b/packages/server/src/db/schema/redis.ts
@@ -3,6 +3,7 @@ import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
+import { environments } from "./environment";
import { mounts } from "./mount";
import { projects } from "./project";
import { server } from "./server";
@@ -60,18 +61,19 @@ export const redis = pgTable("redis", {
labelsSwarm: json("labelsSwarm").$type(),
networkSwarm: json("networkSwarm").$type(),
replicas: integer("replicas").default(1).notNull(),
- projectId: text("projectId")
+
+ environmentId: text("environmentId")
.notNull()
- .references(() => projects.projectId, { onDelete: "cascade" }),
+ .references(() => environments.environmentId, { onDelete: "cascade" }),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
export const redisRelations = relations(redis, ({ one, many }) => ({
- project: one(projects, {
- fields: [redis.projectId],
- references: [projects.projectId],
+ environment: one(environments, {
+ fields: [redis.environmentId],
+ references: [environments.environmentId],
}),
mounts: many(mounts),
server: one(server, {
@@ -93,7 +95,7 @@ const createSchema = createInsertSchema(redis, {
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
- projectId: z.string(),
+ environmentId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
description: z.string().optional(),
@@ -114,7 +116,7 @@ export const apiCreateRedis = createSchema
appName: true,
databasePassword: true,
dockerImage: true,
- projectId: true,
+ environmentId: true,
description: true,
serverId: true,
})
diff --git a/packages/server/src/db/schema/rollbacks.ts b/packages/server/src/db/schema/rollbacks.ts
index 92690dd93..ec27d683f 100644
--- a/packages/server/src/db/schema/rollbacks.ts
+++ b/packages/server/src/db/schema/rollbacks.ts
@@ -27,7 +27,9 @@ export const rollbacks = pgTable("rollback", {
.$defaultFn(() => new Date().toISOString()),
fullContext: jsonb("fullContext").$type<
Application & {
- project: Project;
+ environment: {
+ project: Project;
+ };
mounts: Mount[];
ports: Port[];
registry?: Registry | null;
diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts
index e7fb4cbf4..933a7490c 100644
--- a/packages/server/src/db/schema/user.ts
+++ b/packages/server/src/db/schema/user.ts
@@ -175,6 +175,7 @@ export const apiAssignPermissions = createSchema
})
.extend({
accessedProjects: z.array(z.string()).optional(),
+ accessedEnvironments: z.array(z.string()).optional(),
accessedServices: z.array(z.string()).optional(),
canCreateProjects: z.boolean().optional(),
canCreateServices: z.boolean().optional(),
diff --git a/packages/server/src/db/seed.ts b/packages/server/src/db/seed.ts
deleted file mode 100644
index 7e2736b00..000000000
--- a/packages/server/src/db/seed.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-// import bc from "bcrypt";
-// import { drizzle } from "drizzle-orm/postgres-js";
-// import postgres from "postgres";
-// import { users } from "./schema";
-
-// const connectionString = process.env.DATABASE_URL!;
-
-// const pg = postgres(connectionString, { max: 1 });
-// const db = drizzle(pg);
-
-// function password(txt: string) {
-// return bc.hashSync(txt, 10);
-// }
-
-// async function seed() {
-// console.log("> Seed:", process.env.DATABASE_PATH, "\n");
-
-// // const authenticationR = await db
-// // .insert(users)
-// // .values([
-// // {
-// // email: "user1@hotmail.com",
-// // password: password("12345671"),
-// // },
-// // ])
-// // .onConflictDoNothing()
-// // .returning();
-
-// // console.log("\nSemillas Update:", authenticationR.length);
-// }
-
-// seed().catch((e) => {
-// console.error(e);
-// process.exit(1);
-// });
diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index 775236455..c845dc0bd 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -16,6 +16,7 @@ export * from "./services/deployment";
export * from "./services/destination";
export * from "./services/docker";
export * from "./services/domain";
+export * from "./services/environment";
export * from "./services/git-provider";
export * from "./services/gitea";
export * from "./services/github";
diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts
index a8effba98..e9cde27c5 100644
--- a/packages/server/src/services/application.ts
+++ b/packages/server/src/services/application.ts
@@ -105,7 +105,11 @@ export const findApplicationById = async (applicationId: string) => {
const application = await db.query.applications.findFirst({
where: eq(applications.applicationId, applicationId),
with: {
- project: true,
+ environment: {
+ with: {
+ project: true,
+ },
+ },
domains: true,
deployments: true,
mounts: true,
@@ -180,7 +184,7 @@ export const deployApplication = async ({
}) => {
const application = await findApplicationById(applicationId);
- const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
+ const buildLink = `${await getDokployUrl()}/dashboard/project/${application.environment.projectId}/environment/${application.environmentId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -227,11 +231,11 @@ export const deployApplication = async ({
}
await sendBuildSuccessNotifications({
- projectName: application.project.name,
+ projectName: application.environment.project.name,
applicationName: application.name,
applicationType: "application",
buildLink,
- organizationId: application.project.organizationId,
+ organizationId: application.environment.project.organizationId,
domains: application.domains,
});
} catch (error) {
@@ -239,13 +243,13 @@ export const deployApplication = async ({
await updateApplicationStatus(applicationId, "error");
await sendBuildErrorNotifications({
- projectName: application.project.name,
+ projectName: application.environment.project.name,
applicationName: application.name,
applicationType: "application",
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- organizationId: application.project.organizationId,
+ organizationId: application.environment.project.organizationId,
});
throw error;
@@ -307,7 +311,7 @@ export const deployRemoteApplication = async ({
}) => {
const application = await findApplicationById(applicationId);
- const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
+ const buildLink = `${await getDokployUrl()}/dashboard/project/${application.environment.projectId}/environment/${application.environmentId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -363,11 +367,11 @@ export const deployRemoteApplication = async ({
}
await sendBuildSuccessNotifications({
- projectName: application.project.name,
+ projectName: application.environment.project.name,
applicationName: application.name,
applicationType: "application",
buildLink,
- organizationId: application.project.organizationId,
+ organizationId: application.environment.project.organizationId,
domains: application.domains,
});
} catch (error) {
@@ -387,12 +391,12 @@ export const deployRemoteApplication = async ({
await updateApplicationStatus(applicationId, "error");
await sendBuildErrorNotifications({
- projectName: application.project.name,
+ projectName: application.environment.project.name,
applicationName: application.name,
applicationType: "application",
errorMessage: `Please check the logs for details: ${errorMessage}`,
buildLink,
- organizationId: application.project.organizationId,
+ organizationId: application.environment.project.organizationId,
});
throw error;
diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts
index bb1b2e8a0..bfe8d01d0 100644
--- a/packages/server/src/services/compose.ts
+++ b/packages/server/src/services/compose.ts
@@ -126,7 +126,11 @@ export const findComposeById = async (composeId: string) => {
const result = await db.query.compose.findFirst({
where: eq(compose.composeId, composeId),
with: {
- project: true,
+ environment: {
+ with: {
+ project: true,
+ },
+ },
deployments: true,
mounts: true,
domains: true,
@@ -222,7 +226,7 @@ export const deployCompose = async ({
const compose = await findComposeById(composeId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${
- compose.projectId
+ compose.environment.projectId
}/services/compose/${compose.composeId}?tab=deployments`;
const deployment = await createDeploymentCompose({
composeId: composeId,
@@ -255,11 +259,11 @@ export const deployCompose = async ({
});
await sendBuildSuccessNotifications({
- projectName: compose.project.name,
+ projectName: compose.environment.project.name,
applicationName: compose.name,
applicationType: "compose",
buildLink,
- organizationId: compose.project.organizationId,
+ organizationId: compose.environment.project.organizationId,
domains: compose.domains,
});
} catch (error) {
@@ -268,13 +272,13 @@ export const deployCompose = async ({
composeStatus: "error",
});
await sendBuildErrorNotifications({
- projectName: compose.project.name,
+ projectName: compose.environment.project.name,
applicationName: compose.name,
applicationType: "compose",
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- organizationId: compose.project.organizationId,
+ organizationId: compose.environment.project.organizationId,
});
throw error;
}
@@ -330,7 +334,7 @@ export const deployRemoteCompose = async ({
const compose = await findComposeById(composeId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${
- compose.projectId
+ compose.environment.projectId
}/services/compose/${compose.composeId}?tab=deployments`;
const deployment = await createDeploymentCompose({
composeId: composeId,
@@ -387,11 +391,11 @@ export const deployRemoteCompose = async ({
});
await sendBuildSuccessNotifications({
- projectName: compose.project.name,
+ projectName: compose.environment.project.name,
applicationName: compose.name,
applicationType: "compose",
buildLink,
- organizationId: compose.project.organizationId,
+ organizationId: compose.environment.project.organizationId,
domains: compose.domains,
});
} catch (error) {
@@ -410,13 +414,13 @@ export const deployRemoteCompose = async ({
composeStatus: "error",
});
await sendBuildErrorNotifications({
- projectName: compose.project.name,
+ projectName: compose.environment.project.name,
applicationName: compose.name,
applicationType: "compose",
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- organizationId: compose.project.organizationId,
+ organizationId: compose.environment.project.organizationId,
});
throw error;
}
diff --git a/packages/server/src/services/environment.ts b/packages/server/src/services/environment.ts
new file mode 100644
index 000000000..1d77510be
--- /dev/null
+++ b/packages/server/src/services/environment.ts
@@ -0,0 +1,140 @@
+import { db } from "@dokploy/server/db";
+import {
+ type apiCreateEnvironment,
+ type apiDuplicateEnvironment,
+ environments,
+} from "@dokploy/server/db/schema";
+import { TRPCError } from "@trpc/server";
+import { asc, eq } from "drizzle-orm";
+
+export type Environment = typeof environments.$inferSelect;
+
+export const createEnvironment = async (
+ input: typeof apiCreateEnvironment._type,
+) => {
+ const newEnvironment = await db
+ .insert(environments)
+ .values({
+ ...input,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newEnvironment) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the environment",
+ });
+ }
+
+ return newEnvironment;
+};
+
+export const findEnvironmentById = async (environmentId: string) => {
+ const environment = await db.query.environments.findFirst({
+ where: eq(environments.environmentId, environmentId),
+ with: {
+ applications: true,
+ mariadb: true,
+ mongo: true,
+ mysql: true,
+ postgres: true,
+ redis: true,
+ compose: true,
+ project: true,
+ },
+ });
+ if (!environment) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Environment not found",
+ });
+ }
+ return environment;
+};
+
+export const findEnvironmentsByProjectId = async (projectId: string) => {
+ const projectEnvironments = await db.query.environments.findMany({
+ where: eq(environments.projectId, projectId),
+ orderBy: asc(environments.createdAt),
+ with: {
+ applications: true,
+ mariadb: true,
+ mongo: true,
+ mysql: true,
+ postgres: true,
+ redis: true,
+ compose: true,
+ project: true,
+ },
+ });
+ return projectEnvironments;
+};
+
+export const deleteEnvironment = async (environmentId: string) => {
+ const currentEnvironment = await findEnvironmentById(environmentId);
+ if (currentEnvironment.name === "production") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "You cannot delete the production environment",
+ });
+ }
+ const deletedEnvironment = await db
+ .delete(environments)
+ .where(eq(environments.environmentId, environmentId))
+ .returning()
+ .then((value) => value[0]);
+
+ return deletedEnvironment;
+};
+
+export const updateEnvironmentById = async (
+ environmentId: string,
+ environmentData: Partial,
+) => {
+ const result = await db
+ .update(environments)
+ .set({
+ ...environmentData,
+ })
+ .where(eq(environments.environmentId, environmentId))
+ .returning()
+ .then((res) => res[0]);
+
+ return result;
+};
+
+export const duplicateEnvironment = async (
+ input: typeof apiDuplicateEnvironment._type,
+) => {
+ // Find the original environment
+ const originalEnvironment = await findEnvironmentById(input.environmentId);
+
+ // Create a new environment with the provided name and description
+ const newEnvironment = await db
+ .insert(environments)
+ .values({
+ name: input.name,
+ description: input.description || originalEnvironment.description,
+ projectId: originalEnvironment.projectId,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newEnvironment) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error duplicating the environment",
+ });
+ }
+
+ return newEnvironment;
+};
+
+export const createProductionEnvironment = async (projectId: string) => {
+ return createEnvironment({
+ name: "production",
+ description: "Production environment",
+ projectId,
+ });
+};
diff --git a/packages/server/src/services/mariadb.ts b/packages/server/src/services/mariadb.ts
index b664e1593..8aac45346 100644
--- a/packages/server/src/services/mariadb.ts
+++ b/packages/server/src/services/mariadb.ts
@@ -56,7 +56,11 @@ export const findMariadbById = async (mariadbId: string) => {
const result = await db.query.mariadb.findFirst({
where: eq(mariadb.mariadbId, mariadbId),
with: {
- project: true,
+ environment: {
+ with: {
+ project: true,
+ },
+ },
mounts: true,
server: true,
backups: {
diff --git a/packages/server/src/services/mongo.ts b/packages/server/src/services/mongo.ts
index a760a1669..d52b2445e 100644
--- a/packages/server/src/services/mongo.ts
+++ b/packages/server/src/services/mongo.ts
@@ -53,7 +53,11 @@ export const findMongoById = async (mongoId: string) => {
const result = await db.query.mongo.findFirst({
where: eq(mongo.mongoId, mongoId),
with: {
- project: true,
+ environment: {
+ with: {
+ project: true,
+ },
+ },
mounts: true,
server: true,
backups: {
diff --git a/packages/server/src/services/mount.ts b/packages/server/src/services/mount.ts
index d64fef6f1..f08a32312 100644
--- a/packages/server/src/services/mount.ts
+++ b/packages/server/src/services/mount.ts
@@ -105,13 +105,69 @@ export const findMountById = async (mountId: string) => {
const mount = await db.query.mounts.findFirst({
where: eq(mounts.mountId, mountId),
with: {
- application: true,
- postgres: true,
- mariadb: true,
- mongo: true,
- mysql: true,
- redis: true,
- compose: true,
+ application: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ postgres: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ mariadb: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ mongo: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ mysql: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ redis: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ compose: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
},
});
if (!mount) {
@@ -123,6 +179,34 @@ export const findMountById = async (mountId: string) => {
return mount;
};
+export const findMountOrganizationId = async (mountId: string) => {
+ const mount = await findMountById(mountId);
+
+ if (mount.application) {
+ return mount.application.environment.project.organizationId;
+ }
+ if (mount.postgres) {
+ return mount.postgres.environment.project.organizationId;
+ }
+ if (mount.mariadb) {
+ return mount.mariadb.environment.project.organizationId;
+ }
+ if (mount.mongo) {
+ return mount.mongo.environment.project.organizationId;
+ }
+ if (mount.mysql) {
+ return mount.mysql.environment.project.organizationId;
+ }
+ if (mount.redis) {
+ return mount.redis.environment.project.organizationId;
+ }
+
+ if (mount.compose) {
+ return mount.compose.environment.project.organizationId;
+ }
+ return null;
+};
+
export const updateMount = async (
mountId: string,
mountData: Partial,
diff --git a/packages/server/src/services/mysql.ts b/packages/server/src/services/mysql.ts
index bb838eb97..f25664e01 100644
--- a/packages/server/src/services/mysql.ts
+++ b/packages/server/src/services/mysql.ts
@@ -56,7 +56,11 @@ export const findMySqlById = async (mysqlId: string) => {
const result = await db.query.mysql.findFirst({
where: eq(mysql.mysqlId, mysqlId),
with: {
- project: true,
+ environment: {
+ with: {
+ project: true,
+ },
+ },
mounts: true,
server: true,
backups: {
diff --git a/packages/server/src/services/port.ts b/packages/server/src/services/port.ts
index 1f66c0143..afafba29b 100644
--- a/packages/server/src/services/port.ts
+++ b/packages/server/src/services/port.ts
@@ -27,6 +27,17 @@ export const createPort = async (input: typeof apiCreatePort._type) => {
export const finPortById = async (portId: string) => {
const result = await db.query.ports.findFirst({
where: eq(ports.portId, portId),
+ with: {
+ application: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ },
});
if (!result) {
throw new TRPCError({
diff --git a/packages/server/src/services/postgres.ts b/packages/server/src/services/postgres.ts
index 47ea3fafc..0d900443e 100644
--- a/packages/server/src/services/postgres.ts
+++ b/packages/server/src/services/postgres.ts
@@ -51,7 +51,11 @@ export const findPostgresById = async (postgresId: string) => {
const result = await db.query.postgres.findFirst({
where: eq(postgres.postgresId, postgresId),
with: {
- project: true,
+ environment: {
+ with: {
+ project: true,
+ },
+ },
mounts: true,
server: true,
backups: {
diff --git a/packages/server/src/services/preview-deployment.ts b/packages/server/src/services/preview-deployment.ts
index 44d1604ae..5ee763b08 100644
--- a/packages/server/src/services/preview-deployment.ts
+++ b/packages/server/src/services/preview-deployment.ts
@@ -31,7 +31,11 @@ export const findPreviewDeploymentById = async (
application: {
with: {
server: true,
- project: true,
+ environment: {
+ with: {
+ project: true,
+ },
+ },
},
},
},
@@ -45,37 +49,6 @@ export const findPreviewDeploymentById = async (
return application;
};
-export const findApplicationByPreview = async (applicationId: string) => {
- const application = await db.query.applications.findFirst({
- with: {
- previewDeployments: {
- where: eq(previewDeployments.applicationId, applicationId),
- },
- project: true,
- domains: true,
- deployments: true,
- mounts: true,
- redirects: true,
- security: true,
- ports: true,
- registry: true,
- gitlab: true,
- github: true,
- bitbucket: true,
- gitea: true,
- server: true,
- },
- });
-
- if (!application) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Application not found",
- });
- }
- return application;
-};
-
export const removePreviewDeployment = async (previewDeploymentId: string) => {
try {
const previewDeployment =
@@ -163,7 +136,7 @@ export const createPreviewDeployment = async (
const appName = `preview-${application.appName}-${generatePassword(6)}`;
const org = await db.query.organization.findFirst({
- where: eq(organization.id, application.project.organizationId),
+ where: eq(organization.id, application.environment.project.organizationId),
});
const generateDomain = await generateWildcardDomain(
application.previewWildcard || "*.traefik.me",
diff --git a/packages/server/src/services/project.ts b/packages/server/src/services/project.ts
index b740834b5..cf58b18fa 100644
--- a/packages/server/src/services/project.ts
+++ b/packages/server/src/services/project.ts
@@ -11,6 +11,7 @@ import {
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
+import { createProductionEnvironment } from "./environment";
export type Project = typeof projects.$inferSelect;
@@ -34,20 +35,31 @@ export const createProject = async (
});
}
- return newProject;
+ // Automatically create a production environment
+ const newEnvironment = await createProductionEnvironment(
+ newProject.projectId,
+ );
+ return {
+ project: newProject,
+ environment: newEnvironment,
+ };
};
export const findProjectById = async (projectId: string) => {
const project = await db.query.projects.findFirst({
where: eq(projects.projectId, projectId),
with: {
- applications: true,
- mariadb: true,
- mongo: true,
- mysql: true,
- postgres: true,
- redis: true,
- compose: true,
+ environments: {
+ with: {
+ applications: true,
+ mariadb: true,
+ mongo: true,
+ mysql: true,
+ postgres: true,
+ redis: true,
+ compose: true,
+ },
+ },
},
});
if (!project) {
@@ -86,7 +98,7 @@ export const updateProjectById = async (
};
export const validUniqueServerAppName = async (appName: string) => {
- const query = await db.query.projects.findMany({
+ const query = await db.query.environments.findMany({
with: {
applications: {
where: eq(applications.appName, appName),
diff --git a/packages/server/src/services/redis.ts b/packages/server/src/services/redis.ts
index 7b9721cb8..b382cbfd1 100644
--- a/packages/server/src/services/redis.ts
+++ b/packages/server/src/services/redis.ts
@@ -52,7 +52,11 @@ export const findRedisById = async (redisId: string) => {
const result = await db.query.redis.findFirst({
where: eq(redis.redisId, redisId),
with: {
- project: true,
+ environment: {
+ with: {
+ project: true,
+ },
+ },
mounts: true,
server: true,
},
diff --git a/packages/server/src/services/rollbacks.ts b/packages/server/src/services/rollbacks.ts
index 3e96325a6..16877e7d9 100644
--- a/packages/server/src/services/rollbacks.ts
+++ b/packages/server/src/services/rollbacks.ts
@@ -76,9 +76,24 @@ export const createRollback = async (
});
};
-const findRollbackById = async (rollbackId: string) => {
+export const findRollbackById = async (rollbackId: string) => {
const result = await db.query.rollbacks.findFirst({
where: eq(rollbacks.rollbackId, rollbackId),
+ with: {
+ deployment: {
+ with: {
+ application: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
});
if (!result) {
@@ -179,7 +194,9 @@ const rollbackApplication = async (
image: string,
serverId?: string | null,
fullContext?: Application & {
- project: Project;
+ environment: {
+ project: Project;
+ };
mounts: Mount[];
ports: Port[];
},
@@ -225,7 +242,7 @@ const rollbackApplication = async (
const bindsMount = generateBindMounts(mounts);
const envVariables = prepareEnvironmentVariables(
env,
- fullContext.project.env,
+ fullContext.environment.project.env,
);
// For rollback, we use the provided image instead of calculating it
diff --git a/packages/server/src/services/schedule.ts b/packages/server/src/services/schedule.ts
index b311ba760..4dace4b67 100644
--- a/packages/server/src/services/schedule.ts
+++ b/packages/server/src/services/schedule.ts
@@ -35,9 +35,29 @@ export const findScheduleById = async (scheduleId: string) => {
const schedule = await db.query.schedules.findFirst({
where: eq(schedules.scheduleId, scheduleId),
with: {
- application: true,
- compose: true,
- server: true,
+ application: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ compose: {
+ with: {
+ environment: {
+ with: {
+ project: true,
+ },
+ },
+ },
+ },
+ server: {
+ with: {
+ organization: true,
+ },
+ },
},
});
@@ -50,6 +70,21 @@ export const findScheduleById = async (scheduleId: string) => {
return schedule;
};
+export const findScheduleOrganizationId = async (scheduleId: string) => {
+ const schedule = await findScheduleById(scheduleId);
+
+ if (schedule?.application) {
+ return schedule?.application?.environment?.project?.organizationId;
+ }
+ if (schedule?.compose) {
+ return schedule?.compose?.environment?.project?.organizationId;
+ }
+ if (schedule?.server) {
+ return schedule?.server?.organization?.id;
+ }
+ return null;
+};
+
export const deleteSchedule = async (scheduleId: string) => {
const schedule = await findScheduleById(scheduleId);
const serverId =
diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts
index e4402892f..f39064523 100644
--- a/packages/server/src/services/settings.ts
+++ b/packages/server/src/services/settings.ts
@@ -253,37 +253,36 @@ export const getDockerResourceType = async (
resourceName: string,
serverId?: string,
) => {
- let result = "";
- const command = `
- RESOURCE_NAME="${resourceName}"
- if docker service inspect "$RESOURCE_NAME" &>/dev/null; then
- echo "service"
- exit 0
- fi
+ try {
+ let result = "";
+ const command = `
+RESOURCE_NAME="${resourceName}"
+if docker service inspect "$RESOURCE_NAME" >/dev/null 2>&1; then
+ echo "service"
+elif docker inspect "$RESOURCE_NAME" >/dev/null 2>&1; then
+ echo "standalone"
+else
+ echo "unknown"
+fi`;
- if docker inspect "$RESOURCE_NAME" &>/dev/null; then
- echo "standalone"
- exit 0
- fi
-
- echo "unknown"
- exit 0
- `;
-
- if (serverId) {
- const { stdout } = await execAsyncRemote(serverId, command);
- result = stdout.trim();
- } else {
- const { stdout } = await execAsync(command);
- result = stdout.trim();
+ if (serverId) {
+ const { stdout } = await execAsyncRemote(serverId, command);
+ result = stdout.trim();
+ } else {
+ const { stdout } = await execAsync(command);
+ result = stdout.trim();
+ }
+ if (result === "service") {
+ return "service";
+ }
+ if (result === "standalone") {
+ return "standalone";
+ }
+ return "unknown";
+ } catch (error) {
+ console.error(error);
+ return "unknown";
}
- if (result === "service") {
- return "service";
- }
- if (result === "standalone") {
- return "standalone";
- }
- return "unknown";
};
export const reloadDockerResource = async (
@@ -294,8 +293,10 @@ export const reloadDockerResource = async (
let command = "";
if (resourceType === "service") {
command = `docker service update --force ${resourceName}`;
- } else {
+ } else if (resourceType === "standalone") {
command = `docker restart ${resourceName}`;
+ } else {
+ throw new Error("Resource type not found");
}
if (serverId) {
await execAsyncRemote(serverId, command);
@@ -312,7 +313,7 @@ export const readEnvironmentVariables = async (
let command = "";
if (resourceType === "service") {
command = `docker service inspect ${resourceName} --format '{{json .Spec.TaskTemplate.ContainerSpec.Env}}'`;
- } else {
+ } else if (resourceType === "standalone") {
command = `docker container inspect ${resourceName} --format '{{json .Config.Env}}'`;
}
let result = "";
@@ -339,7 +340,7 @@ export const readPorts = async (
let command = "";
if (resourceType === "service") {
command = `docker service inspect ${resourceName} --format '{{json .Spec.EndpointSpec.Ports}}'`;
- } else {
+ } else if (resourceType === "standalone") {
command = `docker container inspect ${resourceName} --format '{{json .NetworkSettings.Ports}}'`;
}
let result = "";
diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts
index 39ac95cef..728d5b8ee 100644
--- a/packages/server/src/services/user.ts
+++ b/packages/server/src/services/user.ts
@@ -23,6 +23,23 @@ export const addNewProject = async (
);
};
+export const addNewEnvironment = async (
+ userId: string,
+ environmentId: string,
+ organizationId: string,
+) => {
+ const userR = await findMemberById(userId, organizationId);
+
+ await db
+ .update(member)
+ .set({
+ accessedEnvironments: [...userR.accessedEnvironments, environmentId],
+ })
+ .where(
+ and(eq(member.id, userR.id), eq(member.organizationId, organizationId)),
+ );
+};
+
export const addNewService = async (
userId: string,
serviceId: string,
@@ -131,6 +148,21 @@ export const canPerformAccessProject = async (
return false;
};
+export const canPerformAccessEnvironment = async (
+ userId: string,
+ environmentId: string,
+ organizationId: string,
+) => {
+ const { accessedEnvironments } = await findMemberById(userId, organizationId);
+ const haveAccessToEnvironment = accessedEnvironments.includes(environmentId);
+
+ if (haveAccessToEnvironment) {
+ return true;
+ }
+
+ return false;
+};
+
export const canAccessToTraefikFiles = async (
userId: string,
organizationId: string,
@@ -182,6 +214,32 @@ export const checkServiceAccess = async (
}
};
+export const checkEnvironmentAccess = async (
+ userId: string,
+ environmentId: string,
+ organizationId: string,
+ action = "access" as const,
+) => {
+ let hasPermission = false;
+ switch (action) {
+ case "access":
+ hasPermission = await canPerformAccessEnvironment(
+ userId,
+ environmentId,
+ organizationId,
+ );
+ break;
+ default:
+ hasPermission = false;
+ }
+ if (!hasPermission) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Permission denied",
+ });
+ }
+};
+
export const checkProjectAccess = async (
authId: string,
action: "create" | "delete" | "access",
diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts
index a9ca1c371..8128b57e0 100644
--- a/packages/server/src/setup/server-setup.ts
+++ b/packages/server/src/setup/server-setup.ts
@@ -578,8 +578,7 @@ export const createTraefikInstance = () => {
TRAEFIK_VERSION=${TRAEFIK_VERSION}
docker run -d \
--name dokploy-traefik \
- --network dokploy-network \
- --restart unless-stopped \
+ --restart always \
-v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
-v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \
-v /var/run/docker.sock:/var/run/docker.sock \
@@ -587,6 +586,8 @@ export const createTraefikInstance = () => {
-p ${TRAEFIK_PORT}:${TRAEFIK_PORT} \
-p ${TRAEFIK_HTTP3_PORT}:${TRAEFIK_HTTP3_PORT}/udp \
traefik:v$TRAEFIK_VERSION
+
+ docker network connect dokploy-network dokploy-traefik;
echo "Traefik version $TRAEFIK_VERSION installed ✅"
fi
`;
diff --git a/packages/server/src/utils/backups/compose.ts b/packages/server/src/utils/backups/compose.ts
index f260ffa08..1963f2c91 100644
--- a/packages/server/src/utils/backups/compose.ts
+++ b/packages/server/src/utils/backups/compose.ts
@@ -4,6 +4,7 @@ import {
createDeploymentBackup,
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
+import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
@@ -13,8 +14,9 @@ export const runComposeBackup = async (
compose: Compose,
backup: BackupSchedule,
) => {
- const { projectId, name } = compose;
- const project = await findProjectById(projectId);
+ const { environmentId, name } = compose;
+ const environment = await findEnvironmentById(environmentId);
+ const project = await findProjectById(environment.projectId);
const { prefix, databaseType } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;
diff --git a/packages/server/src/utils/backups/mariadb.ts b/packages/server/src/utils/backups/mariadb.ts
index 8760095c8..2353821e5 100644
--- a/packages/server/src/utils/backups/mariadb.ts
+++ b/packages/server/src/utils/backups/mariadb.ts
@@ -4,6 +4,7 @@ import {
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import type { Mariadb } from "@dokploy/server/services/mariadb";
+import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
@@ -13,8 +14,9 @@ export const runMariadbBackup = async (
mariadb: Mariadb,
backup: BackupSchedule,
) => {
- const { projectId, name } = mariadb;
- const project = await findProjectById(projectId);
+ const { environmentId, name } = mariadb;
+ const environment = await findEnvironmentById(environmentId);
+ const project = await findProjectById(environment.projectId);
const { prefix } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;
diff --git a/packages/server/src/utils/backups/mongo.ts b/packages/server/src/utils/backups/mongo.ts
index 6a74f1d10..429de7d4d 100644
--- a/packages/server/src/utils/backups/mongo.ts
+++ b/packages/server/src/utils/backups/mongo.ts
@@ -4,14 +4,16 @@ import {
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import type { Mongo } from "@dokploy/server/services/mongo";
+import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils";
export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
- const { projectId, name } = mongo;
- const project = await findProjectById(projectId);
+ const { environmentId, name } = mongo;
+ const environment = await findEnvironmentById(environmentId);
+ const project = await findProjectById(environment.projectId);
const { prefix } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;
diff --git a/packages/server/src/utils/backups/mysql.ts b/packages/server/src/utils/backups/mysql.ts
index 6f6678421..90919f24c 100644
--- a/packages/server/src/utils/backups/mysql.ts
+++ b/packages/server/src/utils/backups/mysql.ts
@@ -4,14 +4,16 @@ import {
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import type { MySql } from "@dokploy/server/services/mysql";
+import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils";
export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
- const { projectId, name } = mysql;
- const project = await findProjectById(projectId);
+ const { environmentId, name } = mysql;
+ const environment = await findEnvironmentById(environmentId);
+ const project = await findProjectById(environment.projectId);
const { prefix } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;
diff --git a/packages/server/src/utils/backups/postgres.ts b/packages/server/src/utils/backups/postgres.ts
index 1f7b6a2d2..9aa5d8f5f 100644
--- a/packages/server/src/utils/backups/postgres.ts
+++ b/packages/server/src/utils/backups/postgres.ts
@@ -4,6 +4,7 @@ import {
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import type { Postgres } from "@dokploy/server/services/postgres";
+import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
@@ -13,8 +14,9 @@ export const runPostgresBackup = async (
postgres: Postgres,
backup: BackupSchedule,
) => {
- const { name, projectId } = postgres;
- const project = await findProjectById(projectId);
+ const { name, environmentId } = postgres;
+ const environment = await findEnvironmentById(environmentId);
+ const project = await findProjectById(environment.projectId);
const deployment = await createDeploymentBackup({
backupId: backup.backupId,
diff --git a/packages/server/src/utils/backups/web-server.ts b/packages/server/src/utils/backups/web-server.ts
index 923f03fd8..4d13ae31a 100644
--- a/packages/server/src/utils/backups/web-server.ts
+++ b/packages/server/src/utils/backups/web-server.ts
@@ -80,7 +80,7 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
writeStream.write("Zipped database and filesystem\n");
const uploadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${tempDir}/${backupFileName}" "${s3Path}"`;
- writeStream.write(`Running command: ${uploadCommand}\n`);
+ writeStream.write("Running command to upload backup to S3\n");
await execAsync(uploadCommand);
writeStream.write("Uploaded backup to S3 ✅\n");
writeStream.end();
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index d93c9395e..667b46b74 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -22,7 +22,7 @@ import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
"compose",
- { project: true; mounts: true; domains: true }
+ { environment: { with: { project: true } }; mounts: true; domains: true }
>;
export const buildCompose = async (compose: ComposeNested, logPath: string) => {
const writeStream = createWriteStream(logPath, { flags: "a" });
@@ -72,7 +72,10 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
NODE_ENV: process.env.NODE_ENV,
PATH: process.env.PATH,
...(composeType === "stack" && {
- ...getEnviromentVariablesObject(compose.env, compose.project.env),
+ ...getEnviromentVariablesObject(
+ compose.env,
+ compose.environment.project.env,
+ ),
}),
},
},
@@ -202,7 +205,8 @@ const createEnvFile = (compose: ComposeNested) => {
const envFileContent = prepareEnvironmentVariables(
envContent,
- compose.project.env,
+ compose.environment.project.env,
+ compose.environment.env,
).join("\n");
if (!existsSync(dirname(envFilePath))) {
@@ -232,7 +236,8 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const envFileContent = prepareEnvironmentVariables(
envContent,
- compose.project.env,
+ compose.environment.project.env,
+ compose.environment.env,
).join("\n");
const encodedContent = encodeBase64(envFileContent);
@@ -247,7 +252,7 @@ const getExportEnvCommand = (compose: ComposeNested) => {
const envVars = getEnviromentVariablesObject(
compose.env,
- compose.project.env,
+ compose.environment.project.env,
);
const exports = Object.entries(envVars)
.map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
diff --git a/packages/server/src/utils/builders/docker-file.ts b/packages/server/src/utils/builders/docker-file.ts
index 6799b0810..b218590c8 100644
--- a/packages/server/src/utils/builders/docker-file.ts
+++ b/packages/server/src/utils/builders/docker-file.ts
@@ -28,7 +28,8 @@ export const buildCustomDocker = async (
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
const args = prepareEnvironmentVariables(
buildArgs,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
const dockerContextPath = getDockerContextPath(application);
@@ -51,7 +52,12 @@ export const buildCustomDocker = async (
as it could be publicly exposed.
*/
if (!publishDirectory) {
- createEnvFile(dockerFilePath, env, application.project.env);
+ createEnvFile(
+ dockerFilePath,
+ env,
+ application.environment.project.env,
+ application.environment.env,
+ );
}
await spawnAsync(
@@ -92,7 +98,8 @@ export const getDockerCommand = (
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
const args = prepareEnvironmentVariables(
buildArgs,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
const dockerContextPath =
@@ -121,7 +128,8 @@ export const getDockerCommand = (
command += createEnvFileCommand(
dockerFilePath,
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
}
diff --git a/packages/server/src/utils/builders/heroku.ts b/packages/server/src/utils/builders/heroku.ts
index e634ce45c..3306f2fc2 100644
--- a/packages/server/src/utils/builders/heroku.ts
+++ b/packages/server/src/utils/builders/heroku.ts
@@ -13,7 +13,8 @@ export const buildHeroku = async (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
try {
const args = [
@@ -53,7 +54,8 @@ export const getHerokuCommand = (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
const args = [
diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts
index b779558ef..492f799a8 100644
--- a/packages/server/src/utils/builders/index.ts
+++ b/packages/server/src/utils/builders/index.ts
@@ -30,7 +30,7 @@ export type ApplicationNested = InferResultType<
redirects: true;
ports: true;
registry: true;
- project: true;
+ environment: { with: { project: true } };
}
>;
@@ -148,7 +148,8 @@ export const mechanizeDockerContainer = async (
const filesMount = generateFileMounts(appName, application);
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
const image = getImageName(application);
diff --git a/packages/server/src/utils/builders/nixpacks.ts b/packages/server/src/utils/builders/nixpacks.ts
index 373bbafb0..76905d0e7 100644
--- a/packages/server/src/utils/builders/nixpacks.ts
+++ b/packages/server/src/utils/builders/nixpacks.ts
@@ -20,7 +20,8 @@ export const buildNixpacks = async (
const buildContainerId = `${appName}-${nanoid(10)}`;
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
const writeToStream = (data: string) => {
@@ -101,7 +102,8 @@ export const getNixpacksCommand = (
const buildContainerId = `${appName}-${nanoid(10)}`;
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
const args = ["build", buildAppDirectory, "--name", appName];
diff --git a/packages/server/src/utils/builders/paketo.ts b/packages/server/src/utils/builders/paketo.ts
index c9c6c774e..b95a1bb31 100644
--- a/packages/server/src/utils/builders/paketo.ts
+++ b/packages/server/src/utils/builders/paketo.ts
@@ -12,7 +12,8 @@ export const buildPaketo = async (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
try {
const args = [
@@ -52,7 +53,8 @@ export const getPaketoCommand = (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
const args = [
diff --git a/packages/server/src/utils/builders/railpack.ts b/packages/server/src/utils/builders/railpack.ts
index c91a30ab1..4adc9ca1c 100644
--- a/packages/server/src/utils/builders/railpack.ts
+++ b/packages/server/src/utils/builders/railpack.ts
@@ -26,7 +26,8 @@ export const buildRailpack = async (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
try {
@@ -123,7 +124,8 @@ export const getRailpackCommand = (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
- application.project.env,
+ application.environment.project.env,
+ application.environment.env,
);
// Prepare command
diff --git a/packages/server/src/utils/builders/utils.ts b/packages/server/src/utils/builders/utils.ts
index 8eb5bbb07..cce8dfd95 100644
--- a/packages/server/src/utils/builders/utils.ts
+++ b/packages/server/src/utils/builders/utils.ts
@@ -6,14 +6,17 @@ export const createEnvFile = (
directory: string,
env: string | null,
projectEnv?: string | null,
+ environmentEnv?: string | null,
) => {
const envFilePath = join(dirname(directory), ".env");
if (!existsSync(dirname(envFilePath))) {
mkdirSync(dirname(envFilePath), { recursive: true });
}
- const envFileContent = prepareEnvironmentVariables(env, projectEnv).join(
- "\n",
- );
+ const envFileContent = prepareEnvironmentVariables(
+ env,
+ projectEnv,
+ environmentEnv,
+ ).join("\n");
writeFileSync(envFilePath, envFileContent);
};
@@ -21,10 +24,13 @@ export const createEnvFileCommand = (
directory: string,
env: string | null,
projectEnv?: string | null,
+ environmentEnv?: string | null,
) => {
- const envFileContent = prepareEnvironmentVariables(env, projectEnv).join(
- "\n",
- );
+ const envFileContent = prepareEnvironmentVariables(
+ env,
+ projectEnv,
+ environmentEnv,
+ ).join("\n");
const encodedContent = encodeBase64(envFileContent || "");
const envFilePath = join(dirname(directory), ".env");
diff --git a/packages/server/src/utils/databases/mariadb.ts b/packages/server/src/utils/databases/mariadb.ts
index 46e9dac67..1671de454 100644
--- a/packages/server/src/utils/databases/mariadb.ts
+++ b/packages/server/src/utils/databases/mariadb.ts
@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type MariadbNested = InferResultType<
"mariadb",
- { mounts: true; project: true }
+ { mounts: true; environment: { with: { project: true } } }
>;
export const buildMariadb = async (mariadb: MariadbNested) => {
const {
@@ -54,7 +54,8 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
});
const envVariables = prepareEnvironmentVariables(
defaultMariadbEnv,
- mariadb.project.env,
+ mariadb.environment.project.env,
+ mariadb.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
diff --git a/packages/server/src/utils/databases/mongo.ts b/packages/server/src/utils/databases/mongo.ts
index bf188c184..b4c36e6eb 100644
--- a/packages/server/src/utils/databases/mongo.ts
+++ b/packages/server/src/utils/databases/mongo.ts
@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type MongoNested = InferResultType<
"mongo",
- { mounts: true; project: true }
+ { mounts: true; environment: { with: { project: true } } }
>;
export const buildMongo = async (mongo: MongoNested) => {
@@ -102,7 +102,8 @@ ${command ?? "wait $MONGOD_PID"}`;
const envVariables = prepareEnvironmentVariables(
defaultMongoEnv,
- mongo.project.env,
+ mongo.environment.project.env,
+ mongo.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
diff --git a/packages/server/src/utils/databases/mysql.ts b/packages/server/src/utils/databases/mysql.ts
index 51deb7e6a..a3ed72afc 100644
--- a/packages/server/src/utils/databases/mysql.ts
+++ b/packages/server/src/utils/databases/mysql.ts
@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type MysqlNested = InferResultType<
"mysql",
- { mounts: true; project: true }
+ { mounts: true; environment: { with: { project: true } } }
>;
export const buildMysql = async (mysql: MysqlNested) => {
@@ -60,7 +60,8 @@ export const buildMysql = async (mysql: MysqlNested) => {
});
const envVariables = prepareEnvironmentVariables(
defaultMysqlEnv,
- mysql.project.env,
+ mysql.environment.project.env,
+ mysql.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
diff --git a/packages/server/src/utils/databases/postgres.ts b/packages/server/src/utils/databases/postgres.ts
index b017bc3e1..83f65f1c2 100644
--- a/packages/server/src/utils/databases/postgres.ts
+++ b/packages/server/src/utils/databases/postgres.ts
@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type PostgresNested = InferResultType<
"postgres",
- { mounts: true; project: true }
+ { mounts: true; environment: { with: { project: true } } }
>;
export const buildPostgres = async (postgres: PostgresNested) => {
const {
@@ -53,7 +53,8 @@ export const buildPostgres = async (postgres: PostgresNested) => {
});
const envVariables = prepareEnvironmentVariables(
defaultPostgresEnv,
- postgres.project.env,
+ postgres.environment.project.env,
+ postgres.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
diff --git a/packages/server/src/utils/databases/redis.ts b/packages/server/src/utils/databases/redis.ts
index 7cb209de4..85ae873d4 100644
--- a/packages/server/src/utils/databases/redis.ts
+++ b/packages/server/src/utils/databases/redis.ts
@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type RedisNested = InferResultType<
"redis",
- { mounts: true; project: true }
+ { mounts: true; environment: { with: { project: true } } }
>;
export const buildRedis = async (redis: RedisNested) => {
const {
@@ -51,7 +51,8 @@ export const buildRedis = async (redis: RedisNested) => {
});
const envVariables = prepareEnvironmentVariables(
defaultRedisEnv,
- redis.project.env,
+ redis.environment.project.env,
+ redis.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts
index ef7abf613..ae14bcf40 100644
--- a/packages/server/src/utils/docker/utils.ts
+++ b/packages/server/src/utils/docker/utils.ts
@@ -259,21 +259,44 @@ export const removeService = async (
export const prepareEnvironmentVariables = (
serviceEnv: string | null,
projectEnv?: string | null,
+ environmentEnv?: string | null,
) => {
const projectVars = parse(projectEnv ?? "");
+ const environmentVars = parse(environmentEnv ?? "");
const serviceVars = parse(serviceEnv ?? "");
const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
let resolvedValue = value;
+
+ // Replace project variables
if (projectVars) {
- resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
- if (projectVars[ref] !== undefined) {
- return projectVars[ref];
- }
- throw new Error(`Invalid project environment variable: project.${ref}`);
- });
+ resolvedValue = resolvedValue.replace(
+ /\$\{\{project\.(.*?)\}\}/g,
+ (_, ref) => {
+ if (projectVars[ref] !== undefined) {
+ return projectVars[ref];
+ }
+ throw new Error(
+ `Invalid project environment variable: project.${ref}`,
+ );
+ },
+ );
}
+ // Replace environment variables
+ if (environmentVars) {
+ resolvedValue = resolvedValue.replace(
+ /\$\{\{environment\.(.*?)\}\}/g,
+ (_, ref) => {
+ if (environmentVars[ref] !== undefined) {
+ return environmentVars[ref];
+ }
+ throw new Error(`Invalid environment variable: environment.${ref}`);
+ },
+ );
+ }
+
+ // Replace self-references (service variables)
resolvedValue = resolvedValue.replace(/\$\{\{(.*?)\}\}/g, (_, ref) => {
if (serviceVars[ref] !== undefined) {
return serviceVars[ref];
@@ -301,8 +324,9 @@ export const parseEnvironmentKeyValuePair = (
export const getEnviromentVariablesObject = (
input: string | null,
projectEnv?: string | null,
+ environmentEnv?: string | null,
) => {
- const envs = prepareEnvironmentVariables(input, projectEnv);
+ const envs = prepareEnvironmentVariables(input, projectEnv, environmentEnv);
const jsonObject: Record = {};
diff --git a/packages/server/src/utils/providers/docker.ts b/packages/server/src/utils/providers/docker.ts
index 88c457767..56341b7d6 100644
--- a/packages/server/src/utils/providers/docker.ts
+++ b/packages/server/src/utils/providers/docker.ts
@@ -42,7 +42,9 @@ export const buildDocker = async (
await mechanizeDockerContainer(application);
writeStream.write("\nDocker Deployed: ✅\n");
} catch (error) {
- writeStream.write("❌ Error");
+ writeStream.write(
+ `❌ Error: ${error instanceof Error ? error.message : String(error)}`,
+ );
throw error;
} finally {
writeStream.end();