diff --git a/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx b/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx
index 619315be9..338ae3db5 100644
--- a/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx
+++ b/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx
@@ -1,9 +1,12 @@
import { ShowDestinations } from "@/components/dashboard/settings/ssh-keys/show-ssh-keys";
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { SettingsLayout } from "@/components/layouts/settings-layout";
+import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
+import { createServerSideHelpers } from "@trpc/react-query/server";
import type { GetServerSidePropsContext } from "next";
import React, { type ReactElement } from "react";
+import superjson from "superjson";
const Page = () => {
return (
@@ -26,7 +29,7 @@ export async function getServerSideProps(
ctx: GetServerSidePropsContext<{ serviceId: string }>,
) {
const { user, session } = await validateRequest(ctx.req, ctx.res);
- if (!user || user.rol === "user") {
+ if (!user) {
return {
redirect: {
permanent: true,
@@ -34,8 +37,45 @@ export async function getServerSideProps(
},
};
}
+ const { req, res, resolvedUrl } = ctx;
+ const helpers = createServerSideHelpers({
+ router: appRouter,
+ ctx: {
+ req: req as any,
+ res: res as any,
+ db: null as any,
+ session: session,
+ user: user,
+ },
+ transformer: superjson,
+ });
- return {
- props: {},
- };
+ try {
+ await helpers.project.all.prefetch();
+ const auth = await helpers.auth.get.fetch();
+
+ if (auth.rol === "user") {
+ const user = await helpers.user.byAuthId.fetch({
+ authId: auth.id,
+ });
+
+ if (!user.canAccessToSSHKeys) {
+ return {
+ redirect: {
+ permanent: true,
+ destination: "/",
+ },
+ };
+ }
+ }
+ return {
+ props: {
+ trpcState: helpers.dehydrate(),
+ },
+ };
+ } catch (error) {
+ return {
+ props: {},
+ };
+ }
}
diff --git a/apps/dokploy/public/templates/aptabase.svg b/apps/dokploy/public/templates/aptabase.svg
new file mode 100644
index 000000000..3cb71ecfa
--- /dev/null
+++ b/apps/dokploy/public/templates/aptabase.svg
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/soketi.png b/apps/dokploy/public/templates/soketi.png
new file mode 100644
index 000000000..aec5a79a2
Binary files /dev/null and b/apps/dokploy/public/templates/soketi.png differ
diff --git a/apps/dokploy/public/templates/supabase.svg b/apps/dokploy/public/templates/supabase.svg
new file mode 100644
index 000000000..2b69d42e7
--- /dev/null
+++ b/apps/dokploy/public/templates/supabase.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/dokploy/server/api/routers/auth.ts b/apps/dokploy/server/api/routers/auth.ts
index 262c9ac2c..f3b2362fd 100644
--- a/apps/dokploy/server/api/routers/auth.ts
+++ b/apps/dokploy/server/api/routers/auth.ts
@@ -12,6 +12,7 @@ import {
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
+import { db } from "../../db";
import {
createAdmin,
createUser,
@@ -33,6 +34,14 @@ export const authRouter = createTRPCRouter({
.input(apiCreateAdmin)
.mutation(async ({ ctx, input }) => {
try {
+ const admin = await db.query.admins.findFirst({});
+
+ if (admin) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Admin already exists",
+ });
+ }
const newAdmin = await createAdmin(input);
const session = await lucia.createSession(newAdmin.id || "", {});
ctx.res.appendHeader(
diff --git a/apps/dokploy/server/api/routers/cluster.ts b/apps/dokploy/server/api/routers/cluster.ts
index 6bd71ca9f..7294c9029 100644
--- a/apps/dokploy/server/api/routers/cluster.ts
+++ b/apps/dokploy/server/api/routers/cluster.ts
@@ -35,14 +35,23 @@ export const clusterRouter = createTRPCRouter({
}),
addWorker: protectedProcedure.query(async ({ input }) => {
const result = await docker.swarmInspect();
- return `docker swarm join --token ${
- result.JoinTokens.Worker
- } ${await getPublicIpWithFallback()}:2377`;
+ const docker_version = await docker.version();
+
+ return {
+ command: `docker swarm join --token ${
+ result.JoinTokens.Worker
+ } ${await getPublicIpWithFallback()}:2377`,
+ version: docker_version.Version,
+ };
}),
addManager: protectedProcedure.query(async ({ input }) => {
const result = await docker.swarmInspect();
- return `docker swarm join --token ${
- result.JoinTokens.Manager
- } ${await getPublicIpWithFallback()}:2377`;
+ const docker_version = await docker.version();
+ return {
+ command: `docker swarm join --token ${
+ result.JoinTokens.Manager
+ } ${await getPublicIpWithFallback()}:2377`,
+ version: docker_version.Version,
+ };
}),
});
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index 3b8a92f63..fff95bbd5 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -3,6 +3,7 @@ import { db } from "@/server/db";
import {
apiCreateCompose,
apiCreateComposeByTemplate,
+ apiFetchServices,
apiFindCompose,
apiRandomizeCompose,
apiUpdateCompose,
@@ -15,16 +16,18 @@ import {
import { myQueue } from "@/server/queues/queueSetup";
import { createCommand } from "@/server/utils/builders/compose";
import { randomizeComposeFile } from "@/server/utils/docker/compose";
+import { addDomainToCompose, cloneCompose } from "@/server/utils/docker/domain";
import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
import { templates } from "@/templates/templates";
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
import {
generatePassword,
loadTemplateModule,
- readComposeFile,
+ readTemplateComposeFile,
} from "@/templates/utils";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
+import { dump } from "js-yaml";
import _ from "lodash";
import { nanoid } from "nanoid";
import { findAdmin } from "../services/admin";
@@ -38,6 +41,7 @@ import {
updateCompose,
} from "../services/compose";
import { removeDeploymentsByComposeId } from "../services/deployment";
+import { createDomain, findDomainsByComposeId } from "../services/domain";
import { createMount } from "../services/mount";
import { findProjectById } from "../services/project";
import { addNewService, checkServiceAccess } from "../services/user";
@@ -113,10 +117,25 @@ export const composeRouter = createTRPCRouter({
await cleanQueuesByCompose(input.composeId);
}),
- allServices: protectedProcedure
- .input(apiFindCompose)
+ loadServices: protectedProcedure
+ .input(apiFetchServices)
.query(async ({ input }) => {
- return await loadServices(input.composeId);
+ return await loadServices(input.composeId, input.type);
+ }),
+ fetchSourceType: protectedProcedure
+ .input(apiFindCompose)
+ .mutation(async ({ input }) => {
+ try {
+ const compose = await findComposeById(input.composeId);
+ await cloneCompose(compose);
+ return compose.sourceType;
+ } catch (err) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to fetch source type",
+ cause: err,
+ });
+ }
}),
randomizeCompose: protectedProcedure
@@ -124,6 +143,17 @@ export const composeRouter = createTRPCRouter({
.mutation(async ({ input }) => {
return await randomizeComposeFile(input.composeId, input.prefix);
}),
+ getConvertedCompose: protectedProcedure
+ .input(apiFindCompose)
+ .query(async ({ input }) => {
+ const compose = await findComposeById(input.composeId);
+ const domains = await findDomainsByComposeId(input.composeId);
+
+ const composeFile = await addDomainToCompose(compose, domains);
+ return dump(composeFile, {
+ lineWidth: 1000,
+ });
+ }),
deploy: protectedProcedure
.input(apiFindCompose)
@@ -189,7 +219,7 @@ export const composeRouter = createTRPCRouter({
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
}
- const composeFile = await readComposeFile(input.id);
+ const composeFile = await readTemplateComposeFile(input.id);
const generate = await loadTemplateModule(input.id as TemplatesKeys);
@@ -206,7 +236,7 @@ export const composeRouter = createTRPCRouter({
const project = await findProjectById(input.projectId);
const projectName = slugify(`${project.name} ${input.id}`);
- const { envs, mounts } = generate({
+ const { envs, mounts, domains } = generate({
serverIp: admin.serverIp,
projectName: projectName,
});
@@ -214,7 +244,7 @@ export const composeRouter = createTRPCRouter({
const compose = await createComposeByTemplate({
...input,
composeFile: composeFile,
- env: envs.join("\n"),
+ env: envs?.join("\n"),
name: input.id,
sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`,
@@ -237,6 +267,17 @@ export const composeRouter = createTRPCRouter({
}
}
+ if (domains && domains?.length > 0) {
+ for (const domain of domains) {
+ await createDomain({
+ ...domain,
+ domainType: "compose",
+ certificateType: "none",
+ composeId: compose.composeId,
+ });
+ }
+ }
+
return null;
}),
diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts
index ec7f13d4f..309dd91ad 100644
--- a/apps/dokploy/server/api/routers/domain.ts
+++ b/apps/dokploy/server/api/routers/domain.ts
@@ -1,8 +1,12 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiCreateDomain,
+ apiCreateTraefikMeDomain,
+ apiFindCompose,
apiFindDomain,
apiFindDomainByApplication,
+ apiFindDomainByCompose,
+ apiFindOneApplication,
apiUpdateDomain,
} from "@/server/db/schema";
import { manageDomain, removeDomain } from "@/server/utils/traefik/domain";
@@ -12,8 +16,8 @@ import {
createDomain,
findDomainById,
findDomainsByApplicationId,
- generateDomain,
- generateWildcard,
+ findDomainsByComposeId,
+ generateTraefikMeDomain,
removeDomainById,
updateDomainById,
} from "../services/domain";
@@ -33,27 +37,30 @@ export const domainRouter = createTRPCRouter({
}
}),
byApplicationId: protectedProcedure
- .input(apiFindDomainByApplication)
+ .input(apiFindOneApplication)
.query(async ({ input }) => {
return await findDomainsByApplicationId(input.applicationId);
}),
+ byComposeId: protectedProcedure
+ .input(apiFindCompose)
+ .query(async ({ input }) => {
+ return await findDomainsByComposeId(input.composeId);
+ }),
generateDomain: protectedProcedure
- .input(apiFindDomainByApplication)
+ .input(apiCreateTraefikMeDomain)
.mutation(async ({ input }) => {
- return generateDomain(input);
- }),
- generateWildcard: protectedProcedure
- .input(apiFindDomainByApplication)
- .mutation(async ({ input }) => {
- return generateWildcard(input);
+ return generateTraefikMeDomain(input.appName);
}),
+
update: protectedProcedure
.input(apiUpdateDomain)
.mutation(async ({ input }) => {
const result = await updateDomainById(input.domainId, input);
const domain = await findDomainById(input.domainId);
- const application = await findApplicationById(domain.applicationId);
- await manageDomain(application, domain);
+ if (domain.applicationId) {
+ const application = await findApplicationById(domain.applicationId);
+ await manageDomain(application, domain);
+ }
return result;
}),
one: protectedProcedure.input(apiFindDomain).query(async ({ input }) => {
@@ -64,7 +71,9 @@ export const domainRouter = createTRPCRouter({
.mutation(async ({ input }) => {
const domain = await findDomainById(input.domainId);
const result = await removeDomainById(input.domainId);
- await removeDomain(domain.application.appName, domain.uniqueConfigKey);
+ if (domain.application) {
+ await removeDomain(domain.application.appName, domain.uniqueConfigKey);
+ }
return result;
}),
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index d4dfb7ec5..5c3a4f01a 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -15,11 +15,13 @@ import {
cleanUpSystemPrune,
cleanUpUnusedImages,
cleanUpUnusedVolumes,
+ prepareEnvironmentVariables,
startService,
stopService,
} from "@/server/utils/docker/utils";
import { recreateDirectory } from "@/server/utils/filesystem/directory";
import { sendDockerCleanupNotifications } from "@/server/utils/notifications/docker-cleanup";
+import { execAsync } from "@/server/utils/process/execAsync";
import { spawnAsync } from "@/server/utils/process/spawnAsync";
import {
readConfig,
@@ -36,6 +38,7 @@ import {
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
import { TRPCError } from "@trpc/server";
import { scheduleJob, scheduledJobs } from "node-schedule";
+import { z } from "zod";
import { appRouter } from "../root";
import { findAdmin, updateAdmin } from "../services/admin";
import {
@@ -49,14 +52,10 @@ import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
export const settingsRouter = createTRPCRouter({
reloadServer: adminProcedure.mutation(async () => {
- await spawnAsync("docker", [
- "service",
- "update",
- "--force",
- "--image",
- getDokployImage(),
- "dokploy",
- ]);
+ const { stdout } = await execAsync(
+ "docker service inspect dokploy --format '{{.ID}}'",
+ );
+ await execAsync(`docker service update --force ${stdout.trim()}`);
return true;
}),
reloadTraefik: adminProcedure.mutation(async () => {
@@ -72,7 +71,9 @@ export const settingsRouter = createTRPCRouter({
toggleDashboard: adminProcedure
.input(apiEnableDashboard)
.mutation(async ({ input }) => {
- await initializeTraefik(input.enableDashboard);
+ await initializeTraefik({
+ enableDashboard: input.enableDashboard,
+ });
return true;
}),
@@ -312,4 +313,37 @@ export const settingsRouter = createTRPCRouter({
return openApiDocument;
},
),
+ readTraefikEnv: adminProcedure.query(async () => {
+ const { stdout } = await execAsync(
+ "docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik",
+ );
+
+ return stdout.trim();
+ }),
+
+ writeTraefikEnv: adminProcedure
+ .input(z.string())
+ .mutation(async ({ input }) => {
+ const envs = prepareEnvironmentVariables(input);
+ await initializeTraefik({
+ env: envs,
+ });
+
+ return true;
+ }),
+ haveTraefikDashboardPortEnabled: adminProcedure.query(async () => {
+ const { stdout } = await execAsync(
+ "docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik",
+ );
+
+ const parsed: any[] = JSON.parse(stdout.trim());
+
+ for (const port of parsed) {
+ if (port.PublishedPort === 8080) {
+ return true;
+ }
+ }
+
+ return false;
+ }),
});
diff --git a/apps/dokploy/server/api/routers/ssh-key.ts b/apps/dokploy/server/api/routers/ssh-key.ts
index 1421e6175..eb2bf8af2 100644
--- a/apps/dokploy/server/api/routers/ssh-key.ts
+++ b/apps/dokploy/server/api/routers/ssh-key.ts
@@ -34,21 +34,23 @@ export const sshRouter = createTRPCRouter({
});
}
}),
- remove: adminProcedure.input(apiRemoveSshKey).mutation(async ({ input }) => {
- try {
- return await removeSSHKeyById(input.sshKeyId);
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error to delete this ssh key",
- });
- }
- }),
+ remove: protectedProcedure
+ .input(apiRemoveSshKey)
+ .mutation(async ({ input }) => {
+ try {
+ return await removeSSHKeyById(input.sshKeyId);
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to delete this ssh key",
+ });
+ }
+ }),
one: protectedProcedure.input(apiFindOneSshKey).query(async ({ input }) => {
const sshKey = await findSSHKeyById(input.sshKeyId);
return sshKey;
}),
- all: adminProcedure.query(async () => {
+ all: protectedProcedure.query(async () => {
return await db.query.sshKeys.findMany({});
}),
generate: protectedProcedure
@@ -56,15 +58,17 @@ export const sshRouter = createTRPCRouter({
.mutation(async ({ input }) => {
return await generateSSHKey(input.type);
}),
- update: adminProcedure.input(apiUpdateSshKey).mutation(async ({ input }) => {
- try {
- return await updateSSHKeyById(input);
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error to update this ssh key",
- cause: error,
- });
- }
- }),
+ update: protectedProcedure
+ .input(apiUpdateSshKey)
+ .mutation(async ({ input }) => {
+ try {
+ return await updateSSHKeyById(input);
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to update this ssh key",
+ cause: error,
+ });
+ }
+ }),
});
diff --git a/apps/dokploy/server/api/services/compose.ts b/apps/dokploy/server/api/services/compose.ts
index 978c8fb71..bd949bc38 100644
--- a/apps/dokploy/server/api/services/compose.ts
+++ b/apps/dokploy/server/api/services/compose.ts
@@ -4,6 +4,7 @@ import { db } from "@/server/db";
import { type apiCreateCompose, compose } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { buildCompose } from "@/server/utils/builders/compose";
+import { cloneCompose, loadDockerCompose } from "@/server/utils/docker/domain";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
@@ -14,7 +15,6 @@ import { createComposeFile } from "@/server/utils/providers/raw";
import { generatePassword } from "@/templates/utils";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
-import { load } from "js-yaml";
import { findAdmin, getDokployUrl } from "./admin";
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
import { validUniqueServerAppName } from "./project";
@@ -91,6 +91,7 @@ export const findComposeById = async (composeId: string) => {
project: true,
deployments: true,
mounts: true,
+ domains: true,
},
});
if (!result) {
@@ -102,20 +103,27 @@ export const findComposeById = async (composeId: string) => {
return result;
};
-export const loadServices = async (composeId: string) => {
+export const loadServices = async (
+ composeId: string,
+ type: "fetch" | "cache" = "fetch",
+) => {
const compose = await findComposeById(composeId);
- // use js-yaml to parse the docker compose file and then extact the services
- const composeFile = compose.composeFile;
- const composeData = load(composeFile) as ComposeSpecification;
+ if (type === "fetch") {
+ await cloneCompose(compose);
+ }
+ const composeData = await loadDockerCompose(compose);
if (!composeData?.services) {
- return ["All Services"];
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Services not found",
+ });
}
const services = Object.keys(composeData.services);
- return [...services, "All Services"];
+ return [...services];
};
export const updateCompose = async (
diff --git a/apps/dokploy/server/api/services/domain.ts b/apps/dokploy/server/api/services/domain.ts
index b11c0d2ae..53687d28b 100644
--- a/apps/dokploy/server/api/services/domain.ts
+++ b/apps/dokploy/server/api/services/domain.ts
@@ -15,8 +15,6 @@ export type Domain = typeof domains.$inferSelect;
export const createDomain = async (input: typeof apiCreateDomain._type) => {
await db.transaction(async (tx) => {
- const application = await findApplicationById(input.applicationId);
-
const domain = await tx
.insert(domains)
.values({
@@ -32,52 +30,19 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => {
});
}
- await manageDomain(application, domain);
+ if (domain.applicationId) {
+ const application = await findApplicationById(domain.applicationId);
+ await manageDomain(application, domain);
+ }
});
};
-export const generateDomain = async (
- input: typeof apiFindDomainByApplication._type,
-) => {
- const application = await findApplicationById(input.applicationId);
+export const generateTraefikMeDomain = async (appName: string) => {
const admin = await findAdmin();
- const domain = await createDomain({
- applicationId: application.applicationId,
- host: generateRandomDomain({
- serverIp: admin.serverIp || "",
- projectName: application.appName,
- }),
- port: 3000,
- certificateType: "none",
- https: false,
- path: "/",
+ return generateRandomDomain({
+ serverIp: admin.serverIp || "",
+ projectName: appName,
});
-
- return domain;
-};
-
-export const generateWildcard = async (
- input: typeof apiFindDomainByApplication._type,
-) => {
- const application = await findApplicationById(input.applicationId);
- const admin = await findAdmin();
-
- if (!admin.host) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "We need a host to generate a wildcard domain",
- });
- }
- const domain = await createDomain({
- applicationId: application.applicationId,
- host: generateWildcardDomain(application.appName, admin.host || ""),
- port: 3000,
- certificateType: "none",
- https: false,
- path: "/",
- });
-
- return domain;
};
export const generateWildcardDomain = (
@@ -114,6 +79,17 @@ export const findDomainsByApplicationId = async (applicationId: string) => {
return domainsArray;
};
+export const findDomainsByComposeId = async (composeId: string) => {
+ const domainsArray = await db.query.domains.findMany({
+ where: eq(domains.composeId, composeId),
+ with: {
+ compose: true,
+ },
+ });
+
+ return domainsArray;
+};
+
export const updateDomainById = async (
domainId: string,
domainData: Partial
,
diff --git a/apps/dokploy/server/db/schema/compose.ts b/apps/dokploy/server/db/schema/compose.ts
index 485c51e10..80597d76f 100644
--- a/apps/dokploy/server/db/schema/compose.ts
+++ b/apps/dokploy/server/db/schema/compose.ts
@@ -1,11 +1,11 @@
import { sshKeys } from "@/server/db/schema/ssh-key";
-import { generatePassword } from "@/templates/utils";
import { relations } from "drizzle-orm";
import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { deployments } from "./deployment";
+import { domains } from "./domain";
import { mounts } from "./mount";
import { projects } from "./project";
import { applicationStatus } from "./shared";
@@ -72,6 +72,7 @@ export const composeRelations = relations(compose, ({ one, many }) => ({
fields: [compose.customGitSSHKeyId],
references: [sshKeys.sshKeyId],
}),
+ domains: many(domains),
}));
const createSchema = createInsertSchema(compose, {
@@ -106,6 +107,11 @@ export const apiFindCompose = z.object({
composeId: z.string().min(1),
});
+export const apiFetchServices = z.object({
+ composeId: z.string().min(1),
+ type: z.enum(["fetch", "cache"]).optional().default("cache"),
+});
+
export const apiUpdateCompose = createSchema.partial().extend({
composeId: z.string(),
composeFile: z.string().optional(),
diff --git a/apps/dokploy/server/db/schema/domain.ts b/apps/dokploy/server/db/schema/domain.ts
index 48dc05a77..301175fc5 100644
--- a/apps/dokploy/server/db/schema/domain.ts
+++ b/apps/dokploy/server/db/schema/domain.ts
@@ -1,11 +1,22 @@
-import { domain } from "@/server/db/validations";
+import { domain } from "@/server/db/validations/domain";
import { relations } from "drizzle-orm";
-import { boolean, integer, pgTable, serial, text } from "drizzle-orm/pg-core";
+import {
+ boolean,
+ integer,
+ pgEnum,
+ pgTable,
+ serial,
+ text,
+} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
+import { z } from "zod";
import { applications } from "./application";
+import { compose } from "./compose";
import { certificateType } from "./shared";
+export const domainType = pgEnum("domainType", ["compose", "application"]);
+
export const domains = pgTable("domain", {
domainId: text("domainId")
.notNull()
@@ -13,15 +24,21 @@ export const domains = pgTable("domain", {
.$defaultFn(() => nanoid()),
host: text("host").notNull(),
https: boolean("https").notNull().default(false),
- port: integer("port").default(80),
+ port: integer("port").default(3000),
path: text("path").default("/"),
+ serviceName: text("serviceName"),
+ domainType: domainType("domainType").default("application"),
uniqueConfigKey: serial("uniqueConfigKey"),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- applicationId: text("applicationId")
- .notNull()
- .references(() => applications.applicationId, { onDelete: "cascade" }),
+ composeId: text("composeId").references(() => compose.composeId, {
+ onDelete: "cascade",
+ }),
+ applicationId: text("applicationId").references(
+ () => applications.applicationId,
+ { onDelete: "cascade" },
+ ),
certificateType: certificateType("certificateType").notNull().default("none"),
});
@@ -30,6 +47,10 @@ export const domainsRelations = relations(domains, ({ one }) => ({
fields: [domains.applicationId],
references: [applications.applicationId],
}),
+ compose: one(compose, {
+ fields: [domains.composeId],
+ references: [compose.composeId],
+ }),
}));
const createSchema = createInsertSchema(domains, domain._def.schema.shape);
@@ -41,6 +62,9 @@ export const apiCreateDomain = createSchema.pick({
https: true,
applicationId: true,
certificateType: true,
+ composeId: true,
+ serviceName: true,
+ domainType: true,
});
export const apiFindDomain = createSchema
@@ -53,6 +77,14 @@ export const apiFindDomainByApplication = createSchema.pick({
applicationId: true,
});
+export const apiCreateTraefikMeDomain = createSchema.pick({}).extend({
+ appName: z.string().min(1),
+});
+
+export const apiFindDomainByCompose = createSchema.pick({
+ composeId: true,
+});
+
export const apiUpdateDomain = createSchema
.pick({
host: true,
@@ -60,5 +92,7 @@ export const apiUpdateDomain = createSchema
port: true,
https: true,
certificateType: true,
+ serviceName: true,
+ domainType: true,
})
.merge(createSchema.pick({ domainId: true }).required());
diff --git a/apps/dokploy/server/db/schema/user.ts b/apps/dokploy/server/db/schema/user.ts
index 50c1eb83d..2710be837 100644
--- a/apps/dokploy/server/db/schema/user.ts
+++ b/apps/dokploy/server/db/schema/user.ts
@@ -28,6 +28,7 @@ export const users = pgTable("user", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
+ canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
canCreateServices: boolean("canCreateServices").notNull().default(false),
canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
canDeleteServices: boolean("canDeleteServices").notNull().default(false),
@@ -107,6 +108,7 @@ export const apiAssignPermissions = createSchema
canAccessToTraefikFiles: true,
canAccessToDocker: true,
canAccessToAPI: true,
+ canAccessToSSHKeys: true,
})
.required();
diff --git a/apps/dokploy/server/db/validations/domain.ts b/apps/dokploy/server/db/validations/domain.ts
new file mode 100644
index 000000000..b09b57b6c
--- /dev/null
+++ b/apps/dokploy/server/db/validations/domain.ts
@@ -0,0 +1,46 @@
+import { z } from "zod";
+
+export const domain = z
+ .object({
+ host: z.string().min(1, { message: "Add a hostname" }),
+ path: z.string().min(1).optional(),
+ port: z
+ .number()
+ .min(1, { message: "Port must be at least 1" })
+ .max(65535, { message: "Port must be 65535 or below" })
+ .optional(),
+ https: z.boolean().optional(),
+ certificateType: z.enum(["letsencrypt", "none"]).optional(),
+ })
+ .superRefine((input, ctx) => {
+ if (input.https && !input.certificateType) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ path: ["certificateType"],
+ message: "Required",
+ });
+ }
+ });
+
+export const domainCompose = z
+ .object({
+ host: z.string().min(1, { message: "Host is required" }),
+ path: z.string().min(1).optional(),
+ port: z
+ .number()
+ .min(1, { message: "Port must be at least 1" })
+ .max(65535, { message: "Port must be 65535 or below" })
+ .optional(),
+ https: z.boolean().optional(),
+ certificateType: z.enum(["letsencrypt", "none"]).optional(),
+ serviceName: z.string().min(1, { message: "Service name is required" }),
+ })
+ .superRefine((input, ctx) => {
+ if (input.https && !input.certificateType) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ path: ["certificateType"],
+ message: "Required",
+ });
+ }
+ });
diff --git a/apps/dokploy/server/db/validations/index.ts b/apps/dokploy/server/db/validations/index.ts
index cc97ee933..b3cb57852 100644
--- a/apps/dokploy/server/db/validations/index.ts
+++ b/apps/dokploy/server/db/validations/index.ts
@@ -35,27 +35,3 @@ export const sshKeyUpdate = sshKeyCreate.pick({
export const sshKeyType = z.object({
type: z.enum(["rsa", "ed25519"]).optional(),
});
-
-export const domain = z
- .object({
- host: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/, {
- message: "Invalid hostname",
- }),
- path: z.string().min(1).optional(),
- port: z
- .number()
- .min(1, { message: "Port must be at least 1" })
- .max(65535, { message: "Port must be 65535 or below" })
- .optional(),
- https: z.boolean().optional(),
- certificateType: z.enum(["letsencrypt", "none"]).optional(),
- })
- .superRefine((input, ctx) => {
- if (input.https && !input.certificateType) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- path: ["certificateType"],
- message: "Required",
- });
- }
- });
diff --git a/apps/dokploy/server/setup/postgres-setup.ts b/apps/dokploy/server/setup/postgres-setup.ts
index 139a2f35d..ca606c2d1 100644
--- a/apps/dokploy/server/setup/postgres-setup.ts
+++ b/apps/dokploy/server/setup/postgres-setup.ts
@@ -36,10 +36,9 @@ export const initializePostgres = async () => {
Ports: [
{
TargetPort: 5432,
- ...(process.env.NODE_ENV === "development"
- ? { PublishedPort: 5432 }
- : {}),
+ PublishedPort: process.env.NODE_ENV === "development" ? 5432 : 0,
Protocol: "tcp",
+ PublishMode: "host",
},
],
},
diff --git a/apps/dokploy/server/setup/redis-setup.ts b/apps/dokploy/server/setup/redis-setup.ts
index 0f9719a98..abbacdd83 100644
--- a/apps/dokploy/server/setup/redis-setup.ts
+++ b/apps/dokploy/server/setup/redis-setup.ts
@@ -33,10 +33,9 @@ export const initializeRedis = async () => {
Ports: [
{
TargetPort: 6379,
- ...(process.env.NODE_ENV === "development"
- ? { PublishedPort: 6379 }
- : {}),
+ PublishedPort: process.env.NODE_ENV === "development" ? 6379 : 0,
Protocol: "tcp",
+ PublishMode: "host",
},
],
},
diff --git a/apps/dokploy/server/setup/traefik-setup.ts b/apps/dokploy/server/setup/traefik-setup.ts
index 2b44cf401..afebf07e0 100644
--- a/apps/dokploy/server/setup/traefik-setup.ts
+++ b/apps/dokploy/server/setup/traefik-setup.ts
@@ -11,7 +11,15 @@ const TRAEFIK_SSL_PORT =
Number.parseInt(process.env.TRAEFIK_SSL_PORT ?? "", 10) || 443;
const TRAEFIK_PORT = Number.parseInt(process.env.TRAEFIK_PORT ?? "", 10) || 80;
-export const initializeTraefik = async (enableDashboard = false) => {
+interface TraefikOptions {
+ enableDashboard?: boolean;
+ env?: string[];
+}
+
+export const initializeTraefik = async ({
+ enableDashboard = false,
+ env = [],
+}: TraefikOptions = {}) => {
const imageName = "traefik:v2.5";
const containerName = "dokploy-traefik";
const settings: CreateServiceOptions = {
@@ -19,6 +27,7 @@ export const initializeTraefik = async (enableDashboard = false) => {
TaskTemplate: {
ContainerSpec: {
Image: imageName,
+ Env: env,
Mounts: [
{
Type: "bind",
diff --git a/apps/dokploy/server/utils/builders/compose.ts b/apps/dokploy/server/utils/builders/compose.ts
index 9212cf604..4f72f46d3 100644
--- a/apps/dokploy/server/utils/builders/compose.ts
+++ b/apps/dokploy/server/utils/builders/compose.ts
@@ -8,20 +8,28 @@ import { dirname, join } from "node:path";
import { COMPOSE_PATH } from "@/server/constants";
import type { InferResultType } from "@/server/types/with";
import boxen from "boxen";
+import { writeDomainsToCompose } from "../docker/domain";
import { prepareEnvironmentVariables } from "../docker/utils";
import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
"compose",
- { project: true; mounts: true }
+ { project: true; mounts: true; domains: true }
>;
export const buildCompose = async (compose: ComposeNested, logPath: string) => {
const writeStream = createWriteStream(logPath, { flags: "a" });
- const { sourceType, appName, mounts, composeType, env, composePath } =
- compose;
+ const {
+ sourceType,
+ appName,
+ mounts,
+ composeType,
+ env,
+ composePath,
+ domains,
+ } = compose;
try {
const command = createCommand(compose);
-
+ await writeDomainsToCompose(compose, domains);
createEnvFile(compose);
const logContent = `
diff --git a/apps/dokploy/server/utils/docker/domain.ts b/apps/dokploy/server/utils/docker/domain.ts
new file mode 100644
index 000000000..ab4a904bd
--- /dev/null
+++ b/apps/dokploy/server/utils/docker/domain.ts
@@ -0,0 +1,220 @@
+import fs, { existsSync, readFileSync, writeSync } from "node:fs";
+import { writeFile } from "node:fs/promises";
+import { join } from "node:path";
+import type { Compose } from "@/server/api/services/compose";
+import type { Domain } from "@/server/api/services/domain";
+import { COMPOSE_PATH } from "@/server/constants";
+import { dump, load } from "js-yaml";
+import { cloneGitRawRepository } from "../providers/git";
+import { cloneRawGithubRepository } from "../providers/github";
+import { createComposeFileRaw } from "../providers/raw";
+import type {
+ ComposeSpecification,
+ DefinitionsService,
+ PropertiesNetworks,
+} from "./types";
+
+export const cloneCompose = async (compose: Compose) => {
+ if (compose.sourceType === "github") {
+ await cloneRawGithubRepository(compose);
+ } else if (compose.sourceType === "git") {
+ await cloneGitRawRepository(compose);
+ } else if (compose.sourceType === "raw") {
+ await createComposeFileRaw(compose);
+ }
+};
+
+export const getComposePath = (compose: Compose) => {
+ const { appName, sourceType, composePath } = compose;
+ let path = "";
+
+ if (sourceType === "raw") {
+ path = "docker-compose.yml";
+ } else {
+ path = composePath;
+ }
+
+ return join(COMPOSE_PATH, appName, "code", path);
+};
+
+export const loadDockerCompose = async (
+ compose: Compose,
+): Promise => {
+ const path = getComposePath(compose);
+
+ if (existsSync(path)) {
+ const yamlStr = readFileSync(path, "utf8");
+ const parsedConfig = load(yamlStr) as ComposeSpecification;
+ return parsedConfig;
+ }
+ return null;
+};
+
+export const readComposeFile = async (compose: Compose) => {
+ const path = getComposePath(compose);
+ if (existsSync(path)) {
+ const yamlStr = readFileSync(path, "utf8");
+ return yamlStr;
+ }
+ return null;
+};
+
+export const writeDomainsToCompose = async (
+ compose: Compose,
+ domains: Domain[],
+) => {
+ if (!domains.length) {
+ return;
+ }
+ const composeConverted = await addDomainToCompose(compose, domains);
+
+ const path = getComposePath(compose);
+ const composeString = dump(composeConverted, { lineWidth: 1000 });
+ try {
+ await writeFile(path, composeString, "utf8");
+ } catch (error) {
+ throw error;
+ }
+};
+
+export const addDomainToCompose = async (
+ compose: Compose,
+ domains: Domain[],
+) => {
+ const { appName } = compose;
+ const result = await loadDockerCompose(compose);
+
+ if (!result || domains.length === 0) {
+ return null;
+ }
+
+ for (const domain of domains) {
+ const { serviceName, https } = domain;
+ if (!serviceName) {
+ throw new Error("Service name not found");
+ }
+ if (!result?.services?.[serviceName]) {
+ throw new Error(`The service ${serviceName} not found in the compose`);
+ }
+ if (!result.services[serviceName].labels) {
+ result.services[serviceName].labels = [];
+ }
+
+ const httpLabels = await createDomainLabels(appName, domain, "web");
+ if (https) {
+ const httpsLabels = await createDomainLabels(
+ appName,
+ domain,
+ "websecure",
+ );
+ httpLabels.push(...httpsLabels);
+ }
+
+ const labels = result.services[serviceName].labels;
+
+ if (Array.isArray(labels)) {
+ if (!labels.includes("traefik.enable=true")) {
+ labels.push("traefik.enable=true");
+ }
+ labels.push(...httpLabels);
+ }
+
+ // Add the dokploy-network to the service
+ result.services[serviceName].networks = addDokployNetworkToService(
+ result.services[serviceName].networks,
+ );
+ }
+
+ // Add dokploy-network to the root of the compose file
+ result.networks = addDokployNetworkToRoot(result.networks);
+
+ return result;
+};
+
+export const writeComposeFile = async (
+ compose: Compose,
+ composeSpec: ComposeSpecification,
+) => {
+ const path = getComposePath(compose);
+
+ try {
+ const composeFile = dump(composeSpec, {
+ lineWidth: 1000,
+ });
+ fs.writeFileSync(path, composeFile, "utf8");
+ } catch (e) {
+ console.error("Error saving the YAML config file:", e);
+ }
+};
+
+export const createDomainLabels = async (
+ appName: string,
+ domain: Domain,
+ entrypoint: "web" | "websecure",
+) => {
+ const { host, port, https, uniqueConfigKey, certificateType } = domain;
+ const routerName = `${appName}-${uniqueConfigKey}-${entrypoint}`;
+ const labels = [
+ `traefik.http.routers.${routerName}.rule=Host(\`${host}\`)`,
+ `traefik.http.routers.${routerName}.entrypoints=${entrypoint}`,
+ `traefik.http.services.${routerName}.loadbalancer.server.port=${port}`,
+ `traefik.http.routers.${routerName}.service=${routerName}`,
+ ];
+
+ if (entrypoint === "web" && https) {
+ labels.push(
+ `traefik.http.routers.${routerName}.middlewares=redirect-to-https@file`,
+ );
+ }
+
+ if (entrypoint === "websecure") {
+ if (certificateType === "letsencrypt") {
+ labels.push(
+ `traefik.http.routers.${routerName}.tls.certresolver=letsencrypt`,
+ );
+ }
+ }
+
+ return labels;
+};
+
+export const addDokployNetworkToService = (
+ networkService: DefinitionsService["networks"],
+) => {
+ let networks = networkService;
+ const network = "dokploy-network";
+ if (!networks) {
+ networks = [];
+ }
+
+ if (Array.isArray(networks)) {
+ if (!networks.includes(network)) {
+ networks.push(network);
+ }
+ } else if (networks && typeof networks === "object") {
+ if (!(network in networks)) {
+ networks[network] = {};
+ }
+ }
+
+ return networks;
+};
+
+export const addDokployNetworkToRoot = (
+ networkRoot: PropertiesNetworks | undefined,
+) => {
+ let networks = networkRoot;
+ const network = "dokploy-network";
+
+ if (!networks) {
+ networks = {};
+ }
+
+ if (networks[network] || !networks[network]) {
+ networks[network] = {
+ external: true,
+ };
+ }
+
+ return networks;
+};
diff --git a/apps/dokploy/server/utils/providers/git.ts b/apps/dokploy/server/utils/providers/git.ts
index cf3e28aea..bad7e615f 100644
--- a/apps/dokploy/server/utils/providers/git.ts
+++ b/apps/dokploy/server/utils/providers/git.ts
@@ -139,3 +139,61 @@ const sanitizeRepoPathSSH = (input: string) => {
},
};
};
+
+export const cloneGitRawRepository = async (entity: {
+ appName: string;
+ customGitUrl?: string | null;
+ customGitBranch?: string | null;
+ customGitSSHKeyId?: string | null;
+}) => {
+ const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity;
+
+ if (!customGitUrl || !customGitBranch) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error: Repository not found",
+ });
+ }
+
+ const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
+ const basePath = COMPOSE_PATH;
+ const outputPath = join(basePath, appName, "code");
+ const knownHostsPath = path.join(SSH_PATH, "known_hosts");
+
+ try {
+ await addHostToKnownHosts(customGitUrl);
+ await recreateDirectory(outputPath);
+
+ if (customGitSSHKeyId) {
+ await updateSSHKeyById({
+ sshKeyId: customGitSSHKeyId,
+ lastUsedAt: new Date().toISOString(),
+ });
+ }
+
+ await spawnAsync(
+ "git",
+ [
+ "clone",
+ "--branch",
+ customGitBranch,
+ "--depth",
+ "1",
+ customGitUrl,
+ outputPath,
+ "--progress",
+ ],
+ (data) => {},
+ {
+ env: {
+ ...process.env,
+ ...(customGitSSHKeyId && {
+ GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`,
+ }),
+ },
+ },
+ );
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/dokploy/server/utils/providers/github.ts b/apps/dokploy/server/utils/providers/github.ts
index 06e38e191..a49f2b56e 100644
--- a/apps/dokploy/server/utils/providers/github.ts
+++ b/apps/dokploy/server/utils/providers/github.ts
@@ -1,6 +1,6 @@
import { createWriteStream } from "node:fs";
import { join } from "node:path";
-import type { Admin } from "@/server/api/services/admin";
+import { type Admin, findAdmin } from "@/server/api/services/admin";
import { APPLICATIONS_PATH, COMPOSE_PATH } from "@/server/constants";
import { createAppAuth } from "@octokit/auth-app";
import { TRPCError } from "@trpc/server";
@@ -128,3 +128,34 @@ export const cloneGithubRepository = async (
writeStream.end();
}
};
+
+export const cloneRawGithubRepository = async (entity: {
+ appName: string;
+ repository?: string | null;
+ owner?: string | null;
+ branch?: string | null;
+}) => {
+ const { appName, repository, owner, branch } = entity;
+ const admin = await findAdmin();
+ const basePath = COMPOSE_PATH;
+ const outputPath = join(basePath, appName, "code");
+ const octokit = authGithub(admin);
+ const token = await getGithubToken(octokit);
+ const repoclone = `github.com/${owner}/${repository}.git`;
+ await recreateDirectory(outputPath);
+ const cloneUrl = `https://oauth2:${token}@${repoclone}`;
+ try {
+ await spawnAsync("git", [
+ "clone",
+ "--branch",
+ branch!,
+ "--depth",
+ "1",
+ cloneUrl,
+ outputPath,
+ "--progress",
+ ]);
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/dokploy/server/utils/providers/raw.ts b/apps/dokploy/server/utils/providers/raw.ts
index 63b825a7e..9bf818a30 100644
--- a/apps/dokploy/server/utils/providers/raw.ts
+++ b/apps/dokploy/server/utils/providers/raw.ts
@@ -26,3 +26,15 @@ export const createComposeFile = async (compose: Compose, logPath: string) => {
writeStream.end();
}
};
+
+export const createComposeFileRaw = async (compose: Compose) => {
+ const { appName, composeFile } = compose;
+ const outputPath = join(COMPOSE_PATH, appName, "code");
+ const filePath = join(outputPath, "docker-compose.yml");
+ try {
+ await recreateDirectory(outputPath);
+ await writeFile(filePath, composeFile);
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/dokploy/templates/appsmith/docker-compose.yml b/apps/dokploy/templates/appsmith/docker-compose.yml
index ad07a709b..f520ee362 100644
--- a/apps/dokploy/templates/appsmith/docker-compose.yml
+++ b/apps/dokploy/templates/appsmith/docker-compose.yml
@@ -2,17 +2,5 @@ version: "3.8"
services:
appsmith:
image: index.docker.io/appsmith/appsmith-ee:v1.29
- networks:
- - dokploy-network
- ports:
- - ${APP_SMITH_PORT}
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${APP_SMITH_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${APP_SMITH_PORT}"
volumes:
- ../files/stacks:/appsmith-stacks
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/appsmith/index.ts b/apps/dokploy/templates/appsmith/index.ts
index 8da281323..ff744a249 100644
--- a/apps/dokploy/templates/appsmith/index.ts
+++ b/apps/dokploy/templates/appsmith/index.ts
@@ -1,4 +1,5 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateHash,
@@ -7,14 +8,16 @@ import {
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
- const envs = [
- `APP_SMITH_HOST=${randomDomain}`,
- "APP_SMITH_PORT=80",
- `HASH=${mainServiceHash}`,
+
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 80,
+ serviceName: "appsmith",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/aptabase/docker-compose.yml b/apps/dokploy/templates/aptabase/docker-compose.yml
new file mode 100644
index 000000000..934fd1eea
--- /dev/null
+++ b/apps/dokploy/templates/aptabase/docker-compose.yml
@@ -0,0 +1,51 @@
+services:
+ aptabase_db:
+ image: postgres:15-alpine
+ restart: always
+ volumes:
+ - db-data:/var/lib/postgresql/data
+ environment:
+ POSTGRES_USER: aptabase
+ POSTGRES_PASSWORD: sTr0NGp4ssw0rd
+ networks:
+ - dokploy-network
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U aptabase"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ aptabase_events_db:
+ image: clickhouse/clickhouse-server:23.8.16.16-alpine
+ restart: always
+ volumes:
+ - events-db-data:/var/lib/clickhouse
+ environment:
+ CLICKHOUSE_USER: aptabase
+ CLICKHOUSE_PASSWORD: sTr0NGp4ssw0rd
+ ulimits:
+ nofile:
+ soft: 262144
+ hard: 262144
+ networks:
+ - dokploy-network
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8123 || exit 1"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ aptabase:
+ image: ghcr.io/aptabase/aptabase:main
+ restart: always
+ environment:
+ BASE_URL: http://${APTABASE_HOST}
+ AUTH_SECRET: ${AUTH_SECRET}
+ DATABASE_URL: Server=aptabase_db;Port=5432;User Id=aptabase;Password=sTr0NGp4ssw0rd;Database=aptabase
+ CLICKHOUSE_URL: Host=aptabase_events_db;Port=8123;Username=aptabase;Password=sTr0NGp4ssw0rd
+
+volumes:
+ db-data:
+ driver: local
+ events-db-data:
+ driver: local
diff --git a/apps/dokploy/templates/aptabase/index.ts b/apps/dokploy/templates/aptabase/index.ts
new file mode 100644
index 000000000..38b077ae8
--- /dev/null
+++ b/apps/dokploy/templates/aptabase/index.ts
@@ -0,0 +1,27 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const authSecret = generateBase64(32);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 8080,
+ serviceName: "aptabase",
+ },
+ ];
+
+ const envs = [`APTABASE_HOST=${mainDomain}`, `AUTH_SECRET=${authSecret}`];
+
+ return {
+ envs,
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/baserow/docker-compose.yml b/apps/dokploy/templates/baserow/docker-compose.yml
index ffb3e8ad8..db588e83a 100644
--- a/apps/dokploy/templates/baserow/docker-compose.yml
+++ b/apps/dokploy/templates/baserow/docker-compose.yml
@@ -2,21 +2,9 @@ version: "3.8"
services:
baserow:
image: baserow/baserow:1.25.2
- networks:
- - dokploy-network
environment:
BASEROW_PUBLIC_URL: "http://${BASEROW_HOST}"
- ports:
- - ${BASEROW_PORT}
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${BASEROW_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${BASEROW_PORT}
volumes:
- baserow_data:/baserow/data
volumes:
baserow_data:
-
-networks:
- dokploy-network:
- external: true
\ No newline at end of file
diff --git a/apps/dokploy/templates/baserow/index.ts b/apps/dokploy/templates/baserow/index.ts
index a0a387198..fa57417cc 100644
--- a/apps/dokploy/templates/baserow/index.ts
+++ b/apps/dokploy/templates/baserow/index.ts
@@ -1,20 +1,24 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
- const envs = [
- `BASEROW_HOST=${randomDomain}`,
- "BASEROW_PORT=80",
- `HASH=${mainServiceHash}`,
+ const mainHost = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainHost,
+ port: 80,
+ serviceName: "baserow",
+ },
];
+ const envs = [`BASEROW_HOST=${mainHost}`];
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/calcom/docker-compose.yml b/apps/dokploy/templates/calcom/docker-compose.yml
index 1afb2db87..7a1d8c92a 100644
--- a/apps/dokploy/templates/calcom/docker-compose.yml
+++ b/apps/dokploy/templates/calcom/docker-compose.yml
@@ -21,16 +21,6 @@ services:
- DATABASE_URL=postgres://postgres:password@postgres:5432/db
- NEXT_PUBLIC_WEBAPP_URL=http://${CALCOM_HOST}
- NEXTAUTH_URL=http://${CALCOM_HOST}/api/auth
- networks:
- - dokploy-network
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${CALCOM_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${CALCOM_PORT}"
-
-networks:
- dokploy-network:
- external: true
volumes:
calcom-data:
diff --git a/apps/dokploy/templates/calcom/index.ts b/apps/dokploy/templates/calcom/index.ts
index 61c590bf1..d359e9c7c 100644
--- a/apps/dokploy/templates/calcom/index.ts
+++ b/apps/dokploy/templates/calcom/index.ts
@@ -1,27 +1,32 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
-// https://cal.com/
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
const calcomEncryptionKey = generateBase64(32);
const nextAuthSecret = generateBase64(32);
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "calcom",
+ },
+ ];
+
const envs = [
- `CALCOM_HOST=${randomDomain}`,
- "CALCOM_PORT=3000",
- `HASH=${mainServiceHash}`,
+ `CALCOM_HOST=${mainDomain}`,
`NEXTAUTH_SECRET=${nextAuthSecret}`,
`CALENDSO_ENCRYPTION_KEY=${calcomEncryptionKey}`,
];
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/directus/docker-compose.yml b/apps/dokploy/templates/directus/docker-compose.yml
index 08a5db45f..6f058ba61 100644
--- a/apps/dokploy/templates/directus/docker-compose.yml
+++ b/apps/dokploy/templates/directus/docker-compose.yml
@@ -18,8 +18,6 @@ services:
directus:
image: directus/directus:10.12.1
- networks:
- - dokploy-network
ports:
- 8055
volumes:
@@ -28,10 +26,6 @@ services:
depends_on:
- cache
- database
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${DIRECTUS_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${DIRECTUS_PORT}
environment:
SECRET: "replace-with-secure-random-value"
@@ -49,8 +43,5 @@ services:
ADMIN_EMAIL: "admin@example.com"
ADMIN_PASSWORD: "d1r3ctu5"
-networks:
- dokploy-network:
- external: true
volumes:
directus:
diff --git a/apps/dokploy/templates/directus/index.ts b/apps/dokploy/templates/directus/index.ts
index 94bedbb1c..42a05aee0 100644
--- a/apps/dokploy/templates/directus/index.ts
+++ b/apps/dokploy/templates/directus/index.ts
@@ -1,20 +1,20 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
- const envs = [
- `DIRECTUS_HOST=${randomDomain}`,
- "DIRECTUS_PORT=8055",
- `HASH=${mainServiceHash}`,
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 8055,
+ serviceName: "directus",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/documenso/docker-compose.yml b/apps/dokploy/templates/documenso/docker-compose.yml
index f411cfbb4..a649fa757 100644
--- a/apps/dokploy/templates/documenso/docker-compose.yml
+++ b/apps/dokploy/templates/documenso/docker-compose.yml
@@ -19,8 +19,6 @@ services:
documenso:
image: documenso/documenso:1.5.6-rc.2
- networks:
- - dokploy-network
depends_on:
postgres:
condition: service_healthy
@@ -38,16 +36,8 @@ services:
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/opt/documenso/cert.p12
ports:
- ${DOCUMENSO_PORT}
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${DOCUMENSO_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${DOCUMENSO_PORT}"
volumes:
- /opt/documenso/cert.p12:/opt/documenso/cert.p12
-networks:
- dokploy-network:
- external: true
-
volumes:
documenso-data:
diff --git a/apps/dokploy/templates/documenso/index.ts b/apps/dokploy/templates/documenso/index.ts
index 67afd596d..c70d1db4a 100644
--- a/apps/dokploy/templates/documenso/index.ts
+++ b/apps/dokploy/templates/documenso/index.ts
@@ -1,24 +1,29 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateBase64,
- generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
-
+ const mainDomain = generateRandomDomain(schema);
const nextAuthSecret = generateBase64(32);
const documensoEncryptionKey = generatePassword(32);
const documensoSecondaryEncryptionKey = generatePassword(64);
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "documenso",
+ },
+ ];
+
const envs = [
- `DOCUMENSO_HOST=${randomDomain}`,
+ `DOCUMENSO_HOST=${mainDomain}`,
"DOCUMENSO_PORT=3000",
- `HASH=${mainServiceHash}`,
`NEXTAUTH_SECRET=${nextAuthSecret}`,
`NEXT_PRIVATE_ENCRYPTION_KEY=${documensoEncryptionKey}`,
`NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=${documensoSecondaryEncryptionKey}`,
@@ -26,5 +31,6 @@ export function generate(schema: Schema): Template {
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/doublezero/docker-compose.yml b/apps/dokploy/templates/doublezero/docker-compose.yml
index bb7b1d2b3..352470334 100644
--- a/apps/dokploy/templates/doublezero/docker-compose.yml
+++ b/apps/dokploy/templates/doublezero/docker-compose.yml
@@ -2,10 +2,6 @@ services:
doublezero:
restart: always
image: liltechnomancer/double-zero:0.2.1
- ports:
- - ${DOUBLEZERO_PORT}
- networks:
- - dokploy-network
volumes:
- db-data:/var/lib/doublezero/data
environment:
@@ -17,15 +13,7 @@ services:
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
PHX_HOST: ${DOUBLEZERO_HOST}
DATABASE_PATH: ./00.db
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${DOUBLEZERO_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${DOUBLEZERO_PORT}"
-
+
volumes:
db-data:
driver: local
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/doublezero/index.ts b/apps/dokploy/templates/doublezero/index.ts
index 52656f401..fa774e9dc 100644
--- a/apps/dokploy/templates/doublezero/index.ts
+++ b/apps/dokploy/templates/doublezero/index.ts
@@ -1,20 +1,26 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
const secretKeyBase = generateBase64(64);
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 4000,
+ serviceName: "doublezero",
+ },
+ ];
+
const envs = [
- `DOUBLEZERO_HOST=${randomDomain}`,
+ `DOUBLEZERO_HOST=${mainDomain}`,
"DOUBLEZERO_PORT=4000",
- `HASH=${mainServiceHash}`,
`SECRET_KEY_BASE=${secretKeyBase}`,
"AWS_ACCESS_KEY_ID=your-aws-access-key",
"AWS_SECRET_ACCESS_KEY=your-aws-secret-key",
@@ -25,5 +31,6 @@ export function generate(schema: Schema): Template {
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/excalidraw/docker-compose.yml b/apps/dokploy/templates/excalidraw/docker-compose.yml
index 58920b393..8743434b6 100644
--- a/apps/dokploy/templates/excalidraw/docker-compose.yml
+++ b/apps/dokploy/templates/excalidraw/docker-compose.yml
@@ -1,17 +1,7 @@
-version: '3.8'
+version: "3.8"
services:
excalidraw:
networks:
- dokploy-network
image: excalidraw/excalidraw:latest
- ports:
- - ${EXCALIDRAW_PORT}
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${EXCALIDRAW_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${EXCALIDRAW_PORT}
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/excalidraw/index.ts b/apps/dokploy/templates/excalidraw/index.ts
index 10e4bbcff..13a43c440 100644
--- a/apps/dokploy/templates/excalidraw/index.ts
+++ b/apps/dokploy/templates/excalidraw/index.ts
@@ -1,4 +1,5 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateHash,
@@ -6,15 +7,17 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
- const envs = [
- `EXCALIDRAW_HOST=${randomDomain}`,
- "EXCALIDRAW_PORT=80",
- `HASH=${mainServiceHash}`,
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "excalidraw",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/ghost/docker-compose.yml b/apps/dokploy/templates/ghost/docker-compose.yml
index a507b887e..288c59e54 100644
--- a/apps/dokploy/templates/ghost/docker-compose.yml
+++ b/apps/dokploy/templates/ghost/docker-compose.yml
@@ -1,13 +1,8 @@
version: "3.8"
services:
-
ghost:
image: ghost:5-alpine
restart: always
- networks:
- - dokploy-network
- ports:
- - ${GHOST_PORT}
environment:
database__client: mysql
database__connection__host: db
@@ -15,10 +10,7 @@ services:
database__connection__password: example
database__connection__database: ghost
url: http://${GHOST_HOST}
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${GHOST_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${GHOST_PORT}
+
volumes:
- ghost:/var/lib/ghost/content
@@ -35,7 +27,3 @@ services:
volumes:
ghost:
db:
-
-networks:
- dokploy-network:
- external: true
\ No newline at end of file
diff --git a/apps/dokploy/templates/ghost/index.ts b/apps/dokploy/templates/ghost/index.ts
index 178126af6..1a88c3629 100644
--- a/apps/dokploy/templates/ghost/index.ts
+++ b/apps/dokploy/templates/ghost/index.ts
@@ -1,4 +1,5 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateHash,
@@ -6,15 +7,19 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
- const envs = [
- `GHOST_HOST=${randomDomain}`,
- "GHOST_PORT=2368",
- `HASH=${mainServiceHash}`,
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 2368,
+ serviceName: "ghost",
+ },
];
+ const envs = [`GHOST_HOST=${mainDomain}`];
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/glitchtip/docker-compose.yml b/apps/dokploy/templates/glitchtip/docker-compose.yml
index a654a91b2..e45c76627 100644
--- a/apps/dokploy/templates/glitchtip/docker-compose.yml
+++ b/apps/dokploy/templates/glitchtip/docker-compose.yml
@@ -1,16 +1,14 @@
-x-environment:
- &default-environment
+x-environment: &default-environment
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
SECRET_KEY: ${SECRET_KEY}
PORT: ${GLITCHTIP_PORT}
EMAIL_URL: consolemail://
- GLITCHTIP_DOMAIN: http://${GLITCHTIP_HOST}
+ GLITCHTIP_DOMAIN: http://${GLITCHTIP_HOST}
DEFAULT_FROM_EMAIL: email@glitchtip.com
- CELERY_WORKER_AUTOSCALE: "1,3"
+ CELERY_WORKER_AUTOSCALE: "1,3"
CELERY_WORKER_MAX_TASKS_PER_CHILD: "10000"
-x-depends_on:
- &default-depends_on
+x-depends_on: &default-depends_on
- postgres
- redis
@@ -18,7 +16,7 @@ services:
postgres:
image: postgres:16
environment:
- POSTGRES_HOST_AUTH_METHOD: "trust"
+ POSTGRES_HOST_AUTH_METHOD: "trust"
restart: unless-stopped
volumes:
- pg-data:/var/lib/postgresql/data
@@ -36,21 +34,15 @@ services:
- ${GLITCHTIP_PORT}
environment: *default-environment
restart: unless-stopped
- volumes:
+ volumes:
- uploads:/code/uploads
- networks:
- - dokploy-network
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${GLITCHTIP_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${GLITCHTIP_PORT}
worker:
image: glitchtip/glitchtip:v4.0
command: ./bin/run-celery-with-beat.sh
depends_on: *default-depends_on
environment: *default-environment
restart: unless-stopped
- volumes:
+ volumes:
- uploads:/code/uploads
networks:
- dokploy-network
@@ -65,7 +57,3 @@ services:
volumes:
pg-data:
uploads:
-
-networks:
- dokploy-network:
- external: true
\ No newline at end of file
diff --git a/apps/dokploy/templates/glitchtip/index.ts b/apps/dokploy/templates/glitchtip/index.ts
index 024d4106c..093d752e1 100644
--- a/apps/dokploy/templates/glitchtip/index.ts
+++ b/apps/dokploy/templates/glitchtip/index.ts
@@ -1,23 +1,30 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
const secretKey = generateBase64(32);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 8000,
+ serviceName: "web",
+ },
+ ];
const envs = [
- `GLITCHTIP_HOST=${randomDomain}`,
+ `GLITCHTIP_HOST=${mainDomain}`,
"GLITCHTIP_PORT=8000",
`SECRET_KEY=${secretKey}`,
- `HASH=${mainServiceHash}`,
];
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/grafana/docker-compose.yml b/apps/dokploy/templates/grafana/docker-compose.yml
index a0555f9fe..9d913c17f 100644
--- a/apps/dokploy/templates/grafana/docker-compose.yml
+++ b/apps/dokploy/templates/grafana/docker-compose.yml
@@ -1,20 +1,9 @@
version: "3.8"
services:
grafana:
- networks:
- - dokploy-network
image: grafana/grafana-enterprise:9.5.20
restart: unless-stopped
- ports:
- - ${GRAFANA_PORT}
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${GRAFANA_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${GRAFANA_PORT}
volumes:
- grafana-storage:/var/lib/grafana
-networks:
- dokploy-network:
- external: true
volumes:
- grafana-storage: {}
\ No newline at end of file
+ grafana-storage: {}
diff --git a/apps/dokploy/templates/grafana/index.ts b/apps/dokploy/templates/grafana/index.ts
index fc66f56dd..fb614ef32 100644
--- a/apps/dokploy/templates/grafana/index.ts
+++ b/apps/dokploy/templates/grafana/index.ts
@@ -1,20 +1,19 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
- const envs = [
- `GRAFANA_HOST=${randomDomain}`,
- "GRAFANA_PORT=3000",
- `HASH=${mainServiceHash}`,
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 3000,
+ serviceName: "grafana",
+ },
];
-
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/jellyfin/docker-compose.yml b/apps/dokploy/templates/jellyfin/docker-compose.yml
index 16ae6be96..cb61476ab 100644
--- a/apps/dokploy/templates/jellyfin/docker-compose.yml
+++ b/apps/dokploy/templates/jellyfin/docker-compose.yml
@@ -1,30 +1,19 @@
-version: '3.8'
+version: "3.8"
services:
jellyfin:
image: jellyfin/jellyfin:10
- networks:
- - dokploy-network
- ports:
- - ${JELLYFIN_PORT}
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${JELLYFIN_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${JELLYFIN_PORT}"
volumes:
- config:/config
- cache:/cache
- media:/media
- restart: 'unless-stopped'
+ restart: "unless-stopped"
# Optional - alternative address used for autodiscovery
environment:
- JELLYFIN_PublishedServerUrl=http://${JELLYFIN_HOST}
# Optional - may be necessary for docker healthcheck to pass if running in host network mode
extra_hosts:
- - 'host.docker.internal:host-gateway'
+ - "host.docker.internal:host-gateway"
volumes:
config:
cache:
media:
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/jellyfin/index.ts b/apps/dokploy/templates/jellyfin/index.ts
index dc33b121a..61c9c9b7c 100644
--- a/apps/dokploy/templates/jellyfin/index.ts
+++ b/apps/dokploy/templates/jellyfin/index.ts
@@ -1,22 +1,25 @@
// EXAMPLE
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
- const port = 8096;
- const envs = [
- `JELLYFIN_HOST=${randomDomain}`,
- `HASH=${mainServiceHash}`,
- `JELLYFIN_PORT=${port}`,
+ const domain = generateRandomDomain(schema);
+ const domains: DomainSchema[] = [
+ {
+ host: domain,
+ port: 8096,
+ serviceName: "jellyfin",
+ },
];
+ const envs = [`JELLYFIN_HOST=${domain}`];
+
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/listmonk/docker-compose.yml b/apps/dokploy/templates/listmonk/docker-compose.yml
index beabf4474..725d0a098 100644
--- a/apps/dokploy/templates/listmonk/docker-compose.yml
+++ b/apps/dokploy/templates/listmonk/docker-compose.yml
@@ -36,10 +36,6 @@ services:
app:
restart: unless-stopped
image: listmonk/listmonk:v3.0.0
- ports:
- - "${LISTMONK_PORT}"
- networks:
- - dokploy-network
environment:
- TZ=Etc/UTC
depends_on:
@@ -47,15 +43,7 @@ services:
- setup
volumes:
- ../files/config.toml:/listmonk/config.toml
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${LISTMONK_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${LISTMONK_PORT}"
volumes:
listmonk-data:
driver: local
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/listmonk/index.ts b/apps/dokploy/templates/listmonk/index.ts
index d56d783df..725659ca4 100644
--- a/apps/dokploy/templates/listmonk/index.ts
+++ b/apps/dokploy/templates/listmonk/index.ts
@@ -1,20 +1,24 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const adminPassword = generatePassword(32);
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 9000,
+ serviceName: "app",
+ },
+ ];
+
const envs = [
- `LISTMONK_HOST=${randomDomain}`,
- "LISTMONK_PORT=9000",
- `HASH=${mainServiceHash}`,
`# login with admin:${adminPassword}`,
"# check config.toml in Advanced / Volumes for more options",
];
@@ -48,5 +52,6 @@ params = ""
return {
envs,
mounts,
+ domains,
};
}
diff --git a/apps/dokploy/templates/meilisearch/docker-compose.yml b/apps/dokploy/templates/meilisearch/docker-compose.yml
index 3ce8f2124..ae5ebcb1a 100644
--- a/apps/dokploy/templates/meilisearch/docker-compose.yml
+++ b/apps/dokploy/templates/meilisearch/docker-compose.yml
@@ -1,25 +1,14 @@
-version: '3.8'
+version: "3.8"
services:
meilisearch:
- networks:
- - dokploy-network
image: getmeili/meilisearch:v1.8.3
- ports:
- - ${MEILISEARCH_PORT}
volumes:
- meili_data:/meili_data
environment:
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
MEILI_ENV: ${MEILI_ENV}
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${MEILISEARCH_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${MEILISEARCH_PORT}
volumes:
meili_data:
driver: local
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/meilisearch/index.ts b/apps/dokploy/templates/meilisearch/index.ts
index afc009c0c..cfb8a9a4b 100644
--- a/apps/dokploy/templates/meilisearch/index.ts
+++ b/apps/dokploy/templates/meilisearch/index.ts
@@ -1,24 +1,26 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
const masterKey = generateBase64(32);
- const envs = [
- `MEILISEARCH_HOST=${randomDomain}`,
- "MEILISEARCH_PORT=7700",
- "MEILI_ENV=development",
- `MEILI_MASTER_KEY=${masterKey}`,
- `HASH=${mainServiceHash}`,
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 7700,
+ serviceName: "meilisearch",
+ },
];
+ const envs = ["MEILI_ENV=development", `MEILI_MASTER_KEY=${masterKey}`];
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/metabase/docker-compose.yml b/apps/dokploy/templates/metabase/docker-compose.yml
index 3eee3344d..4dca4d015 100644
--- a/apps/dokploy/templates/metabase/docker-compose.yml
+++ b/apps/dokploy/templates/metabase/docker-compose.yml
@@ -4,8 +4,6 @@ services:
image: metabase/metabase:v0.50.8
volumes:
- /dev/urandom:/dev/random:ro
- ports:
- - ${METABASE_PORT}
environment:
MB_DB_TYPE: postgres
MB_DB_DBNAME: metabaseappdb
@@ -13,17 +11,11 @@ services:
MB_DB_USER: metabase
MB_DB_PASS: mysecretpassword
MB_DB_HOST: postgres
- networks:
- - dokploy-network
healthcheck:
test: curl --fail -I http://localhost:3000/api/health || exit 1
interval: 15s
timeout: 5s
retries: 5
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${METABASE_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${METABASE_PORT}
postgres:
image: postgres:14
environment:
@@ -32,7 +24,3 @@ services:
POSTGRES_PASSWORD: mysecretpassword
networks:
- dokploy-network
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/metabase/index.ts b/apps/dokploy/templates/metabase/index.ts
index 1bd4281c6..0a08916e6 100644
--- a/apps/dokploy/templates/metabase/index.ts
+++ b/apps/dokploy/templates/metabase/index.ts
@@ -1,20 +1,22 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
- const envs = [
- `METABASE_HOST=${randomDomain}`,
- "METABASE_PORT=3000",
- `HASH=${mainServiceHash}`,
+
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 3000,
+ serviceName: "metabase",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/minio/docker-compose.yml b/apps/dokploy/templates/minio/docker-compose.yml
index 25701ea89..4b24bbcce 100644
--- a/apps/dokploy/templates/minio/docker-compose.yml
+++ b/apps/dokploy/templates/minio/docker-compose.yml
@@ -1,31 +1,13 @@
-version: '3.8'
+version: "3.8"
services:
minio:
image: minio/minio
- ports:
- - ${MINIO_API_PORT}
- - ${MINIO_DASHBOARD_PORT}
volumes:
- minio-data:/data
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin123
command: server /data --console-address ":9001"
- networks:
- - dokploy-network
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.service=${HASH}
- - traefik.http.routers.${HASH}.rule=Host(`${MINIO_DASHBOARD_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${MINIO_DASHBOARD_PORT}
- # API router and service
- - traefik.http.routers.${HASH}-api.service=${HASH}-api
- - traefik.http.routers.${HASH}-api.rule=Host(`${MINIO_API_HOST}`)
- - traefik.http.services.${HASH}-api.loadbalancer.server.port=${MINIO_API_PORT}
volumes:
minio-data:
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/minio/index.ts b/apps/dokploy/templates/minio/index.ts
index 8e8b101ed..1345aafdc 100644
--- a/apps/dokploy/templates/minio/index.ts
+++ b/apps/dokploy/templates/minio/index.ts
@@ -1,23 +1,28 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
const apiDomain = generateRandomDomain(schema);
- const envs = [
- `MINIO_DASHBOARD_HOST=${randomDomain}`,
- "MINIO_DASHBOARD_PORT=9001",
- `MINIO_API_HOST=${apiDomain}`,
- "MINIO_API_PORT=9000",
- `HASH=${mainServiceHash}`,
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 9001,
+ serviceName: "minio",
+ },
+ {
+ host: apiDomain,
+ port: 9000,
+ serviceName: "minio",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/n8n/docker-compose.yml b/apps/dokploy/templates/n8n/docker-compose.yml
index c26804dae..f8fb1f169 100644
--- a/apps/dokploy/templates/n8n/docker-compose.yml
+++ b/apps/dokploy/templates/n8n/docker-compose.yml
@@ -3,17 +3,9 @@ services:
n8n:
image: docker.n8n.io/n8nio/n8n:1.48.1
restart: always
- networks:
- - dokploy-network
- ports:
- - ${N8N_PORT}
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${N8N_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${N8N_PORT}
environment:
- N8N_HOST=${N8N_HOST}
- - N8N_PORT=5678
+ - N8N_PORT=${N8N_PORT}
- N8N_PROTOCOL=http
- NODE_ENV=production
- WEBHOOK_URL=https://${N8N_HOST}/
@@ -23,7 +15,4 @@ services:
- n8n_data:/home/node/.n8n
volumes:
- n8n_data:
-networks:
- dokploy-network:
- external: true
\ No newline at end of file
+ n8n_data:
diff --git a/apps/dokploy/templates/n8n/index.ts b/apps/dokploy/templates/n8n/index.ts
index 427ef80ed..da93c025f 100644
--- a/apps/dokploy/templates/n8n/index.ts
+++ b/apps/dokploy/templates/n8n/index.ts
@@ -1,21 +1,28 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 5678,
+ serviceName: "n8n",
+ },
+ ];
const envs = [
- `N8N_HOST=${randomDomain}`,
+ `N8N_HOST=${mainDomain}`,
"N8N_PORT=5678",
- `HASH=${mainServiceHash}`,
"GENERIC_TIMEZONE=Europe/Berlin",
];
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/nocodb/docker-compose.yml b/apps/dokploy/templates/nocodb/docker-compose.yml
index 60ecb57c7..726cf5e61 100644
--- a/apps/dokploy/templates/nocodb/docker-compose.yml
+++ b/apps/dokploy/templates/nocodb/docker-compose.yml
@@ -3,18 +3,10 @@ services:
nocodb:
image: nocodb/nocodb:0.251.1
restart: always
- networks:
- - dokploy-network
- ports:
- - ${NOCODB_PORT}
environment:
- NC_DB : "pg://root_db?u=postgres&p=password&d=root_db"
- PORT : ${NOCODB_PORT}
+ NC_DB: "pg://root_db?u=postgres&p=password&d=root_db"
+ PORT: ${NOCODB_PORT}
NC_REDIS_URL: ${NC_REDIS_URL}
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${NOCODB_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${NOCODB_PORT}
volumes:
- nc_data:/usr/app/data
@@ -30,15 +22,11 @@ services:
healthcheck:
interval: 10s
retries: 10
- test: "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""
+ test: 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"'
timeout: 2s
volumes:
- "db_data:/var/lib/postgresql/data"
-networks:
- dokploy-network:
- external: true
-
volumes:
db_data: {}
- nc_data: {}
\ No newline at end of file
+ nc_data: {}
diff --git a/apps/dokploy/templates/nocodb/index.ts b/apps/dokploy/templates/nocodb/index.ts
index 4b52920dc..60620dbd4 100644
--- a/apps/dokploy/templates/nocodb/index.ts
+++ b/apps/dokploy/templates/nocodb/index.ts
@@ -1,26 +1,28 @@
// EXAMPLE
import {
+ type DomainSchema,
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const secretBase = generateBase64(64);
- const toptKeyBase = generateBase64(32);
- const envs = [
- `NOCODB_HOST=${randomDomain}`,
- "NOCODB_PORT=8000",
- `NC_AUTH_JWT_SECRET=${secretBase}`,
- `HASH=${mainServiceHash}`,
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 8000,
+ serviceName: "nocodb",
+ },
];
+ const envs = ["NOCODB_PORT=8000", `NC_AUTH_JWT_SECRET=${secretBase}`];
+
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/odoo/docker-compose.yml b/apps/dokploy/templates/odoo/docker-compose.yml
index e6e2a7242..80b34f0c0 100644
--- a/apps/dokploy/templates/odoo/docker-compose.yml
+++ b/apps/dokploy/templates/odoo/docker-compose.yml
@@ -2,20 +2,12 @@ version: "3.8"
services:
web:
image: odoo:16.0
- networks:
- - dokploy-network
depends_on:
- db
- ports:
- - ${ODOO_PORT}
environment:
- HOST=db
- USER=odoo
- PASSWORD=odoo
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${ODOO_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${ODOO_PORT}"
volumes:
- odoo-web-data:/var/lib/odoo
- ../files/config:/etc/odoo
@@ -35,7 +27,3 @@ services:
volumes:
odoo-web-data:
odoo-db-data:
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/odoo/index.ts b/apps/dokploy/templates/odoo/index.ts
index 346e322b1..904293c11 100644
--- a/apps/dokploy/templates/odoo/index.ts
+++ b/apps/dokploy/templates/odoo/index.ts
@@ -1,20 +1,22 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
- const envs = [
- `ODOO_HOST=${randomDomain}`,
- "ODOO_PORT=8069",
- `HASH=${mainServiceHash}`,
+
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 8069,
+ serviceName: "web",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/open-webui/docker-compose.yml b/apps/dokploy/templates/open-webui/docker-compose.yml
index 457a60bd4..d396dacc0 100644
--- a/apps/dokploy/templates/open-webui/docker-compose.yml
+++ b/apps/dokploy/templates/open-webui/docker-compose.yml
@@ -1,6 +1,5 @@
-version: '3.8'
+version: "3.8"
services:
-
ollama:
volumes:
- ollama:/root/.ollama
@@ -13,24 +12,14 @@ services:
open-webui:
image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main}
- networks:
- - dokploy-network
volumes:
- open-webui:/app/backend/data
depends_on:
- ollama
environment:
- - 'OLLAMA_BASE_URL=http://ollama:11434'
- - 'WEBUI_SECRET_KEY='
+ - "OLLAMA_BASE_URL=http://ollama:11434"
+ - "WEBUI_SECRET_KEY="
restart: unless-stopped
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${OPEN_WEBUI_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${OPEN_WEBUI_PORT}"
-
-networks:
- dokploy-network:
- external: true
volumes:
ollama: {}
diff --git a/apps/dokploy/templates/open-webui/index.ts b/apps/dokploy/templates/open-webui/index.ts
index 931e0ec84..0431c2a11 100644
--- a/apps/dokploy/templates/open-webui/index.ts
+++ b/apps/dokploy/templates/open-webui/index.ts
@@ -1,22 +1,24 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
- const envs = [
- `OPEN_WEBUI_HOST=${randomDomain}`,
- "OPEN_WEBUI_PORT=8080",
- `HASH=${mainServiceHash}`,
- "OLLAMA_DOCKER_TAG=0.1.47",
- "WEBUI_DOCKER_TAG=0.3.7",
+
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 8080,
+ serviceName: "open-webui",
+ },
];
+ const envs = ["OLLAMA_DOCKER_TAG=0.1.47", "WEBUI_DOCKER_TAG=0.3.7"];
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/phpmyadmin/docker-compose.yml b/apps/dokploy/templates/phpmyadmin/docker-compose.yml
index c6743a580..1f775f09a 100644
--- a/apps/dokploy/templates/phpmyadmin/docker-compose.yml
+++ b/apps/dokploy/templates/phpmyadmin/docker-compose.yml
@@ -1,4 +1,4 @@
-version: '3.8'
+version: "3.8"
services:
db:
@@ -20,21 +20,9 @@ services:
PMA_USER: ${MYSQL_USER}
PMA_PASSWORD: ${MYSQL_PASSWORD}
PMA_ARBITRARY: 1
- ports:
- - ${PHPMYADMIN_PORT}
depends_on:
- db
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${PHPMYADMIN_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${PHPMYADMIN_PORT}
- networks:
- - dokploy-network
volumes:
db_data:
driver: local
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/phpmyadmin/index.ts b/apps/dokploy/templates/phpmyadmin/index.ts
index 1bae5ed02..e1c976b9d 100644
--- a/apps/dokploy/templates/phpmyadmin/index.ts
+++ b/apps/dokploy/templates/phpmyadmin/index.ts
@@ -1,20 +1,24 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
const rootPassword = generatePassword(32);
const password = generatePassword(32);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "phpmyadmin",
+ },
+ ];
const envs = [
- `PHPMYADMIN_HOST=${randomDomain}`,
- "PHPMYADMIN_PORT=80",
- `HASH=${mainServiceHash}`,
`MYSQL_ROOT_PASSWORD=${rootPassword}`,
"MYSQL_DATABASE=mysql",
"MYSQL_USER=phpmyadmin",
@@ -23,5 +27,6 @@ export function generate(schema: Schema): Template {
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/plausible/docker-compose.yml b/apps/dokploy/templates/plausible/docker-compose.yml
index 350cd87c5..62ce5ece4 100644
--- a/apps/dokploy/templates/plausible/docker-compose.yml
+++ b/apps/dokploy/templates/plausible/docker-compose.yml
@@ -32,17 +32,9 @@ services:
depends_on:
- plausible_db
- plausible_events_db
- ports:
- - ${PLAUSIBLE_PORT}
- networks:
- - dokploy-network
env_file:
- .env
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${PLAUSIBLE_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${PLAUSIBLE_PORT}"
volumes:
db-data:
driver: local
@@ -50,7 +42,3 @@ volumes:
driver: local
event-logs:
driver: local
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/plausible/index.ts b/apps/dokploy/templates/plausible/index.ts
index 643c5df05..2bd1212b6 100644
--- a/apps/dokploy/templates/plausible/index.ts
+++ b/apps/dokploy/templates/plausible/index.ts
@@ -1,24 +1,28 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
const secretBase = generateBase64(64);
const toptKeyBase = generateBase64(32);
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 8000,
+ serviceName: "plausible",
+ },
+ ];
+
const envs = [
- `PLAUSIBLE_HOST=${randomDomain}`,
- "PLAUSIBLE_PORT=8000",
- `BASE_URL=http://${randomDomain}`,
+ `BASE_URL=http://${mainDomain}`,
`SECRET_KEY_BASE=${secretBase}`,
`TOTP_VAULT_KEY=${toptKeyBase}`,
- `HASH=${mainServiceHash}`,
];
const mounts: Template["mounts"] = [
@@ -62,5 +66,6 @@ export function generate(schema: Schema): Template {
return {
envs,
mounts,
+ domains,
};
}
diff --git a/apps/dokploy/templates/pocketbase/docker-compose.yml b/apps/dokploy/templates/pocketbase/docker-compose.yml
index 7570dd585..fa6674afe 100644
--- a/apps/dokploy/templates/pocketbase/docker-compose.yml
+++ b/apps/dokploy/templates/pocketbase/docker-compose.yml
@@ -3,19 +3,7 @@ services:
pocketbase:
image: spectado/pocketbase:0.22.12
restart: unless-stopped
- ports:
- - ${POCKETBASE_PORT}
- networks:
- - dokploy-network
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${POCKETBASE_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${POCKETBASE_PORT}
volumes:
- /etc/dokploy/templates/${HASH}/data:/pb_data
- /etc/dokploy/templates/${HASH}/public:/pb_public
- /etc/dokploy/templates/${HASH}/migrations:/pb_migrations
-
-networks:
- dokploy-network:
- external: true
\ No newline at end of file
diff --git a/apps/dokploy/templates/pocketbase/index.ts b/apps/dokploy/templates/pocketbase/index.ts
index 1a5e964dc..f9fc7f8f0 100644
--- a/apps/dokploy/templates/pocketbase/index.ts
+++ b/apps/dokploy/templates/pocketbase/index.ts
@@ -1,21 +1,22 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
- const envs = [
- `POCKETBASE_HOST=${randomDomain}`,
- "POCKETBASE_PORT=80",
- `HASH=${mainServiceHash}`,
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "pocketbase",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/rocketchat/docker-compose.yml b/apps/dokploy/templates/rocketchat/docker-compose.yml
index 78056b9a7..751bd845c 100644
--- a/apps/dokploy/templates/rocketchat/docker-compose.yml
+++ b/apps/dokploy/templates/rocketchat/docker-compose.yml
@@ -9,18 +9,10 @@ services:
ROOT_URL: ${ROOT_URL:-http://${ROCKETCHAT_HOST}:${ROCKETCHAT_PORT}}
PORT: ${ROCKETCHAT_PORT}
DEPLOY_METHOD: docker
- DEPLOY_PLATFORM:
- REG_TOKEN:
+ DEPLOY_PLATFORM:
+ REG_TOKEN:
depends_on:
- mongodb
- ports:
- - ${ROCKETCHAT_PORT}
- networks:
- - dokploy-network
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${ROCKETCHAT_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${ROCKETCHAT_PORT}
mongodb:
image: docker.io/bitnami/mongodb:5.0
@@ -41,8 +33,3 @@ services:
volumes:
mongodb_data: { driver: local }
-
-
-networks:
- dokploy-network:
- external: true
\ No newline at end of file
diff --git a/apps/dokploy/templates/rocketchat/index.ts b/apps/dokploy/templates/rocketchat/index.ts
index 768cf46fe..0c10307a2 100644
--- a/apps/dokploy/templates/rocketchat/index.ts
+++ b/apps/dokploy/templates/rocketchat/index.ts
@@ -1,21 +1,25 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
- const envs = [
- `ROCKETCHAT_HOST=${randomDomain}`,
- "ROCKETCHAT_PORT=3000",
- `HASH=${mainServiceHash}`,
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "rocketchat",
+ },
];
+ const envs = [`ROCKETCHAT_HOST=${mainDomain}`, "ROCKETCHAT_PORT=3000"];
+
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/soketi/docker-compose.yml b/apps/dokploy/templates/soketi/docker-compose.yml
new file mode 100644
index 000000000..1784cdc79
--- /dev/null
+++ b/apps/dokploy/templates/soketi/docker-compose.yml
@@ -0,0 +1,12 @@
+version: "3"
+
+services:
+ soketi:
+ image: quay.io/soketi/soketi:1.4-16-debian
+ container_name: soketi
+ environment:
+ SOKETI_DEBUG: "1"
+ SOKETI_HOST: "0.0.0.0"
+ SOKETI_PORT: "6001"
+ SOKETI_METRICS_SERVER_PORT: "9601"
+ restart: unless-stopped
diff --git a/apps/dokploy/templates/soketi/index.ts b/apps/dokploy/templates/soketi/index.ts
new file mode 100644
index 000000000..47aa461df
--- /dev/null
+++ b/apps/dokploy/templates/soketi/index.ts
@@ -0,0 +1,28 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const metricsDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 6001,
+ serviceName: "soketi",
+ },
+ {
+ host: metricsDomain,
+ port: 9601,
+ serviceName: "soketi",
+ },
+ ];
+
+ return {
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/supabase/docker-compose.yml b/apps/dokploy/templates/supabase/docker-compose.yml
new file mode 100644
index 000000000..5be8158c0
--- /dev/null
+++ b/apps/dokploy/templates/supabase/docker-compose.yml
@@ -0,0 +1,437 @@
+# Usage
+# Start: docker compose up
+# With helpers: docker compose -f docker-compose.yml -f ../files/dev/docker-compose.dev.yml up
+# Stop: docker compose down
+# Destroy: docker compose -f docker-compose.yml -f ../files/dev/docker-compose.dev.yml down -v --remove-orphans
+
+name: supabase
+version: "3.8"
+
+services:
+ studio:
+ container_name: supabase-studio
+ image: supabase/studio:20240729-ce42139
+ networks:
+ - dokploy-network
+ restart: unless-stopped
+ healthcheck:
+ test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" ]
+ timeout: 5s
+ interval: 5s
+ retries: 3
+ depends_on:
+ analytics:
+ condition: service_healthy
+ environment:
+ STUDIO_PG_META_URL: http://meta:8080
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+
+ DEFAULT_ORGANIZATION_NAME: ${STUDIO_DEFAULT_ORGANIZATION}
+ DEFAULT_PROJECT_NAME: ${STUDIO_DEFAULT_PROJECT}
+
+ SUPABASE_URL: http://kong:8000
+ SUPABASE_PUBLIC_URL: http://${SUPABASE_HOST}
+ SUPABASE_ANON_KEY: ${ANON_KEY}
+ SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
+ AUTH_JWT_SECRET: ${JWT_SECRET}
+
+ LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
+ LOGFLARE_URL: http://analytics:4000
+ NEXT_PUBLIC_ENABLE_LOGS: true
+ # Comment to use Big Query backend for analytics
+ NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
+ # Uncomment to use Big Query backend for analytics
+ # NEXT_ANALYTICS_BACKEND_PROVIDER: bigquery
+
+ kong:
+ container_name: supabase-kong
+ image: kong:2.8.1
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ # https://unix.stackexchange.com/a/294837
+ entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start'
+ #ports:
+ # - ${KONG_HTTP_PORT}:8000/tcp
+ # - ${KONG_HTTPS_PORT}:8443/tcp
+ expose:
+ - 8000
+ - 8443
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.frontend-app.rule=Host(`${SUPABASE_HOST}`)
+ - traefik.http.routers.frontend-app.entrypoints=web
+ - traefik.http.services.frontend-app.loadbalancer.server.port=${KONG_HTTP_PORT}
+ depends_on:
+ analytics:
+ condition: service_healthy
+ environment:
+ KONG_DATABASE: "off"
+ KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
+ # https://github.com/supabase/cli/issues/14
+ KONG_DNS_ORDER: LAST,A,CNAME
+ KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
+ KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
+ KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
+ SUPABASE_ANON_KEY: ${ANON_KEY}
+ SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
+ DASHBOARD_USERNAME: ${DASHBOARD_USERNAME}
+ DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
+ volumes:
+ # https://github.com/supabase/supabase/issues/12661
+ - ../files/volumes/api/kong.yml:/home/kong/temp.yml:ro
+
+ auth:
+ container_name: supabase-auth
+ image: supabase/gotrue:v2.158.1
+ networks:
+ - dokploy-network
+ depends_on:
+ db:
+ # Disable this if you are using an external Postgres database
+ condition: service_healthy
+ analytics:
+ condition: service_healthy
+ healthcheck:
+ test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health" ]
+ timeout: 5s
+ interval: 5s
+ retries: 3
+ restart: unless-stopped
+ environment:
+ GOTRUE_API_HOST: 0.0.0.0
+ GOTRUE_API_PORT: 9999
+ API_EXTERNAL_URL: http://${SUPABASE_HOST}
+
+ GOTRUE_DB_DRIVER: postgres
+ GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
+
+ GOTRUE_SITE_URL: http://${SUPABASE_HOST}
+ GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS}
+ GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP}
+
+ GOTRUE_JWT_ADMIN_ROLES: service_role
+ GOTRUE_JWT_AUD: authenticated
+ GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
+ GOTRUE_JWT_EXP: ${JWT_EXPIRY}
+ GOTRUE_JWT_SECRET: ${JWT_SECRET}
+
+ GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
+ GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: ${ENABLE_ANONYMOUS_USERS}
+ GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
+ # GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: true
+ # GOTRUE_SMTP_MAX_FREQUENCY: 1s
+ GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
+ GOTRUE_SMTP_HOST: ${SMTP_HOSTNAME}
+ GOTRUE_SMTP_PORT: ${SMTP_PORT}
+ GOTRUE_SMTP_USER: ${SMTP_USER}
+ GOTRUE_SMTP_PASS: ${SMTP_PASS}
+ GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
+ GOTRUE_MAILER_URLPATHS_INVITE: ${MAILER_URLPATHS_INVITE}
+ GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${MAILER_URLPATHS_CONFIRMATION}
+ GOTRUE_MAILER_URLPATHS_RECOVERY: ${MAILER_URLPATHS_RECOVERY}
+ GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${MAILER_URLPATHS_EMAIL_CHANGE}
+
+ GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP}
+ GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM}
+ # Uncomment to enable custom access token hook. You'll need to create a public.custom_access_token_hook function and grant necessary permissions.
+ # See: https://supabase.com/docs/guides/auth/auth-hooks#hook-custom-access-token for details
+ # GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED="true"
+ # GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="pg-functions://postgres/public/custom_access_token_hook"
+
+ # GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED="true"
+ # GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/mfa_verification_attempt"
+
+ # GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED="true"
+ # GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/password_verification_attempt"
+
+
+
+
+ rest:
+ container_name: supabase-rest
+ image: postgrest/postgrest:v12.2.0
+ networks:
+ - dokploy-network
+ depends_on:
+ db:
+ # Disable this if you are using an external Postgres database
+ condition: service_healthy
+ analytics:
+ condition: service_healthy
+ restart: unless-stopped
+ environment:
+ PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
+ PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS}
+ PGRST_DB_ANON_ROLE: anon
+ PGRST_JWT_SECRET: ${JWT_SECRET}
+ PGRST_DB_USE_LEGACY_GUCS: "false"
+ PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
+ PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXPIRY}
+ command: "postgrest"
+
+ realtime:
+ # This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
+ container_name: realtime-dev.supabase-realtime
+ image: supabase/realtime:v2.30.23
+ networks:
+ - dokploy-network
+ depends_on:
+ db:
+ # Disable this if you are using an external Postgres database
+ condition: service_healthy
+ analytics:
+ condition: service_healthy
+ healthcheck:
+ test: [ "CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "-H", "Authorization: Bearer ${ANON_KEY}", "http://localhost:4000/api/tenants/realtime-dev/health" ]
+ timeout: 5s
+ interval: 5s
+ retries: 3
+ restart: unless-stopped
+ environment:
+ PORT: 4000
+ DB_HOST: ${POSTGRES_HOSTNAME}
+ DB_PORT: ${POSTGRES_PORT}
+ DB_USER: supabase_admin
+ DB_PASSWORD: ${POSTGRES_PASSWORD}
+ DB_NAME: ${POSTGRES_DB}
+ DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
+ DB_ENC_KEY: supabaserealtime
+ API_JWT_SECRET: ${JWT_SECRET}
+ SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
+ ERL_AFLAGS: -proto_dist inet_tcp
+ DNS_NODES: "''"
+ RLIMIT_NOFILE: "10000"
+ APP_NAME: realtime
+ SEED_SELF_HOST: true
+
+ # To use S3 backed storage: docker compose -f docker-compose.yml -f docker-compose.s3.yml up
+ storage:
+ container_name: supabase-storage
+ image: supabase/storage-api:v1.0.6
+ networks:
+ - dokploy-network
+ depends_on:
+ db:
+ # Disable this if you are using an external Postgres database
+ condition: service_healthy
+ rest:
+ condition: service_started
+ imgproxy:
+ condition: service_started
+ healthcheck:
+ test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/status" ]
+ timeout: 5s
+ interval: 5s
+ retries: 3
+ restart: unless-stopped
+ environment:
+ ANON_KEY: ${ANON_KEY}
+ SERVICE_KEY: ${SERVICE_ROLE_KEY}
+ POSTGREST_URL: http://rest:3000
+ PGRST_JWT_SECRET: ${JWT_SECRET}
+ DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
+ FILE_SIZE_LIMIT: 52428800
+ STORAGE_BACKEND: file
+ FILE_STORAGE_BACKEND_PATH: /var/lib/storage
+ TENANT_ID: stub
+ # TODO: https://github.com/supabase/storage-api/issues/55
+ REGION: stub
+ GLOBAL_S3_BUCKET: stub
+ ENABLE_IMAGE_TRANSFORMATION: "true"
+ IMGPROXY_URL: http://imgproxy:5001
+ volumes:
+ - ../files/volumes/storage:/var/lib/storage:z
+
+ imgproxy:
+ container_name: supabase-imgproxy
+ image: darthsim/imgproxy:v3.8.0
+ networks:
+ - dokploy-network
+ healthcheck:
+ test: [ "CMD", "imgproxy", "health" ]
+ timeout: 5s
+ interval: 5s
+ retries: 3
+ environment:
+ IMGPROXY_BIND: ":5001"
+ IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
+ IMGPROXY_USE_ETAG: "true"
+ IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION}
+ volumes:
+ - ../files/volumes/storage:/var/lib/storage:z
+
+ meta:
+ container_name: supabase-meta
+ image: supabase/postgres-meta:v0.83.2
+ networks:
+ - dokploy-network
+ depends_on:
+ db:
+ # Disable this if you are using an external Postgres database
+ condition: service_healthy
+ analytics:
+ condition: service_healthy
+ restart: unless-stopped
+ environment:
+ PG_META_PORT: 8080
+ PG_META_DB_HOST: ${POSTGRES_HOSTNAME}
+ PG_META_DB_PORT: ${POSTGRES_PORT}
+ PG_META_DB_NAME: ${POSTGRES_DB}
+ PG_META_DB_USER: supabase_admin
+ PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
+
+ functions:
+ container_name: supabase-edge-functions
+ image: supabase/edge-runtime:v1.56.0
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ analytics:
+ condition: service_healthy
+ environment:
+ JWT_SECRET: ${JWT_SECRET}
+ SUPABASE_URL: http://kong:8000
+ SUPABASE_ANON_KEY: ${ANON_KEY}
+ SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
+ SUPABASE_DB_URL: postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
+ # TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786
+ VERIFY_JWT: "${FUNCTIONS_VERIFY_JWT}"
+ volumes:
+ - ../files/volumes/functions:/home/deno/functions:Z
+ command:
+ - start
+ - --main-service
+ - /home/deno/functions/main
+
+ analytics:
+ container_name: supabase-analytics
+ image: supabase/logflare:1.4.0
+ networks:
+ - dokploy-network
+ healthcheck:
+ test: [ "CMD", "curl", "http://localhost:4000/health" ]
+ timeout: 5s
+ interval: 5s
+ retries: 10
+ restart: unless-stopped
+ depends_on:
+ db:
+ # Disable this if you are using an external Postgres database
+ condition: service_healthy
+ # Uncomment to use Big Query backend for analytics
+ # volumes:
+ # - type: bind
+ # source: ${PWD}/gcloud.json
+ # target: /opt/app/rel/logflare/bin/gcloud.json
+ # read_only: true
+ environment:
+ LOGFLARE_NODE_HOST: 127.0.0.1
+ DB_USERNAME: supabase_admin
+ DB_DATABASE: ${POSTGRES_DB}
+ DB_HOSTNAME: ${POSTGRES_HOSTNAME}
+ DB_PORT: ${POSTGRES_PORT}
+ DB_PASSWORD: ${POSTGRES_PASSWORD}
+ DB_SCHEMA: _analytics
+ LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
+ LOGFLARE_SINGLE_TENANT: true
+ LOGFLARE_SUPABASE_MODE: true
+ LOGFLARE_MIN_CLUSTER_SIZE: 1
+
+ # Comment variables to use Big Query backend for analytics
+ POSTGRES_BACKEND_URL: postgresql://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
+ POSTGRES_BACKEND_SCHEMA: _analytics
+ LOGFLARE_FEATURE_FLAG_OVERRIDE: multibackend=true
+ # Uncomment to use Big Query backend for analytics
+ # GOOGLE_PROJECT_ID: ${GOOGLE_PROJECT_ID}
+ # GOOGLE_PROJECT_NUMBER: ${GOOGLE_PROJECT_NUMBER}
+ #ports:
+ # - 4000:4000
+ expose:
+ - 4000
+
+ # Comment out everything below this point if you are using an external Postgres database
+ db:
+ container_name: supabase-db
+ image: supabase/postgres:15.1.1.78
+ networks:
+ - dokploy-network
+ healthcheck:
+ test: pg_isready -U postgres -h localhost
+ interval: 5s
+ timeout: 5s
+ retries: 10
+ depends_on:
+ vector:
+ condition: service_healthy
+ command:
+ - postgres
+ - -c
+ - config_file=/etc/postgresql/postgresql.conf
+ - -c
+ - log_min_messages=fatal # prevents Realtime polling queries from appearing in logs
+ restart: unless-stopped
+ #ports:
+ # # Pass down internal port because it's set dynamically by other services
+ # - ${POSTGRES_PORT}:${POSTGRES_PORT}
+ expose:
+ - ${POSTGRES_PORT}
+ environment:
+ POSTGRES_HOST: /var/run/postgresql
+ PGPORT: ${POSTGRES_PORT}
+ POSTGRES_PORT: ${POSTGRES_PORT}
+ PGPASSWORD: ${POSTGRES_PASSWORD}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ PGDATABASE: ${POSTGRES_DB}
+ POSTGRES_DB: ${POSTGRES_DB}
+ JWT_SECRET: ${JWT_SECRET}
+ JWT_EXP: ${JWT_EXPIRY}
+ volumes:
+ - ../files/volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z
+ # Must be superuser to create event trigger
+ - ../files/volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z
+ # Must be superuser to alter reserved role
+ - ../files/volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z
+ # Initialize the database settings with JWT_SECRET and JWT_EXP
+ - ../files/volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z
+ # PGDATA directory is persisted between restarts
+ - ../files/volumes/db/data:/var/lib/postgresql/data:Z
+ # Changes required for Analytics support
+ - ../files/volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z
+ # Use named volume to persist pgsodium decryption key between restarts
+ - db-config:/etc/postgresql-custom
+
+ vector:
+ container_name: supabase-vector
+ image: timberio/vector:0.28.1-alpine
+ networks:
+ - dokploy-network
+ healthcheck:
+ test:
+ [
+
+ "CMD",
+ "wget",
+ "--no-verbose",
+ "--tries=1",
+ "--spider",
+ "http://vector:9001/health"
+ ]
+ timeout: 5s
+ interval: 5s
+ retries: 3
+ volumes:
+ - ../files/volumes/logs/vector.yml:/etc/vector/vector.yml:ro
+ - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro
+ environment:
+ LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
+ command: [ "--config", "etc/vector/vector.yml" ]
+
+volumes:
+ db-config:
+
+
+networks:
+ dokploy-network:
+ external: true
diff --git a/apps/dokploy/templates/supabase/index.ts b/apps/dokploy/templates/supabase/index.ts
new file mode 100644
index 000000000..2456f015f
--- /dev/null
+++ b/apps/dokploy/templates/supabase/index.ts
@@ -0,0 +1,989 @@
+import { createHmac, randomBytes } from "node:crypto";
+import {
+ type Schema,
+ type Template,
+ generateBase64,
+ generateHash,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+interface JWTPayload {
+ role: "anon" | "service_role";
+ iss: string;
+ iat: number;
+ exp: number;
+}
+
+function base64UrlEncode(str: string): string {
+ return Buffer.from(str)
+ .toString("base64")
+ .replace(/\+/g, "-")
+ .replace(/\//g, "_")
+ .replace(/=/g, "");
+}
+
+function generateJWT(payload: JWTPayload, secret: string): string {
+ const header = { alg: "HS256", typ: "JWT" };
+
+ const encodedHeader = base64UrlEncode(JSON.stringify(header));
+ const encodedPayload = base64UrlEncode(JSON.stringify(payload));
+
+ const signature = createHmac("sha256", secret)
+ .update(`${encodedHeader}.${encodedPayload}`)
+ .digest("base64url");
+
+ return `${encodedHeader}.${encodedPayload}.${signature}`;
+}
+
+export function generateSupabaseAnonJWT(secret: string): string {
+ const now = Math.floor(Date.now() / 1000);
+ const payload: JWTPayload = {
+ role: "anon",
+ iss: "supabase",
+ iat: now,
+ exp: now + 100 * 365 * 24 * 60 * 60, // 100 years
+ };
+
+ return generateJWT(payload, secret);
+}
+
+export function generateSupabaseServiceJWT(secret: string): string {
+ const now = Math.floor(Date.now() / 1000);
+ const payload: JWTPayload = {
+ role: "service_role",
+ iss: "supabase",
+ iat: now,
+ exp: now + 100 * 365 * 24 * 60 * 60, // 100 years
+ };
+
+ return generateJWT(payload, secret);
+}
+
+export function generate(schema: Schema): Template {
+ const mainServiceHash = generateHash(schema.projectName);
+ const randomDomain = generateRandomDomain(schema);
+
+ const postgresPassword = generatePassword(32);
+ const jwtSecret = generateBase64(32);
+ const dashboardPassword = generatePassword(32);
+ const logflareApiKey = generatePassword(32);
+
+ const annonKey = generateSupabaseAnonJWT(jwtSecret);
+ const serviceRoleKey = generateSupabaseServiceJWT(jwtSecret);
+
+ const envs = [
+ `SUPABASE_HOST=${randomDomain}`,
+ `POSTGRES_PASSWORD=${postgresPassword}`,
+ `JWT_SECRET=${jwtSecret}`,
+ `ANON_KEY=${annonKey}`,
+ `SERVICE_ROLE_KEY=${serviceRoleKey}`,
+ "DASHBOARD_USERNAME=supabase",
+ `DASHBOARD_PASSWORD=${dashboardPassword}`,
+ "POSTGRES_HOSTNAME=db",
+ "POSTGRES_DB=postgres",
+ "POSTGRES_PORT=5432",
+ "KONG_HTTP_PORT=8000",
+ "KONG_HTTPS_PORT=8443",
+ "PGRST_DB_SCHEMAS=public,storage,graphql_public",
+ "ADDITIONAL_REDIRECT_URLS=",
+ "JWT_EXPIRY=3600",
+ "DISABLE_SIGNUP=false",
+ `MAILER_URLPATHS_CONFIRMATION=\"/auth/v1/verify\"`,
+ `MAILER_URLPATHS_INVITE=\"/auth/v1/verify\"`,
+ `MAILER_URLPATHS_RECOVERY=\"/auth/v1/verify\"`,
+ `MAILER_URLPATHS_EMAIL_CHANGE=\"/auth/v1/verify\"`,
+ "ENABLE_EMAIL_SIGNUP=true",
+ "ENABLE_EMAIL_AUTOCONFIRM=false",
+ "SMTP_ADMIN_EMAIL=admin@example.com",
+ "SMTP_HOSTNAME=supabase-mail",
+ "SMTP_PORT=2500",
+ "SMTP_USER=fake_mail_user",
+ "SMTP_PASS=fake_mail_password",
+ "SMTP_SENDER_NAME=fake_sender",
+ "ENABLE_ANONYMOUS_USERS=false",
+ "ENABLE_PHONE_SIGNUP=true",
+ "ENABLE_PHONE_AUTOCONFIRM=true",
+ "STUDIO_DEFAULT_ORGANIZATION=Default Organization",
+ "STUDIO_DEFAULT_PROJECT=Default Project",
+ "STUDIO_PORT=3000",
+ "IMGPROXY_ENABLE_WEBP_DETECTION=true",
+ "FUNCTIONS_VERIFY_JWT=false",
+ `LOGFLARE_LOGGER_BACKEND_API_KEY=${logflareApiKey}`,
+ `LOGFLARE_API_KEY=${logflareApiKey}`,
+ "DOCKER_SOCKET_LOCATION=/var/run/docker.sock",
+ "GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID",
+ "GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER",
+ `HASH=${mainServiceHash}`,
+ ];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "/volumes/api/kong.yml",
+ content: `
+_format_version: '2.1'
+_transform: true
+
+###
+### Consumers / Users
+###
+consumers:
+ - username: DASHBOARD
+ - username: anon
+ keyauth_credentials:
+ - key: $SUPABASE_ANON_KEY
+ - username: service_role
+ keyauth_credentials:
+ - key: $SUPABASE_SERVICE_KEY
+
+###
+### Access Control List
+###
+acls:
+ - consumer: anon
+ group: anon
+ - consumer: service_role
+ group: admin
+
+###
+### Dashboard credentials
+###
+basicauth_credentials:
+ - consumer: DASHBOARD
+ username: $DASHBOARD_USERNAME
+ password: $DASHBOARD_PASSWORD
+
+###
+### API Routes
+###
+services:
+ ## Open Auth routes
+ - name: auth-v1-open
+ url: http://auth:9999/verify
+ routes:
+ - name: auth-v1-open
+ strip_path: true
+ paths:
+ - /auth/v1/verify
+ plugins:
+ - name: cors
+ - name: auth-v1-open-callback
+ url: http://auth:9999/callback
+ routes:
+ - name: auth-v1-open-callback
+ strip_path: true
+ paths:
+ - /auth/v1/callback
+ plugins:
+ - name: cors
+ - name: auth-v1-open-authorize
+ url: http://auth:9999/authorize
+ routes:
+ - name: auth-v1-open-authorize
+ strip_path: true
+ paths:
+ - /auth/v1/authorize
+ plugins:
+ - name: cors
+
+ ## Secure Auth routes
+ - name: auth-v1
+ _comment: 'GoTrue: /auth/v1/* -> http://auth:9999/*'
+ url: http://auth:9999/
+ routes:
+ - name: auth-v1-all
+ strip_path: true
+ paths:
+ - /auth/v1/
+ plugins:
+ - name: cors
+ - name: key-auth
+ config:
+ hide_credentials: false
+ - name: acl
+ config:
+ hide_groups_header: true
+ allow:
+ - admin
+ - anon
+
+ ## Secure REST routes
+ - name: rest-v1
+ _comment: 'PostgREST: /rest/v1/* -> http://rest:3000/*'
+ url: http://rest:3000/
+ routes:
+ - name: rest-v1-all
+ strip_path: true
+ paths:
+ - /rest/v1/
+ plugins:
+ - name: cors
+ - name: key-auth
+ config:
+ hide_credentials: true
+ - name: acl
+ config:
+ hide_groups_header: true
+ allow:
+ - admin
+ - anon
+
+ ## Secure GraphQL routes
+ - name: graphql-v1
+ _comment: 'PostgREST: /graphql/v1/* -> http://rest:3000/rpc/graphql'
+ url: http://rest:3000/rpc/graphql
+ routes:
+ - name: graphql-v1-all
+ strip_path: true
+ paths:
+ - /graphql/v1
+ plugins:
+ - name: cors
+ - name: key-auth
+ config:
+ hide_credentials: true
+ - name: request-transformer
+ config:
+ add:
+ headers:
+ - Content-Profile:graphql_public
+ - name: acl
+ config:
+ hide_groups_header: true
+ allow:
+ - admin
+ - anon
+
+ ## Secure Realtime routes
+ - name: realtime-v1-ws
+ _comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
+ url: http://realtime-dev.supabase-realtime:4000/socket
+ protocol: ws
+ routes:
+ - name: realtime-v1-ws
+ strip_path: true
+ paths:
+ - /realtime/v1/
+ plugins:
+ - name: cors
+ - name: key-auth
+ config:
+ hide_credentials: false
+ - name: acl
+ config:
+ hide_groups_header: true
+ allow:
+ - admin
+ - anon
+ - name: realtime-v1-rest
+ _comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
+ url: http://realtime-dev.supabase-realtime:4000/api
+ protocol: http
+ routes:
+ - name: realtime-v1-rest
+ strip_path: true
+ paths:
+ - /realtime/v1/api
+ plugins:
+ - name: cors
+ - name: key-auth
+ config:
+ hide_credentials: false
+ - name: acl
+ config:
+ hide_groups_header: true
+ allow:
+ - admin
+ - anon
+ ## Storage routes: the storage server manages its own auth
+ - name: storage-v1
+ _comment: 'Storage: /storage/v1/* -> http://storage:5000/*'
+ url: http://storage:5000/
+ routes:
+ - name: storage-v1-all
+ strip_path: true
+ paths:
+ - /storage/v1/
+ plugins:
+ - name: cors
+
+ ## Edge Functions routes
+ - name: functions-v1
+ _comment: 'Edge Functions: /functions/v1/* -> http://functions:9000/*'
+ url: http://functions:9000/
+ routes:
+ - name: functions-v1-all
+ strip_path: true
+ paths:
+ - /functions/v1/
+ plugins:
+ - name: cors
+
+ ## Analytics routes
+ - name: analytics-v1
+ _comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*'
+ url: http://analytics:4000/
+ routes:
+ - name: analytics-v1-all
+ strip_path: true
+ paths:
+ - /analytics/v1/
+
+ ## Secure Database routes
+ - name: meta
+ _comment: 'pg-meta: /pg/* -> http://pg-meta:8080/*'
+ url: http://meta:8080/
+ routes:
+ - name: meta-all
+ strip_path: true
+ paths:
+ - /pg/
+ plugins:
+ - name: key-auth
+ config:
+ hide_credentials: false
+ - name: acl
+ config:
+ hide_groups_header: true
+ allow:
+ - admin
+
+ ## Protected Dashboard - catch all remaining routes
+ - name: dashboard
+ _comment: 'Studio: /* -> http://studio:3000/*'
+ url: http://studio:3000/
+ routes:
+ - name: dashboard-all
+ strip_path: true
+ paths:
+ - /
+ plugins:
+ - name: cors
+ - name: basic-auth
+ config:
+ hide_credentials: true
+ `,
+ },
+ {
+ filePath: "/volumes/db/init/data.sql",
+ content: `
+ `,
+ },
+ {
+ filePath: "/volumes/db/jwt.sql",
+ content: `
+\\set jwt_secret \`echo "$JWT_SECRET"\`
+\\set jwt_exp \`echo "$JWT_EXP"\`
+
+ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
+ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';
+ `,
+ },
+ {
+ filePath: "/volumes/db/logs.sql",
+ content: `
+\\set pguser \`echo "$POSTGRES_USER"\`
+
+create schema if not exists _analytics;
+alter schema _analytics owner to :pguser;
+ `,
+ },
+ {
+ filePath: "/volumes/db/realtime.sql",
+ content: `
+\\set pguser \`echo "$POSTGRES_USER"\`
+
+create schema if not exists _realtime;
+alter schema _realtime owner to :pguser;
+ `,
+ },
+ {
+ filePath: "/volumes/db/roles.sql",
+ content: `
+-- NOTE: change to your own passwords for production environments
+\\set pgpass \`echo "$POSTGRES_PASSWORD"\`
+
+ALTER USER authenticator WITH PASSWORD :'pgpass';
+ALTER USER pgbouncer WITH PASSWORD :'pgpass';
+ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';
+ALTER USER supabase_functions_admin WITH PASSWORD :'pgpass';
+ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';
+ `,
+ },
+ {
+ filePath: "/volumes/db/webhooks.sql",
+ content: `
+BEGIN;
+ -- Create pg_net extension
+ CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;
+ -- Create supabase_functions schema
+ CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;
+ GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;
+ ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
+ ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;
+ ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
+ -- supabase_functions.migrations definition
+ CREATE TABLE supabase_functions.migrations (
+ version text PRIMARY KEY,
+ inserted_at timestamptz NOT NULL DEFAULT NOW()
+ );
+ -- Initial supabase_functions migration
+ INSERT INTO supabase_functions.migrations (version) VALUES ('initial');
+ -- supabase_functions.hooks definition
+ CREATE TABLE supabase_functions.hooks (
+ id bigserial PRIMARY KEY,
+ hook_table_id integer NOT NULL,
+ hook_name text NOT NULL,
+ created_at timestamptz NOT NULL DEFAULT NOW(),
+ request_id bigint
+ );
+ CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
+ CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
+ COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
+ CREATE FUNCTION supabase_functions.http_request()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $function$
+ DECLARE
+ request_id bigint;
+ payload jsonb;
+ url text := TG_ARGV[0]::text;
+ method text := TG_ARGV[1]::text;
+ headers jsonb DEFAULT '{}'::jsonb;
+ params jsonb DEFAULT '{}'::jsonb;
+ timeout_ms integer DEFAULT 1000;
+ BEGIN
+ IF url IS NULL OR url = 'null' THEN
+ RAISE EXCEPTION 'url argument is missing';
+ END IF;
+
+ IF method IS NULL OR method = 'null' THEN
+ RAISE EXCEPTION 'method argument is missing';
+ END IF;
+
+ IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
+ headers = '{"Content-Type": "application/json"}'::jsonb;
+ ELSE
+ headers = TG_ARGV[2]::jsonb;
+ END IF;
+
+ IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
+ params = '{}'::jsonb;
+ ELSE
+ params = TG_ARGV[3]::jsonb;
+ END IF;
+
+ IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
+ timeout_ms = 1000;
+ ELSE
+ timeout_ms = TG_ARGV[4]::integer;
+ END IF;
+
+ CASE
+ WHEN method = 'GET' THEN
+ SELECT http_get INTO request_id FROM net.http_get(
+ url,
+ params,
+ headers,
+ timeout_ms
+ );
+ WHEN method = 'POST' THEN
+ payload = jsonb_build_object(
+ 'old_record', OLD,
+ 'record', NEW,
+ 'type', TG_OP,
+ 'table', TG_TABLE_NAME,
+ 'schema', TG_TABLE_SCHEMA
+ );
+
+ SELECT http_post INTO request_id FROM net.http_post(
+ url,
+ payload,
+ params,
+ headers,
+ timeout_ms
+ );
+ ELSE
+ RAISE EXCEPTION 'method argument % is invalid', method;
+ END CASE;
+
+ INSERT INTO supabase_functions.hooks
+ (hook_table_id, hook_name, request_id)
+ VALUES
+ (TG_RELID, TG_NAME, request_id);
+
+ RETURN NEW;
+ END
+ $function$;
+ -- Supabase super admin
+ DO
+ $$
+ BEGIN
+ IF NOT EXISTS (
+ SELECT 1
+ FROM pg_roles
+ WHERE rolname = 'supabase_functions_admin'
+ )
+ THEN
+ CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
+ END IF;
+ END
+ $$;
+ GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;
+ GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;
+ GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;
+ ALTER USER supabase_functions_admin SET search_path = "supabase_functions";
+ ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin;
+ ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin;
+ ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin;
+ GRANT supabase_functions_admin TO postgres;
+ -- Remove unused supabase_pg_net_admin role
+ DO
+ $$
+ BEGIN
+ IF EXISTS (
+ SELECT 1
+ FROM pg_roles
+ WHERE rolname = 'supabase_pg_net_admin'
+ )
+ THEN
+ REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;
+ DROP OWNED BY supabase_pg_net_admin;
+ DROP ROLE supabase_pg_net_admin;
+ END IF;
+ END
+ $$;
+ -- pg_net grants when extension is already enabled
+ DO
+ $$
+ BEGIN
+ IF EXISTS (
+ SELECT 1
+ FROM pg_extension
+ WHERE extname = 'pg_net'
+ )
+ THEN
+ GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
+ ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
+ ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
+ ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
+ ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
+ REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
+ REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
+ GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
+ GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
+ END IF;
+ END
+ $$;
+ -- Event trigger for pg_net
+ CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
+ RETURNS event_trigger
+ LANGUAGE plpgsql
+ AS $$
+ BEGIN
+ IF EXISTS (
+ SELECT 1
+ FROM pg_event_trigger_ddl_commands() AS ev
+ JOIN pg_extension AS ext
+ ON ev.objid = ext.oid
+ WHERE ext.extname = 'pg_net'
+ )
+ THEN
+ GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
+ ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
+ ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
+ ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
+ ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
+ REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
+ REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
+ GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
+ GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
+ END IF;
+ END;
+ $$;
+ COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';
+ DO
+ $$
+ BEGIN
+ IF NOT EXISTS (
+ SELECT 1
+ FROM pg_event_trigger
+ WHERE evtname = 'issue_pg_net_access'
+ ) THEN
+ CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')
+ EXECUTE PROCEDURE extensions.grant_pg_net_access();
+ END IF;
+ END
+ $$;
+ INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');
+ ALTER function supabase_functions.http_request() SECURITY DEFINER;
+ ALTER function supabase_functions.http_request() SET search_path = supabase_functions;
+ REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;
+ GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;
+COMMIT;
+ `,
+ },
+ {
+ filePath: "/volumes/functions/hello/index.ts",
+ content: `
+// Follow this setup guide to integrate the Deno language server with your editor:
+// https://deno.land/manual/getting_started/setup_your_environment
+// This enables autocomplete, go to definition, etc.
+
+import { serve } from "https://deno.land/std@0.177.1/http/server.ts"
+
+serve(async () => {
+ return new Response(
+ \`"Hello from Edge Functions!"\`,
+ { headers: { "Content-Type": "application/json" } },
+ )
+})
+
+// To invoke:
+// curl 'http://localhost:/functions/v1/hello' \\
+// --header 'Authorization: Bearer '
+ `,
+ },
+ {
+ filePath: "/volumes/functions/main/index.ts",
+ content: `
+import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
+import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
+
+console.log('main function started')
+
+const JWT_SECRET = Deno.env.get('JWT_SECRET')
+const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
+
+function getAuthToken(req: Request) {
+ const authHeader = req.headers.get('authorization')
+ if (!authHeader) {
+ throw new Error('Missing authorization header')
+ }
+ const [bearer, token] = authHeader.split(' ')
+ if (bearer !== 'Bearer') {
+ throw new Error(\`Auth header is not 'Bearer {token}'\`)
+ }
+ return token
+}
+
+async function verifyJWT(jwt: string): Promise {
+ const encoder = new TextEncoder()
+ const secretKey = encoder.encode(JWT_SECRET)
+ try {
+ await jose.jwtVerify(jwt, secretKey)
+ } catch (err) {
+ console.error(err)
+ return false
+ }
+ return true
+}
+
+serve(async (req: Request) => {
+ if (req.method !== 'OPTIONS' && VERIFY_JWT) {
+ try {
+ const token = getAuthToken(req)
+ const isValidJWT = await verifyJWT(token)
+
+ if (!isValidJWT) {
+ return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
+ status: 401,
+ headers: { 'Content-Type': 'application/json' },
+ })
+ }
+ } catch (e) {
+ console.error(e)
+ return new Response(JSON.stringify({ msg: e.toString() }), {
+ status: 401,
+ headers: { 'Content-Type': 'application/json' },
+ })
+ }
+ }
+
+ const url = new URL(req.url)
+ const { pathname } = url
+ const path_parts = pathname.split('/')
+ const service_name = path_parts[1]
+
+ if (!service_name || service_name === '') {
+ const error = { msg: 'missing function name in request' }
+ return new Response(JSON.stringify(error), {
+ status: 400,
+ headers: { 'Content-Type': 'application/json' },
+ })
+ }
+
+ const servicePath = \`/home/deno/functions/\${service_name}\`
+ console.error(\`serving the request with \${servicePath}\`)
+
+ const memoryLimitMb = 150
+ const workerTimeoutMs = 1 * 60 * 1000
+ const noModuleCache = false
+ const importMapPath = null
+ const envVarsObj = Deno.env.toObject()
+ const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
+
+ try {
+ const worker = await EdgeRuntime.userWorkers.create({
+ servicePath,
+ memoryLimitMb,
+ workerTimeoutMs,
+ noModuleCache,
+ importMapPath,
+ envVars,
+ })
+ return await worker.fetch(req)
+ } catch (e) {
+ const error = { msg: e.toString() }
+ return new Response(JSON.stringify(error), {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' },
+ })
+ }
+})
+ `,
+ },
+ {
+ filePath: "/volumes/logs/vector.yml",
+ content: `
+api:
+ enabled: true
+ address: 0.0.0.0:9001
+
+sources:
+ docker_host:
+ type: docker_logs
+ exclude_containers:
+ - supabase-vector
+
+transforms:
+ project_logs:
+ type: remap
+ inputs:
+ - docker_host
+ source: |-
+ .project = "default"
+ .event_message = del(.message)
+ .appname = del(.container_name)
+ del(.container_created_at)
+ del(.container_id)
+ del(.source_type)
+ del(.stream)
+ del(.label)
+ del(.image)
+ del(.host)
+ del(.stream)
+ router:
+ type: route
+ inputs:
+ - project_logs
+ route:
+ kong: '.appname == "supabase-kong"'
+ auth: '.appname == "supabase-auth"'
+ rest: '.appname == "supabase-rest"'
+ realtime: '.appname == "supabase-realtime"'
+ storage: '.appname == "supabase-storage"'
+ functions: '.appname == "supabase-functions"'
+ db: '.appname == "supabase-db"'
+ # Ignores non nginx errors since they are related with kong booting up
+ kong_logs:
+ type: remap
+ inputs:
+ - router.kong
+ source: |-
+ req, err = parse_nginx_log(.event_message, "combined")
+ if err == null {
+ .timestamp = req.timestamp
+ .metadata.request.headers.referer = req.referer
+ .metadata.request.headers.user_agent = req.agent
+ .metadata.request.headers.cf_connecting_ip = req.client
+ .metadata.request.method = req.method
+ .metadata.request.path = req.path
+ .metadata.request.protocol = req.protocol
+ .metadata.response.status_code = req.status
+ }
+ if err != null {
+ abort
+ }
+ # Ignores non nginx errors since they are related with kong booting up
+ kong_err:
+ type: remap
+ inputs:
+ - router.kong
+ source: |-
+ .metadata.request.method = "GET"
+ .metadata.response.status_code = 200
+ parsed, err = parse_nginx_log(.event_message, "error")
+ if err == null {
+ .timestamp = parsed.timestamp
+ .severity = parsed.severity
+ .metadata.request.host = parsed.host
+ .metadata.request.headers.cf_connecting_ip = parsed.client
+ url, err = split(parsed.request, " ")
+ if err == null {
+ .metadata.request.method = url[0]
+ .metadata.request.path = url[1]
+ .metadata.request.protocol = url[2]
+ }
+ }
+ if err != null {
+ abort
+ }
+ # Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency.
+ auth_logs:
+ type: remap
+ inputs:
+ - router.auth
+ source: |-
+ parsed, err = parse_json(.event_message)
+ if err == null {
+ .metadata.timestamp = parsed.time
+ .metadata = merge!(.metadata, parsed)
+ }
+ # PostgREST logs are structured so we separate timestamp from message using regex
+ rest_logs:
+ type: remap
+ inputs:
+ - router.rest
+ source: |-
+ parsed, err = parse_regex(.event_message, r'^(?P.*): (?P.*)$')
+ if err == null {
+ .event_message = parsed.msg
+ .timestamp = to_timestamp!(parsed.time)
+ .metadata.host = .project
+ }
+ # Realtime logs are structured so we parse the severity level using regex (ignore time because it has no date)
+ realtime_logs:
+ type: remap
+ inputs:
+ - router.realtime
+ source: |-
+ .metadata.project = del(.project)
+ .metadata.external_id = .metadata.project
+ parsed, err = parse_regex(.event_message, r'^(?P\\d+:\\d+:\\d+\\.\\d+) \\[(?P\\w+)\\] (?P.*)$')
+ if err == null {
+ .event_message = parsed.msg
+ .metadata.level = parsed.level
+ }
+ # Storage logs may contain json objects so we parse them for completeness
+ storage_logs:
+ type: remap
+ inputs:
+ - router.storage
+ source: |-
+ .metadata.project = del(.project)
+ .metadata.tenantId = .metadata.project
+ parsed, err = parse_json(.event_message)
+ if err == null {
+ .event_message = parsed.msg
+ .metadata.level = parsed.level
+ .metadata.timestamp = parsed.time
+ .metadata.context[0].host = parsed.hostname
+ .metadata.context[0].pid = parsed.pid
+ }
+ # Postgres logs some messages to stderr which we map to warning severity level
+ db_logs:
+ type: remap
+ inputs:
+ - router.db
+ source: |-
+ .metadata.host = "db-default"
+ .metadata.parsed.timestamp = .timestamp
+
+ parsed, err = parse_regex(.event_message, r'.*(?PINFO|NOTICE|WARNING|ERROR|LOG|FATAL|PANIC?):.*', numeric_groups: true)
+
+ if err != null || parsed == null {
+ .metadata.parsed.error_severity = "info"
+ }
+ if parsed != null {
+ .metadata.parsed.error_severity = parsed.level
+ }
+ if .metadata.parsed.error_severity == "info" {
+ .metadata.parsed.error_severity = "log"
+ }
+ .metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity)
+
+sinks:
+ logflare_auth:
+ type: 'http'
+ inputs:
+ - auth_logs
+ encoding:
+ codec: 'json'
+ method: 'post'
+ request:
+ retry_max_duration_secs: 10
+ uri: 'http://analytics:4000/api/logs?source_name=gotrue.logs.prod&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+ logflare_realtime:
+ type: 'http'
+ inputs:
+ - realtime_logs
+ encoding:
+ codec: 'json'
+ method: 'post'
+ request:
+ retry_max_duration_secs: 10
+ uri: 'http://analytics:4000/api/logs?source_name=realtime.logs.prod&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+ logflare_rest:
+ type: 'http'
+ inputs:
+ - rest_logs
+ encoding:
+ codec: 'json'
+ method: 'post'
+ request:
+ retry_max_duration_secs: 10
+ uri: 'http://analytics:4000/api/logs?source_name=postgREST.logs.prod&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+ logflare_db:
+ type: 'http'
+ inputs:
+ - db_logs
+ encoding:
+ codec: 'json'
+ method: 'post'
+ request:
+ retry_max_duration_secs: 10
+ # We must route the sink through kong because ingesting logs before logflare is fully initialised will
+ # lead to broken queries from studio. This works by the assumption that containers are started in the
+ # following order: vector > db > logflare > kong
+ uri: 'http://kong:8000/analytics/v1/api/logs?source_name=postgres.logs&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+ logflare_functions:
+ type: 'http'
+ inputs:
+ - router.functions
+ encoding:
+ codec: 'json'
+ method: 'post'
+ request:
+ retry_max_duration_secs: 10
+ uri: 'http://analytics:4000/api/logs?source_name=deno-relay-logs&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+ logflare_storage:
+ type: 'http'
+ inputs:
+ - storage_logs
+ encoding:
+ codec: 'json'
+ method: 'post'
+ request:
+ retry_max_duration_secs: 10
+ uri: 'http://analytics:4000/api/logs?source_name=storage.logs.prod.2&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+ logflare_kong:
+ type: 'http'
+ inputs:
+ - kong_logs
+ - kong_err
+ encoding:
+ codec: 'json'
+ method: 'post'
+ request:
+ retry_max_duration_secs: 10
+ uri: 'http://analytics:4000/api/logs?source_name=cloudflare.logs.prod&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
+ `,
+ },
+ ];
+
+ return {
+ envs,
+ mounts,
+ };
+}
diff --git a/apps/dokploy/templates/teable/docker-compose.yml b/apps/dokploy/templates/teable/docker-compose.yml
index 478a73be0..b96b677c0 100644
--- a/apps/dokploy/templates/teable/docker-compose.yml
+++ b/apps/dokploy/templates/teable/docker-compose.yml
@@ -4,8 +4,6 @@ services:
teable:
image: ghcr.io/teableio/teable:1.3.1-alpha-build.460
restart: always
- ports:
- - ${TEABLE_PORT}
volumes:
- teable-data:/app/.assets
# you may use a bind-mounted host directory instead,
@@ -24,12 +22,6 @@ services:
- BACKEND_MAIL_SENDER_NAME=${BACKEND_MAIL_SENDER_NAME}
- BACKEND_MAIL_AUTH_USER=${BACKEND_MAIL_AUTH_USER}
- BACKEND_MAIL_AUTH_PASS=${BACKEND_MAIL_AUTH_PASS}
- networks:
- - dokploy-network
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${TEABLE_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${TEABLE_PORT}"
depends_on:
teable-db-migrate:
condition: service_completed_successfully
@@ -72,10 +64,6 @@ services:
teable-db:
condition: service_healthy
-networks:
- dokploy-network:
- external: true
-
volumes:
teable-data: {}
teable-db: {}
diff --git a/apps/dokploy/templates/teable/index.ts b/apps/dokploy/templates/teable/index.ts
index a4b9b7ed0..bf1eb1ffd 100644
--- a/apps/dokploy/templates/teable/index.ts
+++ b/apps/dokploy/templates/teable/index.ts
@@ -1,25 +1,30 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const password = generatePassword();
- const randomDomain = generateRandomDomain(schema);
+ const mainDomain = generateRandomDomain(schema);
const publicDbPort = ((min: number, max: number) => {
return Math.round(Math.random() * (max - min) + min);
})(32769, 65534);
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "teable",
+ },
+ ];
+
const envs = [
- `TEABLE_HOST=${randomDomain}`,
- "TEABLE_PORT=3000",
+ `TEABLE_HOST=${mainDomain}`,
`TEABLE_DB_PORT=${publicDbPort}`,
- `HASH=${mainServiceHash}`,
"TIMEZONE=UTC",
"# Postgres",
"POSTGRES_HOST=teable-db",
@@ -44,5 +49,6 @@ export function generate(schema: Schema): Template {
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts
index 265d0f0de..06653ab2c 100644
--- a/apps/dokploy/templates/templates.ts
+++ b/apps/dokploy/templates/templates.ts
@@ -1,6 +1,21 @@
import type { TemplateData } from "./types/templates-data.type";
export const templates: TemplateData[] = [
+ {
+ id: "supabase",
+ name: "SupaBase",
+ version: "1.24.07",
+ description:
+ "The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. ",
+ links: {
+ github: "https://github.com/supabase/supabase",
+ website: "https://supabase.com/",
+ docs: "https://supabase.com/docs/guides/self-hosting",
+ },
+ logo: "supabase.svg",
+ load: () => import("./supabase/index").then((m) => m.generate),
+ tags: ["database", "firebase", "postgres"],
+ },
{
id: "pocketbase",
name: "Pocketbase",
@@ -421,5 +436,35 @@ export const templates: TemplateData[] = [
},
tags: ["media system", "storage"],
load: () => import("./zipline/index").then((m) => m.generate),
- }
+ },
+{
+ id: "soketi",
+ name: "Soketi",
+ version: "v1.4-16",
+ description:
+ "Soketi is your simple, fast, and resilient open-source WebSockets server.",
+ logo: "soketi.png",
+ links: {
+ github: "https://github.com/soketi/soketi",
+ website: "https://soketi.app/",
+ docs: "https://docs.soketi.app/",
+ },
+ tags: ["chat"],
+ load: () => import("./soketi/index").then((m) => m.generate),
+ },
+ {
+ id: "aptabase",
+ name: "Aptabase",
+ version: "v1.0.0",
+ description:
+ "Aptabase is a self-hosted web analytics platform that lets you track website traffic and user behavior.",
+ logo: "aptabase.svg",
+ links: {
+ github: "https://github.com/aptabase/aptabase",
+ website: "https://aptabase.com/",
+ docs: "https://github.com/aptabase/aptabase/blob/main/README.md",
+ },
+ tags: ["analytics", "self-hosted"],
+ load: () => import("./aptabase/index").then((m) => m.generate),
+ },
];
diff --git a/apps/dokploy/templates/umami/docker-compose.yml b/apps/dokploy/templates/umami/docker-compose.yml
index 43d843af1..a149a5e64 100644
--- a/apps/dokploy/templates/umami/docker-compose.yml
+++ b/apps/dokploy/templates/umami/docker-compose.yml
@@ -10,18 +10,10 @@ services:
depends_on:
db:
condition: service_healthy
- ports:
- - ${UMAMI_PORT}
- networks:
- - dokploy-network
environment:
DATABASE_URL: postgresql://umami:umami@db:5432/umami
DATABASE_TYPE: postgresql
APP_SECRET: ${APP_SECRET}
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${UMAMI_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${UMAMI_PORT}"
db:
image: postgres:15-alpine
restart: always
@@ -39,8 +31,5 @@ services:
POSTGRES_USER: umami
POSTGRES_PASSWORD: umami
-networks:
- dokploy-network:
- external: true
volumes:
db-data:
diff --git a/apps/dokploy/templates/umami/index.ts b/apps/dokploy/templates/umami/index.ts
index 364fbbe9a..d7366e9b8 100644
--- a/apps/dokploy/templates/umami/index.ts
+++ b/apps/dokploy/templates/umami/index.ts
@@ -1,24 +1,27 @@
import {
+ type DomainSchema,
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const randomSecret = generateBase64();
- const envs = [
- `UMAMI_HOST=${randomDomain}`,
- "UMAMI_PORT=3000",
- `APP_SECRET=${randomSecret}`,
- `HASH=${mainServiceHash}`,
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 3000,
+ serviceName: "umami",
+ },
];
+ const envs = [`APP_SECRET=${randomSecret}`];
+
return {
envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/uptime-kuma/docker-compose.yml b/apps/dokploy/templates/uptime-kuma/docker-compose.yml
index 2e2109a85..ccd775526 100644
--- a/apps/dokploy/templates/uptime-kuma/docker-compose.yml
+++ b/apps/dokploy/templates/uptime-kuma/docker-compose.yml
@@ -1,21 +1,10 @@
version: "3.8"
services:
uptime-kuma:
- networks:
- - dokploy-network
image: louislam/uptime-kuma:1
restart: always
- ports:
- - ${UPTIME_KUMA_PORT}
volumes:
- uptime-kuma-data:/app/data
- labels:
- - traefik.enable=true
- - traefik.http.routers.${HASH}.rule=Host(`${UPTIME_KUMA_HOST}`)
- - traefik.http.services.${HASH}.loadbalancer.server.port=${UPTIME_KUMA_PORT}
volumes:
uptime-kuma-data:
-networks:
- dokploy-network:
- external: true
\ No newline at end of file
diff --git a/apps/dokploy/templates/uptime-kuma/index.ts b/apps/dokploy/templates/uptime-kuma/index.ts
index 31bdf27b2..d9f3e5dbe 100644
--- a/apps/dokploy/templates/uptime-kuma/index.ts
+++ b/apps/dokploy/templates/uptime-kuma/index.ts
@@ -1,20 +1,22 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
- const envs = [
- `UPTIME_KUMA_HOST=${randomDomain}`,
- "UPTIME_KUMA_PORT=3001",
- `HASH=${mainServiceHash}`,
+
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 3001,
+ serviceName: "uptime-kuma",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/dokploy/templates/utils/index.ts b/apps/dokploy/templates/utils/index.ts
index 4d194ba94..9040e83a0 100644
--- a/apps/dokploy/templates/utils/index.ts
+++ b/apps/dokploy/templates/utils/index.ts
@@ -1,6 +1,7 @@
import { randomBytes } from "node:crypto";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
+import type { Domain } from "@/server/api/services/domain";
import { TRPCError } from "@trpc/server";
import { templates } from "../templates";
import type { TemplatesKeys } from "../types/templates-data.type";
@@ -10,12 +11,15 @@ export interface Schema {
projectName: string;
}
+export type DomainSchema = Pick;
+
export interface Template {
- envs: string[];
+ envs?: string[];
mounts?: {
filePath: string;
content?: string;
}[];
+ domains?: DomainSchema[];
}
export const generateRandomDomain = ({
@@ -57,7 +61,7 @@ export const loadTemplateModule = async (
return generate;
};
-export const readComposeFile = async (id: string) => {
+export const readTemplateComposeFile = async (id: string) => {
const cwd = process.cwd();
const composeFile = await readFile(
join(cwd, ".next", "templates", id, "docker-compose.yml"),
diff --git a/apps/dokploy/templates/wordpress/docker-compose.yml b/apps/dokploy/templates/wordpress/docker-compose.yml
index e1e99e6be..6ff6b2fd4 100644
--- a/apps/dokploy/templates/wordpress/docker-compose.yml
+++ b/apps/dokploy/templates/wordpress/docker-compose.yml
@@ -1,20 +1,12 @@
-version: '3.8'
+version: "3.8"
services:
wordpress:
image: wordpress:5.8.3
- networks:
- - dokploy-network
- ports:
- - ${WORDPRESS_PORT}
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
- labels:
- - "traefik.enable=true"
- - "traefik.http.routers.${HASH}.rule=Host(`${WORDPRESS_HOST}`)"
- - "traefik.http.services.${HASH}.loadbalancer.server.port=${WORDPRESS_PORT}"
volumes:
- wordpress_data:/var/www/html
@@ -33,7 +25,3 @@ services:
volumes:
wordpress_data:
db_data:
-
-networks:
- dokploy-network:
- external: true
diff --git a/apps/dokploy/templates/wordpress/index.ts b/apps/dokploy/templates/wordpress/index.ts
index 38d7d0e7d..5126f1571 100644
--- a/apps/dokploy/templates/wordpress/index.ts
+++ b/apps/dokploy/templates/wordpress/index.ts
@@ -1,20 +1,22 @@
import {
+ type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
- const envs = [
- `WORDPRESS_HOST=${randomDomain}`,
- "WORDPRESS_PORT=80",
- `HASH=${mainServiceHash}`,
+
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 80,
+ serviceName: "wordpress",
+ },
];
return {
- envs,
+ domains,
};
}
diff --git a/apps/website/public/canary.sh b/apps/website/public/canary.sh
index ba8327363..3a9102b00 100644
--- a/apps/website/public/canary.sh
+++ b/apps/website/public/canary.sh
@@ -81,6 +81,7 @@ docker service create \
--network dokploy-network \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
+ --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
--publish published=3000,target=3000,mode=host \
--update-parallelism 1 \
--update-order stop-first \
diff --git a/apps/website/public/feature.sh b/apps/website/public/feature.sh
index 50955cc24..453da012f 100644
--- a/apps/website/public/feature.sh
+++ b/apps/website/public/feature.sh
@@ -65,6 +65,7 @@ docker service create \
--network dokploy-network \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
+ --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
--publish published=3000,target=3000,mode=host \
--update-parallelism 1 \
--update-order stop-first \
diff --git a/apps/website/public/install.sh b/apps/website/public/install.sh
index 8dc80620f..e68a59fd2 100644
--- a/apps/website/public/install.sh
+++ b/apps/website/public/install.sh
@@ -81,6 +81,7 @@ docker service create \
--network dokploy-network \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
+ --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
--publish published=3000,target=3000,mode=host \
--update-parallelism 1 \
--update-order stop-first \
diff --git a/package.json b/package.json
index 0c5731d98..5ca5f59af 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"format-and-lint": "biome check .",
"check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
"format-and-lint:fix": "biome check . --write",
- "prepare": "node .husky/install.mjs"
+ "prepare": "node ./.config/.husky/install.mjs"
},
"devDependencies": {
"dotenv": "16.4.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 09c5d103b..452a10caf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -114,6 +114,9 @@ importers:
'@codemirror/legacy-modes':
specifier: 6.4.0
version: 6.4.0
+ '@codemirror/view':
+ specifier: 6.29.0
+ version: 6.29.0
'@dokploy/trpc-openapi':
specifier: 0.0.4
version: 0.0.4(@trpc/server@10.45.2)(zod@3.23.8)