(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..b89637de3 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,
@@ -63,10 +64,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 +84,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 +102,7 @@ export const applicationRouter = createTRPCRouter({
}
return newApplication;
} catch (error: unknown) {
+ console.log("error", error);
if (error instanceof TRPCError) {
throw error;
}
@@ -120,7 +126,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 +182,7 @@ export const applicationRouter = createTRPCRouter({
try {
if (
- application.project.organizationId !==
+ application.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
@@ -212,7 +219,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 +255,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 +285,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",
@@ -296,7 +310,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",
@@ -331,7 +346,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 +365,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 +391,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 +419,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 +448,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 +475,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 +502,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 +526,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 +552,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 +614,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 +629,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 +656,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",
@@ -647,7 +674,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",
@@ -683,7 +711,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 +727,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 +764,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 +810,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 +847,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 +862,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 +879,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..f84da9bc6 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,
@@ -64,10 +65,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 +84,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 +121,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 +175,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 +200,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 +208,6 @@ export const composeRouter = createTRPCRouter({
message: "You are not authorized to delete this compose",
});
}
- 4;
const result = await db
.delete(composeTable)
@@ -215,13 +226,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 +248,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 +268,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 +290,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 +317,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 +332,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 +350,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",
@@ -341,7 +371,10 @@ export const composeRouter = createTRPCRouter({
.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",
@@ -374,7 +407,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 redeploy this compose",
@@ -406,7 +442,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 +459,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 +477,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 +493,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 +510,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 +540,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 +641,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 +700,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 +759,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 +831,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..91d565655
--- /dev/null
+++ b/apps/dokploy/server/api/routers/environment.ts
@@ -0,0 +1,328 @@
+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
+
+ 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 (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/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..42e958e4e 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,
});
diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts
index 57d8d9f1e..2d75e511a 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),
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 83fa94f14..716ca7b71 100644
--- a/packages/server/src/db/schema/mariadb.ts
+++ b/packages/server/src/db/schema/mariadb.ts
@@ -4,6 +4,7 @@ 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";
@@ -66,18 +67,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),
@@ -115,7 +117,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(),
@@ -136,7 +138,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 fed414bf6..ead51ef2d 100644
--- a/packages/server/src/db/schema/mongo.ts
+++ b/packages/server/src/db/schema/mongo.ts
@@ -4,6 +4,7 @@ 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";
@@ -62,9 +63,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 +74,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),
@@ -104,7 +106,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(),
@@ -125,7 +127,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 361d5685d..eef6b496f 100644
--- a/packages/server/src/db/schema/mysql.ts
+++ b/packages/server/src/db/schema/mysql.ts
@@ -4,6 +4,7 @@ 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";
@@ -64,18 +65,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),
@@ -113,7 +115,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(),
@@ -133,7 +134,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 66a58d5c6..929ee7ffa 100644
--- a/packages/server/src/db/schema/postgres.ts
+++ b/packages/server/src/db/schema/postgres.ts
@@ -4,6 +4,7 @@ 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";
@@ -64,18 +65,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),
@@ -104,7 +106,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(),
@@ -128,7 +130,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/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/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/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/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();