diff --git a/Dockerfile b/Dockerfile
index 4cab0e8f36..6886e32178 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,6 +19,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/dokploy run build
+RUN test -f /usr/src/app/apps/dokploy/dist/caddy-migration-rollback.mjs
RUN pnpm --filter=./apps/dokploy --prod deploy --legacy /prod/dokploy
@@ -43,7 +44,8 @@ COPY --from=build /prod/dokploy/drizzle ./drizzle
COPY .env.production ./.env
COPY --from=build /prod/dokploy/components.json ./components.json
COPY --from=build /prod/dokploy/node_modules ./node_modules
-
+RUN test -f /app/dist/caddy-migration-rollback.mjs \
+ && node -r dotenv/config /app/dist/caddy-migration-rollback.mjs --help | grep -q "Usage: caddy-migration-rollback"
# Install docker
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh --version 28.5.2 && rm get-docker.sh && curl https://rclone.org/install.sh | bash
diff --git a/apps/dokploy/__test__/caddy/application-web-server-config-cache.test.ts b/apps/dokploy/__test__/caddy/application-web-server-config-cache.test.ts
new file mode 100644
index 0000000000..36f1b926c0
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/application-web-server-config-cache.test.ts
@@ -0,0 +1,96 @@
+import { readFileSync } from "node:fs";
+import { expect, test, vi } from "vitest";
+import { invalidateApplicationWebServerConfig } from "@/components/dashboard/application/web-server-config-cache";
+
+const createUtils = () => ({
+ application: {
+ readTraefikConfig: {
+ invalidate: vi.fn(),
+ },
+ readWebServerConfig: {
+ invalidate: vi.fn(),
+ },
+ },
+});
+
+test("invalidates legacy Traefik and provider-aware application web-server config caches", async () => {
+ const utils = createUtils();
+
+ await invalidateApplicationWebServerConfig(utils, "app-1");
+
+ expect(utils.application.readTraefikConfig.invalidate).toHaveBeenCalledWith({
+ applicationId: "app-1",
+ });
+ expect(utils.application.readWebServerConfig.invalidate).toHaveBeenCalledWith(
+ { applicationId: "app-1" },
+ );
+});
+
+test("awaits both application web-server config cache invalidations", async () => {
+ const calls: string[] = [];
+ const utils = createUtils();
+ utils.application.readTraefikConfig.invalidate.mockImplementation(
+ async () => {
+ await Promise.resolve();
+ calls.push("traefik");
+ },
+ );
+ utils.application.readWebServerConfig.invalidate.mockImplementation(
+ async () => {
+ await Promise.resolve();
+ calls.push("web-server");
+ },
+ );
+
+ await invalidateApplicationWebServerConfig(utils, "app-1");
+
+ expect(calls).toEqual(["traefik", "web-server"]);
+});
+
+test("propagates application web-server config cache invalidation failures", async () => {
+ const utils = createUtils();
+ utils.application.readWebServerConfig.invalidate.mockRejectedValueOnce(
+ new Error("provider-aware cache failed"),
+ );
+
+ await expect(
+ invalidateApplicationWebServerConfig(utils, "app-1"),
+ ).rejects.toThrow("provider-aware cache failed");
+ expect(utils.application.readTraefikConfig.invalidate).toHaveBeenCalledWith({
+ applicationId: "app-1",
+ });
+});
+
+const applicationMutationHandlers = [
+ [
+ "domain handler",
+ "../../components/dashboard/application/domains/handle-domain.tsx",
+ ],
+ [
+ "redirect form handler",
+ "../../components/dashboard/application/advanced/redirects/handle-redirect.tsx",
+ ],
+ [
+ "redirect delete handler",
+ "../../components/dashboard/application/advanced/redirects/show-redirects.tsx",
+ ],
+ [
+ "security form handler",
+ "../../components/dashboard/application/advanced/security/handle-security.tsx",
+ ],
+ [
+ "security delete handler",
+ "../../components/dashboard/application/advanced/security/show-security.tsx",
+ ],
+] as const;
+
+test.each(applicationMutationHandlers)(
+ "%s uses the shared application web-server cache invalidation helper",
+ (_name, filePath) => {
+ const source = readFileSync(new URL(filePath, import.meta.url), "utf8");
+
+ expect(source).toContain("invalidateApplicationWebServerConfig");
+ expect(source).not.toMatch(/readTraefikConfig\.invalidate/);
+ expect(source).not.toMatch(/readWebServerConfig\.invalidate/);
+ },
+);
diff --git a/apps/dokploy/__test__/caddy/application/domain-service.test.ts b/apps/dokploy/__test__/caddy/application/domain-service.test.ts
new file mode 100644
index 0000000000..9e09a71ed8
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/application/domain-service.test.ts
@@ -0,0 +1,241 @@
+import { beforeEach, expect, test, vi } from "vitest";
+
+const txInsertMock = vi.hoisted(() => vi.fn());
+const transactionMock = vi.hoisted(() => vi.fn());
+const dbDeleteMock = vi.hoisted(() => vi.fn());
+const domainsFindFirstMock = vi.hoisted(() => vi.fn());
+const domainsFindManyMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server/db", () => ({
+ db: {
+ transaction: transactionMock,
+ query: {
+ domains: {
+ findFirst: domainsFindFirstMock,
+ findMany: domainsFindManyMock,
+ },
+ },
+ delete: dbDeleteMock,
+ },
+}));
+
+vi.mock("@dokploy/server/services/application", () => ({
+ findApplicationById: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/utils/web-server/domain", () => ({
+ manageWebServerDomain: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/utils/docker/domain", () => ({
+ getCaddyComposeRouteTargetsForWebServer: vi.fn(),
+ writeCaddyComposeRoutesForTargets: vi.fn(),
+}));
+
+import { findApplicationById } from "@dokploy/server/services/application";
+import {
+ createComposeDomain,
+ createDomain,
+ removeComposeDomainsForWebServer,
+} from "@dokploy/server/services/domain";
+import {
+ getCaddyComposeRouteTargetsForWebServer,
+ writeCaddyComposeRoutesForTargets,
+} from "@dokploy/server/utils/docker/domain";
+import { manageWebServerDomain } from "@dokploy/server/utils/web-server/domain";
+
+const domain = {
+ domainId: "domain-1",
+ applicationId: "app-1",
+ composeId: null,
+ previewDeploymentId: null,
+ host: "example.com",
+ uniqueConfigKey: 7,
+};
+
+const composeDomain = {
+ ...domain,
+ applicationId: null,
+ composeId: "compose-1",
+ domainType: "compose",
+};
+
+const retainedComposeDomain = {
+ ...composeDomain,
+ domainId: "domain-2",
+ host: "retained.example.com",
+ uniqueConfigKey: 8,
+};
+
+const application = {
+ applicationId: "app-1",
+ appName: "my-app",
+ serverId: null,
+};
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ txInsertMock.mockReturnValue({
+ values: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([domain]),
+ }),
+ });
+ transactionMock.mockImplementation(async (callback) => {
+ const tx = { delete: dbDeleteMock, insert: txInsertMock };
+ return callback(tx);
+ });
+ domainsFindFirstMock.mockResolvedValue(domain);
+ domainsFindManyMock.mockResolvedValue([composeDomain]);
+ dbDeleteMock.mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([composeDomain]),
+ }),
+ });
+ vi.mocked(getCaddyComposeRouteTargetsForWebServer).mockResolvedValue([
+ {},
+ ] as never);
+ vi.mocked(writeCaddyComposeRoutesForTargets).mockResolvedValue(
+ undefined as never,
+ );
+ vi.mocked(findApplicationById).mockResolvedValue(application as never);
+});
+
+test("creates copied new-service application domains through the active web server provider", async () => {
+ vi.mocked(manageWebServerDomain).mockResolvedValue(undefined as never);
+
+ const created = await createDomain({
+ host: " example.com ",
+ applicationId: "app-1",
+ domainType: "application",
+ } as never);
+
+ expect(created).toBe(domain);
+ expect(txInsertMock).toHaveBeenCalled();
+ expect(manageWebServerDomain).toHaveBeenCalledWith(application, domain);
+});
+
+test("removes application domain rows when provider route creation fails", async () => {
+ vi.mocked(manageWebServerDomain).mockRejectedValueOnce(
+ new Error("caddy reload failed") as never,
+ );
+
+ await expect(
+ createDomain({
+ host: "example.com",
+ applicationId: "app-1",
+ domainType: "application",
+ } as never),
+ ).rejects.toThrow("caddy reload failed");
+
+ expect(manageWebServerDomain).toHaveBeenCalledWith(application, domain);
+ expect(dbDeleteMock).toHaveBeenCalled();
+});
+
+test("removes compose domain rows when Caddy compose route refresh fails after creation", async () => {
+ txInsertMock.mockReturnValueOnce({
+ values: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([composeDomain]),
+ }),
+ });
+ vi.mocked(writeCaddyComposeRoutesForTargets)
+ .mockRejectedValueOnce(new Error("caddy refresh failed") as never)
+ .mockResolvedValueOnce(undefined as never);
+
+ await expect(
+ createComposeDomain(
+ {
+ composeId: "compose-1",
+ appName: "my-compose",
+ serverId: null,
+ } as never,
+ {
+ host: "example.com",
+ composeId: "compose-1",
+ domainType: "compose",
+ } as never,
+ "caddy",
+ ),
+ ).rejects.toThrow("caddy refresh failed");
+
+ expect(dbDeleteMock).toHaveBeenCalled();
+ expect(writeCaddyComposeRoutesForTargets).toHaveBeenCalledTimes(2);
+});
+
+test("refreshes Caddy compose routes with zero remaining domains before deleting imported template domains", async () => {
+ domainsFindManyMock.mockResolvedValueOnce([composeDomain]);
+
+ const removed = await removeComposeDomainsForWebServer(
+ {
+ composeId: "compose-1",
+ appName: "my-compose",
+ serverId: null,
+ } as never,
+ [composeDomain] as never,
+ "caddy",
+ );
+
+ expect(removed).toEqual([composeDomain]);
+ expect(getCaddyComposeRouteTargetsForWebServer).toHaveBeenCalledWith(
+ expect.objectContaining({ composeId: "compose-1" }),
+ [],
+ "caddy",
+ );
+ expect(dbDeleteMock).toHaveBeenCalled();
+ expect(writeCaddyComposeRoutesForTargets).toHaveBeenCalledTimes(1);
+});
+
+test("restores Caddy compose routes if imported template domain deletion fails", async () => {
+ domainsFindManyMock.mockResolvedValueOnce([
+ composeDomain,
+ retainedComposeDomain,
+ ]);
+ dbDeleteMock.mockReturnValueOnce({
+ where: vi.fn().mockReturnValue({
+ returning: vi.fn().mockRejectedValue(new Error("db delete failed")),
+ }),
+ });
+
+ await expect(
+ removeComposeDomainsForWebServer(
+ {
+ composeId: "compose-1",
+ appName: "my-compose",
+ serverId: null,
+ } as never,
+ [composeDomain] as never,
+ "caddy",
+ ),
+ ).rejects.toThrow("db delete failed");
+
+ expect(getCaddyComposeRouteTargetsForWebServer).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining({ composeId: "compose-1" }),
+ [retainedComposeDomain],
+ "caddy",
+ );
+ expect(getCaddyComposeRouteTargetsForWebServer).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining({ composeId: "compose-1" }),
+ [composeDomain, retainedComposeDomain],
+ "caddy",
+ );
+ expect(writeCaddyComposeRoutesForTargets).toHaveBeenCalledTimes(2);
+});
+
+test("deletes imported template compose domains without Caddy refresh under Traefik", async () => {
+ domainsFindManyMock.mockResolvedValueOnce([composeDomain]);
+
+ await removeComposeDomainsForWebServer(
+ {
+ composeId: "compose-1",
+ appName: "my-compose",
+ serverId: null,
+ } as never,
+ [composeDomain] as never,
+ "traefik",
+ );
+
+ expect(getCaddyComposeRouteTargetsForWebServer).not.toHaveBeenCalled();
+ expect(writeCaddyComposeRoutesForTargets).not.toHaveBeenCalled();
+ expect(dbDeleteMock).toHaveBeenCalled();
+});
diff --git a/apps/dokploy/__test__/caddy/application/domain.test.ts b/apps/dokploy/__test__/caddy/application/domain.test.ts
new file mode 100644
index 0000000000..9b9569e743
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/application/domain.test.ts
@@ -0,0 +1,165 @@
+import type { ApplicationNested, Domain } from "@dokploy/server";
+import {
+ compileCaddyConfig,
+ createCaddyApplicationRouteFragment,
+ getCaddyApplicationFragmentId,
+ paths,
+} from "@dokploy/server";
+import { expect, test } from "vitest";
+
+const app = (overrides: Partial = {}) =>
+ ({
+ appName: "my-app",
+ serverId: null,
+ ...overrides,
+ }) as ApplicationNested;
+
+const domain = (overrides: Partial = {}) =>
+ ({
+ domainId: "domain-1",
+ applicationId: "app-1",
+ composeId: null,
+ previewDeploymentId: null,
+ domainType: "application",
+ host: "example.com",
+ path: null,
+ internalPath: "/",
+ stripPath: false,
+ https: false,
+ certificateType: "none",
+ customCertResolver: null,
+ customEntrypoint: null,
+ middlewares: null,
+ port: null,
+ serviceName: null,
+ uniqueConfigKey: 7,
+ createdAt: "",
+ ...overrides,
+ }) as Domain;
+
+const getServers = (config: ReturnType) => {
+ const apps = config.apps as Record;
+ return apps.http.servers as Record;
+};
+
+test("creates deterministic Caddy application fragments", () => {
+ const fragment = createCaddyApplicationRouteFragment(app(), domain());
+
+ expect(fragment.id).toBe(getCaddyApplicationFragmentId("my-app", 7));
+ expect(fragment.source).toBe("dokploy-application");
+ expect(fragment.routes[0]).toMatchObject({
+ id: "my-app-route-7",
+ hosts: ["example.com"],
+ https: false,
+ upstreams: ["http://my-app:80"],
+ });
+});
+
+test("renders path rewrites and custom upstream port", () => {
+ const fragment = createCaddyApplicationRouteFragment(
+ app(),
+ domain({
+ path: "/public",
+ stripPath: true,
+ internalPath: "/internal",
+ port: 3000,
+ }),
+ );
+ const config = compileCaddyConfig({ fragments: [fragment] });
+ const handlers = getServers(config).http.routes[0].handle;
+
+ expect(getServers(config).http.routes[0].match[0]).toMatchObject({
+ host: ["example.com"],
+ path: ["/public*"],
+ });
+ expect(handlers[0]).toMatchObject({
+ handler: "rewrite",
+ strip_path_prefix: "/public",
+ });
+ expect(handlers[1]).toMatchObject({
+ handler: "rewrite",
+ uri: "/internal{http.request.uri.path}",
+ });
+ expect(handlers[2]).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "my-app:3000" }],
+ });
+});
+
+test("HTTPS application routes redirect HTTP and proxy on HTTPS", () => {
+ const fragment = createCaddyApplicationRouteFragment(
+ app(),
+ domain({ https: true, certificateType: "letsencrypt" }),
+ );
+ const config = compileCaddyConfig({ fragments: [fragment] });
+ const servers = getServers(config);
+
+ expect(servers.http.routes[0].handle[0]).toMatchObject({
+ handler: "static_response",
+ status_code: 308,
+ });
+ expect(servers.https.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "my-app:80" }],
+ });
+});
+
+test("loads uploaded certificates for custom Caddy HTTPS routes", () => {
+ const fragment = createCaddyApplicationRouteFragment(
+ app(),
+ domain({
+ https: true,
+ certificateType: "custom",
+ customCertResolver: "certificate-uploaded",
+ }),
+ );
+ const config = compileCaddyConfig({ fragments: [fragment] });
+ const tls = (config.apps as any).tls;
+ const certificatePath = `${paths(false).CERTIFICATES_PATH}/certificate-uploaded`;
+
+ expect(tls.certificates.load_files).toEqual([
+ {
+ certificate: `${certificatePath}/chain.crt`,
+ key: `${certificatePath}/privkey.key`,
+ },
+ ]);
+ expect(getServers(config).https.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "my-app:80" }],
+ });
+});
+
+test("punycodes internationalized hosts for Caddy routes", () => {
+ const fragment = createCaddyApplicationRouteFragment(
+ app(),
+ domain({ host: "тест.рф" }),
+ );
+
+ expect(fragment.routes[0]?.hosts).toEqual(["xn--e1aybc.xn--p1ai"]);
+});
+
+test("rejects Traefik-only domain features for Caddy routes", () => {
+ expect(() =>
+ createCaddyApplicationRouteFragment(
+ app(),
+ domain({
+ customEntrypoint: "admin",
+ customCertResolver: "internal",
+ middlewares: ["auth@file"],
+ }),
+ ),
+ ).toThrow("unsupported Caddy fields");
+});
+
+test("rejects Caddy custom certificate routes without an uploaded certificate", () => {
+ expect(() =>
+ createCaddyApplicationRouteFragment(
+ app(),
+ domain({
+ https: true,
+ certificateType: "custom",
+ customCertResolver: null,
+ }),
+ ),
+ ).toThrow("custom certificates require an uploaded certificate");
+});
diff --git a/apps/dokploy/__test__/caddy/certificate-guard.test.ts b/apps/dokploy/__test__/caddy/certificate-guard.test.ts
new file mode 100644
index 0000000000..454a3cd97e
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/certificate-guard.test.ts
@@ -0,0 +1,146 @@
+import { fs, vol } from "memfs";
+import { beforeEach, expect, test, vi } from "vitest";
+
+const certificatesFindFirstMock = vi.hoisted(() => vi.fn());
+
+vi.mock("node:fs", () => ({
+ ...fs,
+ default: fs,
+}));
+
+vi.mock("@dokploy/server/db", () => ({
+ db: {
+ query: {
+ certificates: {
+ findFirst: certificatesFindFirstMock,
+ },
+ },
+ },
+}));
+
+import type { Domain } from "@dokploy/server";
+import { paths } from "@dokploy/server/constants";
+import { assertCaddyDomainCertificateAvailable } from "@dokploy/server/utils/caddy/domain";
+
+const writeCertificateFiles = (certificatePath: string) => {
+ const certDir = `${paths().CERTIFICATES_PATH}/${certificatePath}`;
+ vol.mkdirSync(certDir, { recursive: true });
+ vol.writeFileSync(`${certDir}/chain.crt`, "cert");
+ vol.writeFileSync(`${certDir}/privkey.key`, "key");
+};
+
+const domain = (overrides: Partial = {}) =>
+ ({
+ domainId: "domain-1",
+ applicationId: "app-1",
+ composeId: null,
+ previewDeploymentId: null,
+ domainType: "application",
+ host: "example.com",
+ path: "/",
+ internalPath: "/",
+ stripPath: false,
+ https: true,
+ certificateType: "custom",
+ customCertResolver: "certificate-uploaded",
+ customEntrypoint: null,
+ middlewares: null,
+ port: 3000,
+ serviceName: null,
+ uniqueConfigKey: 7,
+ createdAt: "",
+ ...overrides,
+ }) as Domain;
+
+beforeEach(() => {
+ vol.reset();
+ vi.clearAllMocks();
+});
+
+test("allows uploaded Caddy certificates assigned to the same server and organization", async () => {
+ writeCertificateFiles("certificate-uploaded");
+ certificatesFindFirstMock.mockResolvedValue({
+ certificatePath: "certificate-uploaded",
+ serverId: null,
+ organizationId: "org-1",
+ });
+
+ await expect(
+ assertCaddyDomainCertificateAvailable(null, domain(), "org-1"),
+ ).resolves.toBeUndefined();
+});
+
+test("requires organization context for uploaded Caddy certificate validation", async () => {
+ writeCertificateFiles("certificate-uploaded");
+ certificatesFindFirstMock.mockResolvedValue({
+ certificatePath: "certificate-uploaded",
+ serverId: null,
+ organizationId: "org-1",
+ });
+
+ await expect(
+ assertCaddyDomainCertificateAvailable(null, domain(), null),
+ ).rejects.toThrow(
+ "Caddy custom certificate validation requires organization context",
+ );
+ expect(certificatesFindFirstMock).not.toHaveBeenCalled();
+});
+
+test("rejects missing, cross-server, or cross-organization Caddy certificate paths", async () => {
+ certificatesFindFirstMock.mockResolvedValueOnce(null);
+ await expect(
+ assertCaddyDomainCertificateAvailable(null, domain(), "org-1"),
+ ).rejects.toThrow("is not available for this server and organization");
+
+ certificatesFindFirstMock.mockResolvedValueOnce({
+ certificatePath: "certificate-uploaded",
+ serverId: "server-2",
+ organizationId: "org-1",
+ });
+ await expect(
+ assertCaddyDomainCertificateAvailable("server-1", domain(), "org-1"),
+ ).rejects.toThrow("is not available for this server and organization");
+
+ certificatesFindFirstMock.mockResolvedValueOnce({
+ certificatePath: "certificate-uploaded",
+ serverId: null,
+ organizationId: "org-2",
+ });
+ await expect(
+ assertCaddyDomainCertificateAvailable(null, domain(), "org-1"),
+ ).rejects.toThrow("is not available for this server and organization");
+});
+
+test("rejects uploaded Caddy certificate rows when files are missing", async () => {
+ certificatesFindFirstMock.mockResolvedValue({
+ certificatePath: "certificate-uploaded",
+ serverId: null,
+ organizationId: "org-1",
+ });
+
+ await expect(
+ assertCaddyDomainCertificateAvailable(null, domain(), "org-1"),
+ ).rejects.toThrow("missing readable chain.crt or privkey.key");
+});
+
+test("ignores stale custom certificate fields when HTTPS is disabled", async () => {
+ await expect(
+ assertCaddyDomainCertificateAvailable(
+ null,
+ domain({ https: false }),
+ "org-1",
+ ),
+ ).resolves.toBeUndefined();
+ expect(certificatesFindFirstMock).not.toHaveBeenCalled();
+});
+
+test("does not require organization context for non-custom Caddy certificates", async () => {
+ await expect(
+ assertCaddyDomainCertificateAvailable(
+ null,
+ domain({ certificateType: "letsencrypt", customCertResolver: null }),
+ null,
+ ),
+ ).resolves.toBeUndefined();
+ expect(certificatesFindFirstMock).not.toHaveBeenCalled();
+});
diff --git a/apps/dokploy/__test__/caddy/certificate-lifecycle.test.ts b/apps/dokploy/__test__/caddy/certificate-lifecycle.test.ts
new file mode 100644
index 0000000000..b49ddf616c
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/certificate-lifecycle.test.ts
@@ -0,0 +1,93 @@
+import { beforeEach, expect, test, vi } from "vitest";
+
+const dbMock = vi.hoisted(() => ({
+ query: {
+ certificates: { findFirst: vi.fn() },
+ domains: { findFirst: vi.fn() },
+ },
+ delete: vi.fn(),
+ update: vi.fn(),
+}));
+
+const resolveWebServerProviderMock = vi.hoisted(() => vi.fn());
+const removeDirectoryIfExistsContentMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server/db", () => ({
+ db: dbMock,
+}));
+
+vi.mock("@dokploy/server/services/web-server-settings", () => ({
+ getCaddyCompileSettings: vi.fn().mockResolvedValue({}),
+ resolveWebServerProvider: resolveWebServerProviderMock,
+}));
+
+vi.mock("@dokploy/server/utils/filesystem/directory", () => ({
+ removeDirectoryIfExistsContent: removeDirectoryIfExistsContentMock,
+}));
+
+vi.mock("@dokploy/server/utils/process/execAsync", () => ({
+ execAsyncRemote: vi.fn(),
+}));
+
+import {
+ removeCertificateById,
+ updateCertificate,
+} from "@dokploy/server/services/certificate";
+
+const certificate = {
+ certificateId: "cert-1",
+ name: "Shared cert",
+ certificatePath: "certificate-uploaded",
+ certificateData: "cert",
+ privateKey: "key",
+ autoRenew: null,
+ organizationId: "org-1",
+ serverId: null,
+};
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ dbMock.query.certificates.findFirst.mockResolvedValue(certificate);
+ dbMock.query.domains.findFirst.mockResolvedValue(null);
+ dbMock.delete.mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([certificate]),
+ }),
+ });
+ dbMock.update.mockReturnValue({
+ set: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([certificate]),
+ }),
+ }),
+ });
+ resolveWebServerProviderMock.mockResolvedValue("caddy");
+ removeDirectoryIfExistsContentMock.mockResolvedValue(undefined);
+});
+
+test("blocks deleting uploaded certificates used by active Caddy domains", async () => {
+ dbMock.query.domains.findFirst.mockResolvedValueOnce({
+ host: "example.com",
+ });
+
+ await expect(removeCertificateById("cert-1")).rejects.toThrow(
+ 'Cannot delete certificate "Shared cert" because active Caddy domain "example.com" uses it',
+ );
+
+ expect(removeDirectoryIfExistsContentMock).not.toHaveBeenCalled();
+ expect(dbMock.delete).not.toHaveBeenCalled();
+});
+
+test("blocks replacing uploaded certificate files used by active Caddy domains", async () => {
+ dbMock.query.domains.findFirst.mockResolvedValueOnce({
+ host: "example.com",
+ });
+
+ await expect(
+ updateCertificate("cert-1", { certificateData: "new cert" }),
+ ).rejects.toThrow(
+ 'Cannot update certificate "Shared cert" because active Caddy domain "example.com" uses it',
+ );
+
+ expect(dbMock.update).not.toHaveBeenCalled();
+});
diff --git a/apps/dokploy/__test__/caddy/compose-domain-call-sites.test.ts b/apps/dokploy/__test__/caddy/compose-domain-call-sites.test.ts
new file mode 100644
index 0000000000..b6f4f98647
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/compose-domain-call-sites.test.ts
@@ -0,0 +1,68 @@
+import { readFileSync } from "node:fs";
+import { describe, expect, test } from "vitest";
+
+const readSource = (relativePath: string) =>
+ readFileSync(new URL(relativePath, import.meta.url), "utf8");
+
+const expectNoRawComposeCreateDomain = (source: string) => {
+ expect(source).not.toMatch(
+ /createDomain\(\s*\{[\s\S]{0,500}domainType:\s*"compose"/,
+ );
+};
+
+describe("compose domain creation call-site contract", () => {
+ test("routes template compose domains through shared provider-aware helpers", () => {
+ const source = readSource("../../server/api/routers/compose.ts");
+
+ expect(source).toContain("createComposeDomain(");
+ expect(source).toContain("removeComposeDomainsForWebServer(");
+ expect(source).toContain("ctx.session.activeOrganizationId");
+ expectNoRawComposeCreateDomain(source);
+ });
+
+ test("routes AI-generated compose domains through shared provider-aware helpers", () => {
+ const source = readSource("../../server/api/routers/ai.ts");
+
+ expect(source).toContain("createComposeDomain(");
+ expect(source).toContain("ctx.session.activeOrganizationId");
+ expectNoRawComposeCreateDomain(source);
+ });
+
+ test("routes project duplication domains through application and compose helpers", () => {
+ const source = readSource("../../server/api/routers/project.ts");
+
+ expect(source).toContain("await createDomain({");
+ expect(source).toContain("applicationId: newApplication.applicationId");
+ expect(source).toContain("createComposeDomain(");
+ expect(source).toContain("ctx.session.activeOrganizationId");
+ expectNoRawComposeCreateDomain(source);
+ });
+
+ test("routes domain-router compose domains through shared provider-aware helpers", () => {
+ const source = readSource("../../server/api/routers/domain.ts");
+
+ expect(source).toContain("createComposeDomain(");
+ expect(source).toContain("refreshCaddyComposeRoutes(");
+ expect(source).toContain("ctx.session.activeOrganizationId");
+ expectNoRawComposeCreateDomain(source);
+ });
+
+ test("keeps compose deploy route refreshes tied to eagerly loaded project org context", () => {
+ const source = readSource(
+ "../../../../packages/server/src/services/compose.ts",
+ );
+
+ expect(source).toMatch(
+ /export const findComposeById[\s\S]*environment:\s*{\s*with:\s*{\s*project:\s*true/,
+ );
+
+ const routeRefreshCalls =
+ source.match(/writeCaddyComposeRoutesForTargets\([\s\S]*?\);/g) ?? [];
+ expect(routeRefreshCalls).toHaveLength(2);
+ for (const call of routeRefreshCalls) {
+ expect(call).toContain(
+ "organizationId: compose.environment.project.organizationId",
+ );
+ }
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/compose/domain.test.ts b/apps/dokploy/__test__/caddy/compose/domain.test.ts
new file mode 100644
index 0000000000..35208b5651
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/compose/domain.test.ts
@@ -0,0 +1,348 @@
+import path from "node:path";
+import type { Compose, Domain } from "@dokploy/server";
+import { fs, vol } from "memfs";
+import { parse, stringify } from "yaml";
+
+vi.mock("node:fs", () => ({
+ ...fs,
+ default: fs,
+}));
+
+const execAsyncMock = vi.hoisted(() => vi.fn());
+const execAsyncRemoteMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server/utils/process/execAsync", () => ({
+ execAsync: execAsyncMock,
+ execAsyncRemote: execAsyncRemoteMock,
+}));
+
+import {
+ addDomainToCompose,
+ addDomainToComposeForWebServer,
+ compileCaddyConfig,
+ createCaddyComposeRouteFragment,
+ createDomainLabels,
+ isDokployGeneratedTraefikLabel,
+ paths,
+ readCaddyRouteFragments,
+ refreshCaddyComposeRoutes,
+} from "@dokploy/server";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+const compose = (overrides: Partial = {}) =>
+ ({
+ appName: "my-compose",
+ serverId: null,
+ sourceType: "raw",
+ composePath: "docker-compose.yml",
+ composeType: "docker-compose",
+ isolatedDeployment: false,
+ isolatedDeploymentsVolume: false,
+ randomize: false,
+ suffix: null,
+ ...overrides,
+ }) as Compose;
+
+const domain = (overrides: Partial = {}) =>
+ ({
+ domainId: "domain-1",
+ applicationId: null,
+ composeId: "compose-1",
+ previewDeploymentId: null,
+ domainType: "compose",
+ host: "example.com",
+ path: "/",
+ internalPath: "/",
+ stripPath: false,
+ https: false,
+ certificateType: "none",
+ customCertResolver: null,
+ customEntrypoint: null,
+ middlewares: null,
+ port: 8080,
+ serviceName: "web",
+ uniqueConfigKey: 3,
+ createdAt: "",
+ ...overrides,
+ }) as Domain;
+
+const writeComposeFile = (
+ composeInput: Compose,
+ content: Record,
+) => {
+ const filePath = path.join(
+ paths(false).COMPOSE_PATH,
+ composeInput.appName,
+ "code",
+ "docker-compose.yml",
+ );
+ vol.mkdirSync(path.dirname(filePath), { recursive: true });
+ vol.writeFileSync(filePath, stringify(content));
+};
+
+const getServers = (config: ReturnType) => {
+ const apps = config.apps as Record;
+ return apps.http.servers as Record;
+};
+
+beforeEach(() => {
+ vol.reset();
+ execAsyncMock.mockReset();
+ execAsyncRemoteMock.mockReset();
+ execAsyncMock.mockResolvedValue({ stdout: "dokploy-caddy\n", stderr: "" });
+ execAsyncRemoteMock.mockResolvedValue({
+ stdout: "dokploy-caddy\n",
+ stderr: "",
+ });
+});
+
+describe("Caddy compose route generation", () => {
+ test("creates route fragments with compose upstream names", () => {
+ const fragment = createCaddyComposeRouteFragment(
+ compose(),
+ domain(),
+ "web",
+ );
+ const config = compileCaddyConfig({ fragments: [fragment] });
+
+ expect(fragment).toMatchObject({
+ id: "compose.my-compose.3",
+ source: "dokploy-compose",
+ });
+ expect(getServers(config).http.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "my-compose-web:8080" }],
+ });
+ });
+
+ test("uses swarm stack service names for Caddy upstreams", () => {
+ const fragment = createCaddyComposeRouteFragment(
+ compose({ composeType: "stack" }),
+ domain(),
+ "web-blue",
+ );
+ const config = compileCaddyConfig({ fragments: [fragment] });
+
+ expect(getServers(config).http.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "my-compose_web-blue:8080" }],
+ });
+ });
+
+ test("refreshes Caddy compose route fragments for domains created outside the domain router", async () => {
+ const composeInput = compose();
+ const routeDomain = domain({ https: true });
+ writeComposeFile(composeInput, {
+ services: {
+ web: { image: "nginx" },
+ },
+ });
+
+ await refreshCaddyComposeRoutes(composeInput, [routeDomain], "caddy");
+
+ const fragments = await readCaddyRouteFragments();
+ expect(fragments).toHaveLength(1);
+ expect(fragments[0]).toMatchObject({
+ id: "compose.my-compose.3",
+ source: "dokploy-compose",
+ });
+ const config = compileCaddyConfig({ fragments });
+ expect(getServers(config).https.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "my-compose-web:8080" }],
+ });
+ });
+
+ test("loads uploaded certificates for custom Caddy compose HTTPS routes", () => {
+ const fragment = createCaddyComposeRouteFragment(
+ compose(),
+ domain({
+ https: true,
+ certificateType: "custom",
+ customCertResolver: "certificate-uploaded",
+ }),
+ "web",
+ );
+ const config = compileCaddyConfig({ fragments: [fragment] });
+ const tls = (config.apps as any).tls;
+ const certificatePath = `${paths(false).CERTIFICATES_PATH}/certificate-uploaded`;
+
+ expect(tls.certificates.load_files).toEqual([
+ {
+ certificate: `${certificatePath}/chain.crt`,
+ key: `${certificatePath}/privkey.key`,
+ },
+ ]);
+ expect(getServers(config).https.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "my-compose-web:8080" }],
+ });
+ });
+
+ test("skips compose route refresh for Traefik provider", async () => {
+ const composeInput = compose();
+ writeComposeFile(composeInput, {
+ services: {
+ web: { image: "nginx" },
+ },
+ });
+
+ await refreshCaddyComposeRoutes(composeInput, [domain()], "traefik");
+
+ expect(await readCaddyRouteFragments()).toEqual([]);
+ expect(execAsyncMock).not.toHaveBeenCalled();
+ });
+
+ test("Traefik provider conversion delegates to the existing Traefik label path", async () => {
+ const composeInput = compose();
+ writeComposeFile(composeInput, {
+ services: {
+ web: { image: "nginx" },
+ },
+ });
+
+ const existingTraefikOutput = await addDomainToCompose(composeInput, [
+ domain(),
+ ]);
+ const providerOutput = await addDomainToComposeForWebServer(
+ composeInput,
+ [domain()],
+ "traefik",
+ );
+
+ expect(providerOutput).toEqual(existingTraefikOutput);
+ });
+
+ test("Caddy compose conversion strips only Dokploy Traefik labels and attaches shared network", async () => {
+ const composeInput = compose();
+ const routeDomain = domain({ https: true, path: "/app", stripPath: true });
+ const dokployLabels = [
+ "traefik.docker.network=dokploy-network",
+ "traefik.enable=true",
+ ...createDomainLabels(composeInput.appName, routeDomain, "web"),
+ ...createDomainLabels(composeInput.appName, routeDomain, "websecure"),
+ ];
+ writeComposeFile(composeInput, {
+ services: {
+ web: {
+ image: "nginx",
+ labels: [
+ "com.example.keep=true",
+ "traefik.http.routers.manual.rule=Host(`manual.example.com`)",
+ "traefik.http.routers.my-compose-custom-web.rule=Host(`custom.example.com`)",
+ ...dokployLabels,
+ ],
+ },
+ },
+ });
+
+ const converted = await addDomainToComposeForWebServer(
+ composeInput,
+ [routeDomain],
+ "caddy",
+ );
+
+ const webService = converted?.services?.web;
+ expect(webService).toBeDefined();
+ const labels = webService?.labels as string[];
+ expect(labels).toEqual([
+ "com.example.keep=true",
+ "traefik.http.routers.manual.rule=Host(`manual.example.com`)",
+ "traefik.http.routers.my-compose-custom-web.rule=Host(`custom.example.com`)",
+ ]);
+ expect(webService?.networks).toEqual({
+ "dokploy-network": { aliases: ["my-compose-web"] },
+ default: {},
+ });
+ expect(converted?.networks?.["dokploy-network"]).toEqual({
+ external: true,
+ });
+ });
+
+ test("Caddy compose conversion rejects unsupported domain fields before mutating compose labels", async () => {
+ const composeInput = compose();
+ writeComposeFile(composeInput, {
+ services: {
+ web: {
+ image: "nginx",
+ labels: [
+ "traefik.enable=true",
+ "traefik.http.routers.my-compose-3-web.rule=Host(`example.com`)",
+ ],
+ },
+ },
+ });
+
+ await expect(
+ addDomainToComposeForWebServer(
+ composeInput,
+ [domain({ middlewares: ["auth@file"] })],
+ "caddy",
+ ),
+ ).rejects.toThrow("unsupported Caddy fields");
+
+ const stored = parse(
+ vol
+ .readFileSync(
+ path.join(
+ paths(false).COMPOSE_PATH,
+ composeInput.appName,
+ "code",
+ "docker-compose.yml",
+ ),
+ "utf8",
+ )
+ .toString(),
+ ) as any;
+ expect(stored.services.web.labels).toEqual([
+ "traefik.enable=true",
+ "traefik.http.routers.my-compose-3-web.rule=Host(`example.com`)",
+ ]);
+ });
+
+ test("Caddy compose conversion uses finalized randomized service names", async () => {
+ const composeInput = compose({ randomize: true, suffix: "blue" });
+ writeComposeFile(composeInput, {
+ services: {
+ web: { image: "nginx" },
+ worker: { image: "busybox", depends_on: ["web"] },
+ },
+ });
+
+ const converted = await addDomainToComposeForWebServer(
+ composeInput,
+ [domain()],
+ "caddy",
+ );
+
+ expect(converted?.services?.web).toBeUndefined();
+ expect(converted?.services?.["web-blue"]?.networks).toEqual({
+ "dokploy-network": { aliases: ["my-compose-web-blue"] },
+ default: {},
+ });
+ expect(converted?.services?.["worker-blue"]?.depends_on).toEqual([
+ "web-blue",
+ ]);
+ });
+
+ test("classifier identifies current Dokploy-generated Traefik labels without matching unrelated labels", () => {
+ const routeDomain = domain({ https: true });
+ expect(
+ isDokployGeneratedTraefikLabel(
+ "traefik.http.routers.my-compose-3-web.rule=Host(`example.com`)",
+ { appName: "my-compose", domains: [routeDomain] },
+ ),
+ ).toBe(true);
+ expect(
+ isDokployGeneratedTraefikLabel("traefik.enable=true", {
+ includeGenericLabels: true,
+ }),
+ ).toBe(true);
+ expect(
+ isDokployGeneratedTraefikLabel(
+ "traefik.http.routers.manual.rule=Host(`manual.example.com`)",
+ { appName: "my-compose", domains: [routeDomain] },
+ ),
+ ).toBe(false);
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/config.test.ts b/apps/dokploy/__test__/caddy/config.test.ts
new file mode 100644
index 0000000000..96c0659ed5
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/config.test.ts
@@ -0,0 +1,818 @@
+import { fs, vol } from "memfs";
+
+vi.mock("node:fs", () => ({
+ ...fs,
+ default: fs,
+}));
+
+const execAsyncMock = vi.hoisted(() => vi.fn());
+const execAsyncRemoteMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server/utils/process/execAsync", () => ({
+ execAsync: execAsyncMock,
+ execAsyncRemote: execAsyncRemoteMock,
+}));
+
+import {
+ type ApplicationNested,
+ type CaddyRouteFragment,
+ type CaddyRouteIntent,
+ CLOUDFLARE_TRUSTED_PROXY_RANGES,
+ caddyTrustedProxySettingsToConfig,
+ compileAndWriteCaddyConfig,
+ compileCaddyConfig,
+ compileWriteAndReloadCaddyConfigSafely,
+ compileWriteAndValidateCaddyConfigSafely,
+ createCaddyDashboardRouteFragment,
+ type Domain,
+ getCaddyMigrationArtifactPaths,
+ manageCaddyDomain,
+ normalizeCaddyTrustedProxySettings,
+ paths,
+ readCaddyRouteFragments,
+ removeCaddyDomain,
+ updateServerCaddy,
+ validateCaddyConfigFileWithImage,
+ writeCaddyRouteFragment,
+} from "@dokploy/server";
+import type { webServerSettings } from "@dokploy/server/db/schema";
+import { beforeEach, expect, test, vi } from "vitest";
+
+const route = (
+ overrides: Partial = {},
+): CaddyRouteIntent => ({
+ id: "app-route",
+ source: "dokploy-application",
+ hosts: ["example.com"],
+ https: false,
+ upstreams: ["http://app:3000"],
+ ...overrides,
+});
+
+const getServers = (config: ReturnType) => {
+ const apps = config.apps as Record;
+ return apps.http.servers as Record;
+};
+
+type WebServerSettings = typeof webServerSettings.$inferSelect;
+
+const settings = (overrides: Partial = {}) =>
+ ({
+ id: "settings-1",
+ webServerProvider: "caddy",
+ caddyTrustedProxyConfig: null,
+ requestLogsEnabled: false,
+ https: false,
+ certificateType: "none",
+ host: null,
+ serverIp: null,
+ letsEncryptEmail: null,
+ sshPrivateKey: null,
+ enableDockerCleanup: false,
+ logCleanupCron: null,
+ metricsConfig: {} as WebServerSettings["metricsConfig"],
+ whitelabelingConfig: null,
+ remoteServersOnly: false,
+ enforceSSO: false,
+ cleanupCacheApplications: false,
+ cleanupCacheOnCompose: false,
+ cleanupCacheOnPreviews: false,
+ createdAt: null,
+ updatedAt: new Date(),
+ ...overrides,
+ }) as WebServerSettings;
+
+beforeEach(() => {
+ vol.reset();
+ execAsyncMock.mockReset();
+ execAsyncRemoteMock.mockReset();
+ execAsyncMock.mockResolvedValue({ stdout: "dokploy-caddy\n", stderr: "" });
+ execAsyncRemoteMock.mockResolvedValue({
+ stdout: "dokploy-caddy\n",
+ stderr: "",
+ });
+});
+
+test("compiles explicit http and https servers with managed HTTPS redirect", () => {
+ const config = compileCaddyConfig({
+ letsEncryptEmail: "ops@example.com",
+ routes: [route({ https: true })],
+ });
+
+ const servers = getServers(config);
+ expect(servers.http.listen).toEqual([":80"]);
+ expect(servers.https.listen).toEqual([":443"]);
+ expect(servers.http.routes[0].handle[0].handler).toBe("static_response");
+ expect(servers.http.routes[0].handle[0].headers.Location).toEqual([
+ "https://{http.request.host}{http.request.uri}",
+ ]);
+ expect(servers.https.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "app:3000" }],
+ });
+ expect((config.apps as any).tls.automation.policies[0].issuers[0].email).toBe(
+ "ops@example.com",
+ );
+ expect(servers.http.trusted_proxies).toBeUndefined();
+ expect(servers.https.trusted_proxies).toBeUndefined();
+ expect(servers.http.client_ip_headers).toBeUndefined();
+ expect(servers.https.client_ip_headers).toBeUndefined();
+ expect((config as any).logging).toBeUndefined();
+ expect(servers.http.logs).toBeUndefined();
+ expect(servers.https.logs).toBeUndefined();
+});
+
+test("compiles Caddy access-log output when request analytics are enabled", () => {
+ const config = compileCaddyConfig({
+ routes: [route({ https: true })],
+ accessLogs: { enabled: true },
+ });
+
+ const servers = getServers(config);
+ expect(servers.http.logs).toEqual({});
+ expect(servers.https.logs).toEqual({});
+ expect((config as any).logging.logs["dokploy-requests"]).toEqual({
+ writer: {
+ output: "file",
+ filename: "/etc/caddy/access.log",
+ },
+ encoder: {
+ format: "json",
+ },
+ include: ["http.log.access"],
+ });
+});
+
+test("dashboard Caddy updates preserve enabled request access logs", async () => {
+ await updateServerCaddy(
+ settings({
+ requestLogsEnabled: true,
+ https: true,
+ letsEncryptEmail: "ops@example.com",
+ caddyTrustedProxyConfig: {
+ mode: "static",
+ ranges: ["192.0.2.0/24"],
+ clientIpHeaders: ["X-Forwarded-For"],
+ strict: true,
+ },
+ }),
+ "dashboard.example.com",
+ );
+
+ const config = JSON.parse(
+ vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8") as string,
+ );
+ const servers = (config.apps as Record).http.servers;
+
+ expect(servers.http.logs).toEqual({});
+ expect(servers.http.trusted_proxies).toEqual({
+ source: "static",
+ ranges: ["192.0.2.0/24"],
+ });
+ expect(servers.http.client_ip_headers).toEqual(["X-Forwarded-For"]);
+ expect(servers.https.trusted_proxies).toEqual(servers.http.trusted_proxies);
+ expect((config.apps as any).tls.automation.policies[0].issuers[0].email).toBe(
+ "ops@example.com",
+ );
+ expect((config as any).logging.logs["dokploy-requests"]).toEqual({
+ writer: {
+ output: "file",
+ filename: "/etc/caddy/access.log",
+ },
+ encoder: {
+ format: "json",
+ },
+ include: ["http.log.access"],
+ });
+});
+
+test("restores dashboard fragments when Caddy dashboard reload fails", async () => {
+ const existingFragment = createCaddyDashboardRouteFragment(
+ settings(),
+ "old-dashboard.example.com",
+ );
+ const concurrentFragment: CaddyRouteFragment = {
+ version: 1,
+ id: "application.concurrent",
+ source: "dokploy-application",
+ routes: [route({ id: "concurrent", hosts: ["concurrent.example.com"] })],
+ };
+ await writeCaddyRouteFragment(existingFragment);
+ const previousConfig = `${JSON.stringify(
+ compileCaddyConfig({ fragments: [existingFragment] }),
+ null,
+ 2,
+ )}\n`;
+ vol.mkdirSync(paths().MAIN_CADDY_PATH, { recursive: true });
+ vol.writeFileSync(paths().CADDY_CONFIG_PATH, previousConfig);
+ let concurrentFragmentWritten = false;
+ execAsyncMock.mockImplementation(async (command: string) => {
+ if (command.includes("caddy validate")) {
+ if (!concurrentFragmentWritten) {
+ concurrentFragmentWritten = true;
+ await writeCaddyRouteFragment(concurrentFragment);
+ }
+ throw new Error("validation failed");
+ }
+ return { stdout: "dokploy-caddy\n", stderr: "" };
+ });
+
+ await expect(
+ updateServerCaddy(settings(), "new-dashboard.example.com"),
+ ).rejects.toThrow("validation failed");
+
+ expect(await readCaddyRouteFragments()).toEqual([
+ concurrentFragment,
+ existingFragment,
+ ]);
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ previousConfig,
+ );
+});
+
+test("does not overwrite a concurrent dashboard fragment update when dashboard reload fails", async () => {
+ const existingFragment = createCaddyDashboardRouteFragment(
+ settings(),
+ "old-dashboard.example.com",
+ );
+ const concurrentDashboardFragment = createCaddyDashboardRouteFragment(
+ settings(),
+ "concurrent-dashboard.example.com",
+ );
+ await writeCaddyRouteFragment(existingFragment);
+ const previousConfig = `${JSON.stringify(
+ compileCaddyConfig({ fragments: [existingFragment] }),
+ null,
+ 2,
+ )}\n`;
+ const concurrentConfig = `${JSON.stringify(
+ compileCaddyConfig({ fragments: [concurrentDashboardFragment] }),
+ null,
+ 2,
+ )}\n`;
+ vol.mkdirSync(paths().MAIN_CADDY_PATH, { recursive: true });
+ vol.writeFileSync(paths().CADDY_CONFIG_PATH, previousConfig);
+ let failedInitialValidation = false;
+ execAsyncMock.mockImplementation(async (command: string) => {
+ if (command.includes("caddy validate") && !failedInitialValidation) {
+ failedInitialValidation = true;
+ await writeCaddyRouteFragment(concurrentDashboardFragment);
+ throw new Error("validation failed");
+ }
+ return { stdout: "dokploy-caddy\n", stderr: "" };
+ });
+
+ await expect(
+ updateServerCaddy(settings(), "new-dashboard.example.com"),
+ ).rejects.toThrow("validation failed");
+
+ expect(await readCaddyRouteFragments()).toEqual([
+ concurrentDashboardFragment,
+ ]);
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ concurrentConfig,
+ );
+});
+
+test("restores removed dashboard fragments when Caddy dashboard removal reload fails", async () => {
+ const existingFragment = createCaddyDashboardRouteFragment(
+ settings(),
+ "old-dashboard.example.com",
+ );
+ await writeCaddyRouteFragment(existingFragment);
+ const previousConfig = `${JSON.stringify(
+ compileCaddyConfig({ fragments: [existingFragment] }),
+ null,
+ 2,
+ )}\n`;
+ vol.mkdirSync(paths().MAIN_CADDY_PATH, { recursive: true });
+ vol.writeFileSync(paths().CADDY_CONFIG_PATH, previousConfig);
+ execAsyncMock.mockImplementation(async (command: string) => {
+ if (command.includes("caddy validate")) {
+ throw new Error("validation failed");
+ }
+ return { stdout: "dokploy-caddy\n", stderr: "" };
+ });
+
+ await expect(updateServerCaddy(settings(), null)).rejects.toThrow(
+ "validation failed",
+ );
+
+ expect(await readCaddyRouteFragments()).toEqual([existingFragment]);
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ previousConfig,
+ );
+});
+
+test("compiles Cloudflare trusted proxy settings with safe client IP headers", () => {
+ const config = compileCaddyConfig({
+ routes: [route({ https: true })],
+ trustedProxies: {
+ source: "cloudflare",
+ },
+ });
+
+ const servers = getServers(config);
+ for (const server of [servers.http, servers.https]) {
+ expect(server.trusted_proxies).toEqual({
+ source: "static",
+ ranges: [...CLOUDFLARE_TRUSTED_PROXY_RANGES],
+ });
+ expect(server.client_ip_headers).toEqual([
+ "CF-Connecting-IP",
+ "X-Forwarded-For",
+ ]);
+ expect(server.trusted_proxies_strict).toBe(true);
+ }
+});
+
+test("compiles custom static trusted proxy ranges", () => {
+ const config = compileCaddyConfig({
+ routes: [route()],
+ trustedProxies: {
+ source: "static",
+ ranges: ["192.0.2.0/24", "2001:db8::/32"],
+ clientIpHeaders: ["X-Forwarded-For"],
+ strict: false,
+ },
+ });
+
+ const servers = getServers(config);
+ expect(servers.http).toMatchObject({
+ trusted_proxies: {
+ source: "static",
+ ranges: ["192.0.2.0/24", "2001:db8::/32"],
+ },
+ client_ip_headers: ["X-Forwarded-For"],
+ });
+ expect(servers.http.trusted_proxies_strict).toBeUndefined();
+ expect(servers.https.trusted_proxies).toEqual(servers.http.trusted_proxies);
+});
+
+test("normalizes persisted Caddy trusted proxy settings", () => {
+ const settings = normalizeCaddyTrustedProxySettings({
+ mode: "static",
+ ranges: [" 192.0.2.0/24 ", "192.0.2.0/24"],
+ clientIpHeaders: [" X-Forwarded-For ", ""],
+ strict: null,
+ });
+
+ expect(settings).toEqual({
+ mode: "static",
+ ranges: ["192.0.2.0/24"],
+ clientIpHeaders: ["X-Forwarded-For"],
+ strict: true,
+ });
+ expect(caddyTrustedProxySettingsToConfig(settings)).toEqual({
+ source: "static",
+ ranges: ["192.0.2.0/24"],
+ clientIpHeaders: ["X-Forwarded-For"],
+ strict: true,
+ });
+ expect(normalizeCaddyTrustedProxySettings({ mode: "disabled" })).toBeNull();
+});
+
+test("rejects invalid trusted proxy settings before writing Caddy config", () => {
+ expect(() =>
+ compileCaddyConfig({
+ trustedProxies: { source: "static", ranges: ["192.0.2.0/33"] },
+ }),
+ ).toThrow("Invalid Caddy trusted proxy prefix");
+
+ expect(() =>
+ compileCaddyConfig({
+ trustedProxies: {
+ source: "static",
+ ranges: ["192.0.2.0/24"],
+ clientIpHeaders: ["X-Forwarded-For", "x-forwarded-for"],
+ },
+ }),
+ ).toThrow("Duplicate Caddy trusted proxy client IP header");
+});
+
+test("sorts routes by priority before path specificity and stable id", () => {
+ const config = compileCaddyConfig({
+ routes: [
+ route({ id: "low-specific", priority: 1, pathPrefix: "/very/specific" }),
+ route({ id: "high-priority", priority: 10, pathPrefix: "/api" }),
+ route({ id: "same-priority-longer", priority: 1, pathPrefix: "/longer" }),
+ route({
+ id: "same-tiebreak-b",
+ priority: 1,
+ pathPrefix: "/same",
+ upstreams: ["http://b:3000"],
+ }),
+ route({
+ id: "same-tiebreak-a",
+ priority: 1,
+ pathPrefix: "/same",
+ upstreams: ["http://a:3000"],
+ }),
+ ],
+ });
+
+ const [first, second, third, fourth, fifth] = getServers(config).http.routes;
+ expect(first.match[0].path).toEqual(["/api*"]);
+ expect(second.match[0].path).toEqual(["/very/specific*"]);
+ expect(third.match[0].path).toEqual(["/longer*"]);
+ expect(fourth.handle.at(-1).upstreams).toEqual([{ dial: "a:3000" }]);
+ expect(fifth.handle.at(-1).upstreams).toEqual([{ dial: "b:3000" }]);
+});
+
+test("orders migrated manual and Traefik routes before DB fallbacks for identical catch-all matches", () => {
+ const config = compileCaddyConfig({
+ routes: [
+ route({
+ id: "db-compose-fallback",
+ source: "dokploy-compose",
+ https: true,
+ upstreams: ["http://cms:3000"],
+ }),
+ route({
+ id: "manual-waf",
+ source: "manual",
+ https: true,
+ upstreams: ["http://waf:8080"],
+ }),
+ route({
+ id: "dynamic-blog",
+ source: "traefik-dynamic-file",
+ https: true,
+ upstreams: ["http://blog:8080"],
+ }),
+ route({
+ id: "label-cms",
+ source: "traefik-compose-label",
+ https: true,
+ upstreams: ["http://cms:80"],
+ }),
+ ],
+ });
+
+ const upstreamOrder = getServers(config).https.routes.map(
+ (compiledRoute: any) => compiledRoute.handle.at(-1).upstreams[0].dial,
+ );
+ expect(upstreamOrder).toEqual([
+ "waf:8080",
+ "cms:80",
+ "blog:8080",
+ "cms:3000",
+ ]);
+});
+
+test("renders transforms, headers, and HTTPS upstream transport", () => {
+ const config = compileCaddyConfig({
+ routes: [
+ route({
+ upstreams: ["https://upstream.example.com:443"],
+ transforms: {
+ stripPrefix: "/public",
+ addPrefix: "/internal",
+ responseHeaders: {
+ "Cache-Control": "no-store",
+ },
+ },
+ }),
+ ],
+ });
+
+ const handlers = getServers(config).http.routes[0].handle;
+ expect(handlers[0]).toMatchObject({
+ handler: "headers",
+ response: { set: { "Cache-Control": ["no-store"] } },
+ });
+ expect(handlers[1]).toMatchObject({
+ handler: "rewrite",
+ strip_path_prefix: "/public",
+ });
+ expect(handlers[2]).toMatchObject({
+ handler: "rewrite",
+ uri: "/internal{http.request.uri.path}",
+ });
+ expect(handlers[3]).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "upstream.example.com:443" }],
+ transport: { protocol: "http", tls: {} },
+ });
+});
+
+test("rejects proxy upstreams without explicit valid ports", () => {
+ for (const upstream of [
+ "http://admin",
+ "https://external.example.com",
+ "http://app:0",
+ "http://app:65536",
+ "http://app:abc",
+ "app",
+ "app:0",
+ ]) {
+ expect(() =>
+ compileCaddyConfig({
+ routes: [route({ upstreams: [upstream] })],
+ }),
+ ).toThrow(`invalid upstream "${upstream}"`);
+ }
+});
+
+test("allows redirect-only routes without upstreams", () => {
+ const config = compileCaddyConfig({
+ routes: [
+ route({
+ upstreams: [],
+ redirectScheme: { scheme: "https", permanent: true },
+ }),
+ ],
+ });
+
+ expect(getServers(config).http.routes[0].handle[0]).toMatchObject({
+ handler: "static_response",
+ status_code: 308,
+ });
+});
+
+test("allows static response routes without upstreams", () => {
+ const config = compileCaddyConfig({
+ routes: [
+ route({
+ source: "manual",
+ https: true,
+ upstreams: [],
+ staticResponse: {
+ statusCode: 404,
+ headers: {
+ "Cache-Control": "no-store",
+ },
+ },
+ }),
+ ],
+ });
+
+ const servers = getServers(config);
+ expect(servers.http.routes[0].handle[0]).toMatchObject({
+ handler: "static_response",
+ status_code: 308,
+ });
+ expect(servers.https.routes[0].handle).toEqual([
+ {
+ handler: "static_response",
+ status_code: 404,
+ headers: {
+ "Cache-Control": ["no-store"],
+ },
+ },
+ ]);
+});
+
+test("stores fragments and compiles them into the active config", async () => {
+ const fragment: CaddyRouteFragment = {
+ version: 1,
+ id: "app.example",
+ source: "dokploy-application",
+ routes: [route({ id: "stored", pathPrefix: "/stored" })],
+ };
+
+ await writeCaddyRouteFragment(fragment);
+ const fragments = await readCaddyRouteFragments();
+ const config = await compileAndWriteCaddyConfig();
+
+ expect(fragments).toEqual([fragment]);
+ expect(getServers(config).http.routes[0].match[0].path).toEqual(["/stored*"]);
+});
+
+test("rejects invalid fragment ids before touching the store", async () => {
+ for (const id of ["../bad", "..", ".", "bad..segment"]) {
+ await expect(
+ writeCaddyRouteFragment({
+ version: 1,
+ id,
+ source: "manual",
+ routes: [route()],
+ }),
+ ).rejects.toThrow("Invalid Caddy fragment id");
+ }
+ expect(vol.existsSync(paths().CADDY_FRAGMENTS_PATH)).toBe(false);
+});
+
+test("rejects unsafe Caddy migration ids", () => {
+ for (const id of ["..", ".", "bad..segment"]) {
+ expect(() => getCaddyMigrationArtifactPaths(id)).toThrow(
+ "Invalid Caddy migration id",
+ );
+ }
+});
+
+test("validates a config file with the Caddy binary in an isolated runtime container", async () => {
+ await validateCaddyConfigFileWithImage(
+ "/etc/dokploy/caddy/migrations/test/caddy.json",
+ );
+
+ const validateCommand = execAsyncMock.mock.calls
+ .map(([command]) => command as string)
+ .find((command) => command.includes("docker run"));
+ expect(validateCommand).toContain("docker run --rm --network none");
+ expect(validateCommand).toContain("caddy\\:2.11.3");
+ expect(validateCommand).toContain(
+ "/etc/dokploy/caddy/migrations/test/caddy.json\\:/etc/caddy/caddy.json\\:ro",
+ );
+ expect(validateCommand).toContain(
+ "/etc/dokploy/caddy/migrations/test/.validate-runtime/config\\:/config",
+ );
+ expect(validateCommand).toContain(
+ `${paths().CERTIFICATES_PATH}\\:${paths().CERTIFICATES_PATH}\\:ro`,
+ );
+ expect(validateCommand).toContain(" caddy validate --config");
+ expect(validateCommand).not.toContain("caddy\\:2.11.3 validate --config");
+});
+
+test("restores the previous Caddy config when safe validation fails without reloading", async () => {
+ const previousConfig = `${JSON.stringify(
+ compileCaddyConfig({ routes: [route({ hosts: ["old.example.com"] })] }),
+ null,
+ 2,
+ )}\n`;
+ vol.mkdirSync(paths().MAIN_CADDY_PATH, { recursive: true });
+ vol.mkdirSync(paths().CADDY_FRAGMENTS_PATH, { recursive: true });
+ vol.writeFileSync(paths().CADDY_CONFIG_PATH, previousConfig);
+ execAsyncMock.mockImplementation(async (command: string) => {
+ if (command.includes("caddy validate")) {
+ throw new Error("validation failed");
+ }
+ return { stdout: "dokploy-caddy\n", stderr: "" };
+ });
+ const consoleError = vi
+ .spyOn(console, "error")
+ .mockImplementation(() => undefined);
+
+ await expect(
+ compileWriteAndValidateCaddyConfigSafely({
+ accessLogs: { enabled: true },
+ }),
+ ).rejects.toThrow("validation failed");
+
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ previousConfig,
+ );
+ expect(
+ execAsyncMock.mock.calls.some(([command]) =>
+ (command as string).includes("caddy reload"),
+ ),
+ ).toBe(false);
+ consoleError.mockRestore();
+});
+
+test("reloads the restored Caddy config when safe reload fails", async () => {
+ const previousConfig = `${JSON.stringify(
+ compileCaddyConfig({ routes: [route({ hosts: ["old.example.com"] })] }),
+ null,
+ 2,
+ )}\n`;
+ vol.mkdirSync(paths().MAIN_CADDY_PATH, { recursive: true });
+ vol.mkdirSync(paths().CADDY_FRAGMENTS_PATH, { recursive: true });
+ vol.writeFileSync(paths().CADDY_CONFIG_PATH, previousConfig);
+ let reloads = 0;
+ execAsyncMock.mockImplementation(async (command: string) => {
+ if (command.includes("caddy reload")) {
+ reloads += 1;
+ if (reloads === 1) {
+ throw new Error("reload failed");
+ }
+ }
+ return { stdout: "dokploy-caddy\n", stderr: "" };
+ });
+
+ await expect(
+ compileWriteAndReloadCaddyConfigSafely({
+ trustedProxies: {
+ source: "static",
+ ranges: ["192.0.2.0/24"],
+ },
+ }),
+ ).rejects.toThrow("reload failed");
+
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ previousConfig,
+ );
+ expect(reloads).toBe(2);
+});
+
+test("preserves the original safe reload error when the restored Caddy reload also fails", async () => {
+ const previousConfig = `${JSON.stringify(
+ compileCaddyConfig({ routes: [route({ hosts: ["old.example.com"] })] }),
+ null,
+ 2,
+ )}\n`;
+ vol.mkdirSync(paths().MAIN_CADDY_PATH, { recursive: true });
+ vol.mkdirSync(paths().CADDY_FRAGMENTS_PATH, { recursive: true });
+ vol.writeFileSync(paths().CADDY_CONFIG_PATH, previousConfig);
+ const restoreError = new Error("restored reload failed");
+ let reloads = 0;
+ execAsyncMock.mockImplementation(async (command: string) => {
+ if (command.includes("caddy reload")) {
+ reloads += 1;
+ if (reloads === 1) {
+ throw new Error("new config reload failed");
+ }
+ throw restoreError;
+ }
+ return { stdout: "dokploy-caddy\n", stderr: "" };
+ });
+ const consoleError = vi
+ .spyOn(console, "error")
+ .mockImplementation(() => undefined);
+
+ let thrownError: Error | undefined;
+ try {
+ await compileWriteAndReloadCaddyConfigSafely({
+ trustedProxies: {
+ source: "static",
+ ranges: ["192.0.2.0/24"],
+ },
+ });
+ } catch (error) {
+ thrownError = error as Error;
+ }
+
+ expect(thrownError).toBeInstanceOf(Error);
+ expect(thrownError?.message).toBe("new config reload failed");
+ expect((thrownError as Error & { restoreError?: unknown }).restoreError).toBe(
+ restoreError,
+ );
+ expect(consoleError).toHaveBeenCalledWith(
+ "Failed to restore Caddy config:",
+ restoreError,
+ );
+ consoleError.mockRestore();
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ previousConfig,
+ );
+ expect(reloads).toBe(2);
+});
+
+test("restores previous fragments when Caddy domain reload fails", async () => {
+ const existingFragment: CaddyRouteFragment = {
+ version: 1,
+ id: "app.existing",
+ source: "dokploy-application",
+ routes: [route({ id: "existing", hosts: ["old.example.com"] })],
+ };
+ await writeCaddyRouteFragment(existingFragment);
+ execAsyncMock.mockImplementation(async (command: string) => {
+ if (command.includes("caddy validate")) {
+ throw new Error("validation failed");
+ }
+ return { stdout: "dokploy-caddy\n", stderr: "" };
+ });
+
+ await expect(
+ manageCaddyDomain(
+ { appName: "my-app", serverId: null } as ApplicationNested,
+ {
+ domainId: "domain-1",
+ applicationId: "app-1",
+ composeId: null,
+ previewDeploymentId: null,
+ domainType: "application",
+ host: "example.com",
+ path: "/",
+ internalPath: "/",
+ stripPath: false,
+ https: false,
+ certificateType: "none",
+ customCertResolver: null,
+ customEntrypoint: null,
+ middlewares: null,
+ port: 3000,
+ serviceName: null,
+ uniqueConfigKey: 7,
+ createdAt: "",
+ } as Domain,
+ ),
+ ).rejects.toThrow("validation failed");
+
+ expect(await readCaddyRouteFragments()).toEqual([existingFragment]);
+});
+
+test("restores removed fragments when Caddy domain removal reload fails", async () => {
+ const existingFragment: CaddyRouteFragment = {
+ version: 1,
+ id: "application.my-app.7",
+ source: "dokploy-application",
+ routes: [route({ id: "existing", hosts: ["old.example.com"] })],
+ };
+ await writeCaddyRouteFragment(existingFragment);
+ execAsyncMock.mockImplementation(async (command: string) => {
+ if (command.includes("caddy validate")) {
+ throw new Error("validation failed");
+ }
+ return { stdout: "dokploy-caddy\n", stderr: "" };
+ });
+
+ await expect(
+ removeCaddyDomain(
+ { appName: "my-app", serverId: null } as ApplicationNested,
+ 7,
+ ),
+ ).rejects.toThrow("validation failed");
+
+ expect(await readCaddyRouteFragments()).toEqual([existingFragment]);
+});
diff --git a/apps/dokploy/__test__/caddy/dashboard-route.test.ts b/apps/dokploy/__test__/caddy/dashboard-route.test.ts
new file mode 100644
index 0000000000..7ed02b2096
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/dashboard-route.test.ts
@@ -0,0 +1,88 @@
+import {
+ compileCaddyConfig,
+ createCaddyDashboardRouteFragment,
+} from "@dokploy/server";
+import type { webServerSettings } from "@dokploy/server/db/schema";
+import { expect, test } from "vitest";
+
+type WebServerSettings = typeof webServerSettings.$inferSelect;
+
+const settings = (overrides: Partial = {}) =>
+ ({
+ id: "settings-1",
+ webServerProvider: "caddy",
+ caddyTrustedProxyConfig: null,
+ https: false,
+ certificateType: "none",
+ host: null,
+ serverIp: null,
+ letsEncryptEmail: null,
+ sshPrivateKey: null,
+ enableDockerCleanup: false,
+ logCleanupCron: null,
+ metricsConfig: {} as WebServerSettings["metricsConfig"],
+ whitelabelingConfig: null,
+ cleanupCacheApplications: false,
+ cleanupCacheOnCompose: false,
+ cleanupCacheOnPreviews: false,
+ createdAt: null,
+ updatedAt: new Date(),
+ ...overrides,
+ }) as WebServerSettings;
+
+const getServers = (config: ReturnType) => {
+ const apps = config.apps as Record;
+ return apps.http.servers as Record;
+};
+
+test("keeps the Caddy admin API bound to localhost", () => {
+ const config = compileCaddyConfig();
+
+ expect(config.admin.listen).toBe("localhost:2019");
+});
+
+test("creates a Caddy dashboard route to the Dokploy container", () => {
+ const fragment = createCaddyDashboardRouteFragment(
+ settings(),
+ "dashboard.example.com",
+ );
+ const config = compileCaddyConfig({ fragments: [fragment] });
+ const route = getServers(config).http.routes[0];
+
+ expect(fragment).toMatchObject({
+ id: "dashboard.dokploy",
+ source: "dokploy-dashboard",
+ });
+ expect(route.match[0]).toMatchObject({
+ host: ["dashboard.example.com"],
+ });
+ expect(route.match[0].path).toBeUndefined();
+ expect(route.handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "dokploy:3000" }],
+ });
+});
+
+test("dashboard HTTPS route uses global ACME email when compiled", () => {
+ const fragment = createCaddyDashboardRouteFragment(
+ settings({ https: true, letsEncryptEmail: "ops@example.com" }),
+ "панель.example.com",
+ );
+ const config = compileCaddyConfig({
+ fragments: [fragment],
+ letsEncryptEmail: "ops@example.com",
+ });
+ const servers = getServers(config);
+
+ expect(fragment.routes[0]?.hosts).toEqual(["xn--80aksgi6f.example.com"]);
+ expect(servers.http.routes[0].handle[0]).toMatchObject({
+ handler: "static_response",
+ status_code: 308,
+ });
+ expect(servers.https.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ });
+ expect((config.apps as any).tls.automation.policies[0].issuers[0].email).toBe(
+ "ops@example.com",
+ );
+});
diff --git a/apps/dokploy/__test__/caddy/domain-modal-ui-contract.test.ts b/apps/dokploy/__test__/caddy/domain-modal-ui-contract.test.ts
new file mode 100644
index 0000000000..15292a69f3
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/domain-modal-ui-contract.test.ts
@@ -0,0 +1,110 @@
+import { readFileSync } from "node:fs";
+import { describe, expect, test } from "vitest";
+
+const readSource = (relativePath: string) =>
+ readFileSync(new URL(relativePath, import.meta.url), "utf8");
+
+const compact = (source: string) => source.replace(/\s+/g, " ");
+
+describe("Caddy domain modal UI contract", () => {
+ test("wires application domain tab and edit actions through AddDomain", () => {
+ const applicationPage = compact(
+ readSource(
+ "../../pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx",
+ ),
+ );
+ const showDomainsSource = readSource(
+ "../../components/dashboard/application/domains/show-domains.tsx",
+ );
+ const showDomains = compact(showDomainsSource);
+ const columns = compact(
+ readSource("../../components/dashboard/application/domains/columns.tsx"),
+ );
+
+ expect(applicationPage).toContain(
+ 'import { ShowDomains } from "@/components/dashboard/application/domains/show-domains";',
+ );
+ expect(applicationPage).toContain(' ',
+ );
+
+ expect(showDomains).toContain(
+ 'import { AddDomain } from "./handle-domain";',
+ );
+ expect(showDomains).toContain("");
+ expect(showDomainsSource).toMatch(
+ /",
+ );
+ });
+
+ test("resolves the active Caddy provider and scopes uploaded certificates", () => {
+ const source = readSource(
+ "../../components/dashboard/application/domains/handle-domain.tsx",
+ );
+ const normalized = compact(source);
+
+ expect(source).toMatch(
+ /api\.settings\.getActiveWebServerProvider\.useQuery\(\s*\{\s*serverId:\s*application\?\.serverId \|\| undefined\s*\},\s*\{\s*enabled:\s*!!application\s*\}/,
+ );
+ expect(normalized).toContain(
+ 'const isCaddyProvider = activeProvider === "caddy";',
+ );
+ expect(source).toMatch(
+ /api\.certificates\.all\.useQuery\(undefined,\s*\{\s*enabled:\s*isOpen && isCaddyProvider,\s*\}\)/,
+ );
+ expect(normalized).toContain(
+ "certificate.serverId === application.serverId",
+ );
+ expect(normalized).toContain(": !certificate.serverId");
+ });
+
+ test("renders Caddy certificate copy and submits uploaded certificate paths", () => {
+ const source = readSource(
+ "../../components/dashboard/application/domains/handle-domain.tsx",
+ );
+ const normalized = compact(source);
+
+ for (const expectedCopy of [
+ "This server uses Caddy",
+ "Caddy route fragments",
+ "Caddy can manage HTTPS",
+ "Let Caddy manage HTTPS automatically for this host.",
+ "Caddy-managed HTTPS (ACME)",
+ "Uploaded certificate",
+ "Uploaded Certificate",
+ "Select an uploaded certificate",
+ "Add an uploaded certificate for this server",
+ ]) {
+ expect(source).toContain(expectedCopy);
+ }
+
+ expect(normalized).toContain('name="customCertResolver"');
+ expect(normalized).toContain("key={certificate.certificateId}");
+ expect(normalized).toContain("value={certificate.certificatePath}");
+ });
+
+ test("keeps Traefik-only domain controls hidden when Caddy is active", () => {
+ const source = readSource(
+ "../../components/dashboard/application/domains/handle-domain.tsx",
+ );
+
+ expect(source).toMatch(
+ /\{!isCaddyProvider && \(\s* ({
+ assertCaddyDomainSupported: vi.fn(),
+ createComposeDomain: vi.fn(),
+ createDomain: vi.fn(),
+ findApplicationById: vi.fn(),
+ findComposeById: vi.fn(),
+ findDomainById: vi.fn(),
+ findDomainsByApplicationId: vi.fn(),
+ findDomainsByComposeId: vi.fn(),
+ findPreviewDeploymentById: vi.fn(),
+ findServerById: vi.fn(),
+ generateTraefikMeDomain: vi.fn(),
+ getWebServerSettings: vi.fn(),
+ manageWebServerDomain: vi.fn(),
+ refreshCaddyComposeRoutes: vi.fn(),
+ removeDomainById: vi.fn(),
+ removeWebServerDomain: vi.fn(),
+ resolveWebServerProvider: vi.fn(),
+ updateDomainById: vi.fn(),
+ validateDomain: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/services/permission", () => ({
+ checkServicePermissionAndAccess: vi.fn(),
+}));
+
+vi.mock("@/server/api/utils/audit", () => ({
+ audit: vi.fn(),
+}));
+
+import {
+ findApplicationById,
+ findComposeById,
+ findDomainById,
+ findDomainsByComposeId,
+ findPreviewDeploymentById,
+ manageWebServerDomain,
+ refreshCaddyComposeRoutes,
+ removeDomainById,
+ removeWebServerDomain,
+ resolveWebServerProvider,
+ updateDomainById,
+} from "@dokploy/server";
+import { domainRouter } from "@/server/api/routers/domain";
+
+const application = {
+ applicationId: "app-1",
+ appName: "my-app",
+ serverId: null,
+};
+
+const currentDomain = {
+ domainId: "domain-1",
+ applicationId: "app-1",
+ composeId: null,
+ previewDeploymentId: null,
+ domainType: "application",
+ host: "old.example.com",
+ path: "/",
+ internalPath: "/",
+ stripPath: false,
+ https: true,
+ certificateType: "letsencrypt",
+ customCertResolver: null,
+ customEntrypoint: null,
+ middlewares: null,
+ port: 3000,
+ serviceName: null,
+ uniqueConfigKey: 7,
+ createdAt: "",
+};
+
+const compose = {
+ composeId: "compose-1",
+ appName: "my-compose",
+ serverId: null,
+};
+
+const currentComposeDomain = {
+ ...currentDomain,
+ applicationId: null,
+ composeId: "compose-1",
+ domainType: "compose" as const,
+ serviceName: "web",
+};
+
+const siblingComposeDomain = {
+ ...currentComposeDomain,
+ domainId: "domain-2",
+ host: "sibling.example.com",
+ uniqueConfigKey: 8,
+};
+
+const previewDeployment = {
+ previewDeploymentId: "preview-1",
+ applicationId: "app-1",
+ appName: "my-app-pr-42",
+};
+
+const currentPreviewDomain = {
+ ...currentDomain,
+ applicationId: null,
+ previewDeploymentId: "preview-1",
+ domainType: "preview" as const,
+ host: "preview.example.com",
+};
+
+const updateInput = {
+ domainId: "domain-1",
+ domainType: "application" as const,
+ host: "new.example.com",
+ path: "/",
+ internalPath: "/",
+ stripPath: false,
+ https: true,
+ certificateType: "letsencrypt" as const,
+ customCertResolver: null,
+ customEntrypoint: null,
+ middlewares: null,
+ port: 3000,
+ serviceName: null,
+};
+
+const caller = domainRouter.createCaller({
+ session: {
+ userId: "user-1",
+ activeOrganizationId: "org-1",
+ },
+ user: {
+ id: "user-1",
+ role: "owner",
+ ownerId: "user-1",
+ email: "owner@example.com",
+ enableEnterpriseFeatures: true,
+ isValidEnterpriseLicense: true,
+ },
+ req: { headers: {} },
+ res: {},
+} as never);
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ vi.mocked(findDomainById).mockResolvedValue(currentDomain as never);
+ vi.mocked(findApplicationById).mockResolvedValue(application as never);
+ vi.mocked(findComposeById).mockResolvedValue(compose as never);
+ vi.mocked(findPreviewDeploymentById).mockResolvedValue(
+ previewDeployment as never,
+ );
+ vi.mocked(findDomainsByComposeId).mockResolvedValue([
+ currentComposeDomain,
+ siblingComposeDomain,
+ ] as never);
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ vi.mocked(manageWebServerDomain).mockResolvedValue(undefined as never);
+ vi.mocked(removeWebServerDomain).mockResolvedValue(undefined as never);
+ vi.mocked(refreshCaddyComposeRoutes).mockResolvedValue(undefined as never);
+});
+
+test("restores the previous Caddy application route when domain update persistence fails", async () => {
+ vi.mocked(updateDomainById).mockRejectedValueOnce(
+ new Error("db update failed") as never,
+ );
+
+ await expect(caller.update(updateInput)).rejects.toThrow("db update failed");
+
+ expect(manageWebServerDomain).toHaveBeenNthCalledWith(
+ 1,
+ application,
+ expect.objectContaining({
+ domainId: "domain-1",
+ host: "new.example.com",
+ }),
+ );
+ expect(manageWebServerDomain).toHaveBeenNthCalledWith(
+ 2,
+ application,
+ currentDomain,
+ );
+});
+
+test("preserves application domain rows when Caddy route removal fails before delete", async () => {
+ vi.mocked(removeWebServerDomain).mockRejectedValueOnce(
+ new Error("caddy route removal failed") as never,
+ );
+
+ await expect(caller.delete({ domainId: "domain-1" })).rejects.toThrow(
+ "caddy route removal failed",
+ );
+
+ expect(removeWebServerDomain).toHaveBeenCalledWith(application, 7);
+ expect(removeDomainById).not.toHaveBeenCalled();
+ expect(manageWebServerDomain).not.toHaveBeenCalled();
+});
+
+test("restores the removed Caddy application route when domain delete persistence fails", async () => {
+ vi.mocked(removeDomainById).mockRejectedValueOnce(
+ new Error("db delete failed") as never,
+ );
+
+ await expect(caller.delete({ domainId: "domain-1" })).rejects.toThrow(
+ "db delete failed",
+ );
+
+ expect(removeWebServerDomain).toHaveBeenCalledWith(application, 7);
+ expect(manageWebServerDomain).toHaveBeenCalledWith(
+ application,
+ currentDomain,
+ );
+});
+
+test("preserves preview domain rows when Caddy route removal fails before delete", async () => {
+ vi.mocked(findDomainById).mockResolvedValueOnce(
+ currentPreviewDomain as never,
+ );
+ vi.mocked(removeWebServerDomain).mockRejectedValueOnce(
+ new Error("preview caddy route removal failed") as never,
+ );
+
+ await expect(caller.delete({ domainId: "domain-1" })).rejects.toThrow(
+ "preview caddy route removal failed",
+ );
+
+ expect(findPreviewDeploymentById).toHaveBeenCalledWith("preview-1");
+ expect(removeWebServerDomain).toHaveBeenCalledWith(
+ expect.objectContaining({ appName: "my-app-pr-42" }),
+ 7,
+ );
+ expect(removeDomainById).not.toHaveBeenCalled();
+});
+
+test("restores previous compose domain fields when Caddy route refresh fails after update", async () => {
+ const updatedDomain = {
+ ...currentComposeDomain,
+ host: "new.example.com",
+ };
+ vi.mocked(findDomainById).mockResolvedValueOnce(
+ currentComposeDomain as never,
+ );
+ vi.mocked(updateDomainById)
+ .mockResolvedValueOnce(updatedDomain as never)
+ .mockResolvedValueOnce(currentComposeDomain as never);
+ vi.mocked(refreshCaddyComposeRoutes)
+ .mockRejectedValueOnce(new Error("caddy refresh failed") as never)
+ .mockResolvedValueOnce(undefined as never);
+
+ await expect(
+ caller.update({
+ ...updateInput,
+ domainType: "compose",
+ serviceName: "web",
+ }),
+ ).rejects.toThrow("caddy refresh failed");
+
+ expect(updateDomainById).toHaveBeenNthCalledWith(
+ 1,
+ "domain-1",
+ expect.objectContaining({
+ host: "new.example.com",
+ domainType: "compose",
+ serviceName: "web",
+ }),
+ );
+ expect(updateDomainById).toHaveBeenNthCalledWith(
+ 2,
+ "domain-1",
+ expect.objectContaining({
+ host: "old.example.com",
+ domainType: "compose",
+ serviceName: "web",
+ }),
+ );
+ expect(refreshCaddyComposeRoutes).toHaveBeenNthCalledWith(
+ 2,
+ compose,
+ undefined,
+ "caddy",
+ "org-1",
+ );
+});
+
+test("preserves compose domain rows when Caddy route refresh fails before delete", async () => {
+ vi.mocked(findDomainById).mockResolvedValueOnce(
+ currentComposeDomain as never,
+ );
+ vi.mocked(refreshCaddyComposeRoutes)
+ .mockRejectedValueOnce(new Error("caddy refresh failed") as never)
+ .mockResolvedValueOnce(undefined as never);
+
+ await expect(caller.delete({ domainId: "domain-1" })).rejects.toThrow(
+ "caddy refresh failed",
+ );
+
+ expect(refreshCaddyComposeRoutes).toHaveBeenNthCalledWith(
+ 1,
+ compose,
+ [siblingComposeDomain],
+ "caddy",
+ "org-1",
+ );
+ expect(refreshCaddyComposeRoutes).toHaveBeenNthCalledWith(
+ 2,
+ compose,
+ undefined,
+ "caddy",
+ "org-1",
+ );
+ expect(removeDomainById).not.toHaveBeenCalled();
+});
+
+test("restores all compose routes when compose domain delete persistence fails", async () => {
+ vi.mocked(findDomainById).mockResolvedValueOnce(
+ currentComposeDomain as never,
+ );
+ vi.mocked(removeDomainById).mockRejectedValueOnce(
+ new Error("db delete failed") as never,
+ );
+
+ await expect(caller.delete({ domainId: "domain-1" })).rejects.toThrow(
+ "db delete failed",
+ );
+
+ expect(refreshCaddyComposeRoutes).toHaveBeenNthCalledWith(
+ 1,
+ compose,
+ [siblingComposeDomain],
+ "caddy",
+ "org-1",
+ );
+ expect(refreshCaddyComposeRoutes).toHaveBeenNthCalledWith(
+ 2,
+ compose,
+ undefined,
+ "caddy",
+ "org-1",
+ );
+});
diff --git a/apps/dokploy/__test__/caddy/domain-validation.test.ts b/apps/dokploy/__test__/caddy/domain-validation.test.ts
new file mode 100644
index 0000000000..1d314014c0
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/domain-validation.test.ts
@@ -0,0 +1,169 @@
+import {
+ apiCreateDomain,
+ apiUpdateDomain,
+} from "@dokploy/server/db/schema/domain";
+import { domain, domainCompose } from "@dokploy/server/db/validations/domain";
+import { describe, expect, test } from "vitest";
+
+describe("domain validation", () => {
+ test("does not require a custom certificate resolver when HTTPS is disabled", () => {
+ expect(
+ domain.safeParse({
+ host: "example.com",
+ https: false,
+ certificateType: "none",
+ }).success,
+ ).toBe(true);
+
+ expect(
+ domainCompose.safeParse({
+ host: "example.com",
+ https: false,
+ certificateType: "none",
+ serviceName: "web",
+ }).success,
+ ).toBe(true);
+ });
+
+ test("requires a custom certificate resolver for HTTPS custom certificates", () => {
+ const result = domain.safeParse({
+ host: "example.com",
+ https: true,
+ certificateType: "custom",
+ });
+
+ expect(result.success).toBe(false);
+ if (!result.success) {
+ expect(result.error.issues).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ path: ["customCertResolver"],
+ message: "Required when certificate type is custom",
+ }),
+ ]),
+ );
+ }
+ });
+
+ const baseApplicationDomain = {
+ applicationId: "app-1",
+ domainType: "application" as const,
+ host: "app.example.com",
+ path: "/",
+ internalPath: "/",
+ stripPath: false,
+ port: 3000,
+ middlewares: [] as string[],
+ };
+
+ test("accepts application domain create payloads used by the Caddy modal", () => {
+ expect(
+ apiCreateDomain.parse({
+ ...baseApplicationDomain,
+ https: true,
+ certificateType: "letsencrypt",
+ }),
+ ).toMatchObject({
+ applicationId: "app-1",
+ domainType: "application",
+ https: true,
+ certificateType: "letsencrypt",
+ });
+
+ expect(
+ apiCreateDomain.parse({
+ ...baseApplicationDomain,
+ https: true,
+ certificateType: "custom",
+ customCertResolver: "local-uploaded-cert-path",
+ }),
+ ).toMatchObject({
+ customCertResolver: "local-uploaded-cert-path",
+ certificateType: "custom",
+ });
+
+ expect(
+ apiCreateDomain.parse({
+ ...baseApplicationDomain,
+ https: false,
+ certificateType: "none",
+ }),
+ ).toMatchObject({
+ https: false,
+ certificateType: "none",
+ });
+ });
+
+ test("accepts application domain update payloads used by the Caddy modal", () => {
+ const baseUpdate = {
+ ...baseApplicationDomain,
+ domainId: "domain-1",
+ };
+
+ expect(
+ apiUpdateDomain.parse({
+ ...baseUpdate,
+ https: true,
+ certificateType: "letsencrypt",
+ }),
+ ).toMatchObject({
+ domainId: "domain-1",
+ domainType: "application",
+ https: true,
+ certificateType: "letsencrypt",
+ });
+
+ expect(
+ apiUpdateDomain.parse({
+ ...baseUpdate,
+ https: true,
+ certificateType: "custom",
+ customCertResolver: "local-uploaded-cert-path",
+ }),
+ ).toMatchObject({
+ domainId: "domain-1",
+ customCertResolver: "local-uploaded-cert-path",
+ certificateType: "custom",
+ });
+
+ expect(
+ apiUpdateDomain.parse({
+ ...baseUpdate,
+ https: false,
+ certificateType: "none",
+ }),
+ ).toMatchObject({
+ domainId: "domain-1",
+ https: false,
+ certificateType: "none",
+ });
+ });
+
+ test("rejects Caddy custom certificate payloads without an uploaded certificate reference", () => {
+ const createResult = apiCreateDomain.safeParse({
+ ...baseApplicationDomain,
+ https: true,
+ certificateType: "custom",
+ });
+ const updateResult = apiUpdateDomain.safeParse({
+ ...baseApplicationDomain,
+ domainId: "domain-1",
+ https: true,
+ certificateType: "custom",
+ });
+
+ for (const result of [createResult, updateResult]) {
+ expect(result.success).toBe(false);
+ if (!result.success) {
+ expect(result.error.issues).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ path: ["customCertResolver"],
+ message: "Required when certificate type is custom",
+ }),
+ ]),
+ );
+ }
+ }
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/migration/apply-rollback.test.ts b/apps/dokploy/__test__/caddy/migration/apply-rollback.test.ts
new file mode 100644
index 0000000000..0e87695167
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/apply-rollback.test.ts
@@ -0,0 +1,700 @@
+import { fs, vol } from "memfs";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+vi.mock("node:fs", () => ({
+ ...fs,
+ default: fs,
+}));
+
+vi.mock("@dokploy/server/services/settings", () => ({
+ getDockerResourceSnapshot: vi.fn(),
+ readEnvironmentVariables: vi.fn(),
+ readPorts: vi.fn(),
+ stopDockerResource: vi.fn(),
+ startDockerResourceFromSnapshot: vi.fn(),
+ ensureTraefikRunningFromSnapshot: vi.fn(),
+ waitForDockerResourceRunning: vi.fn(),
+ writeCaddySetup: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/services/web-server-settings", () => ({
+ getCaddyCompileSettings: vi.fn(),
+ resolveWebServerProvider: vi.fn(),
+ updateLocalWebServerProvider: vi.fn(),
+ updateRemoteWebServerProvider: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/utils/caddy/config", () => ({
+ reloadCaddyAfterValidation: vi.fn(),
+ validateCaddyConfigFileWithImage: vi.fn(),
+ validateCaddyConfigWithContainer: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/utils/caddy/migration/upstream-preflight", () => ({
+ runCaddyMigrationUpstreamPreflight: vi.fn(),
+}));
+
+import { paths } from "@dokploy/server/constants";
+import * as settingsService from "@dokploy/server/services/settings";
+import * as providerService from "@dokploy/server/services/web-server-settings";
+import * as caddyConfig from "@dokploy/server/utils/caddy/config";
+import { applyCaddyMigration } from "@dokploy/server/utils/caddy/migration/apply";
+import { getCaddyMigrationArtifactPaths } from "@dokploy/server/utils/caddy/migration/files";
+import { rollbackCaddyMigration } from "@dokploy/server/utils/caddy/migration/rollback";
+import type { CaddyMigrationReport } from "@dokploy/server/utils/caddy/migration/types";
+import * as upstreamPreflight from "@dokploy/server/utils/caddy/migration/upstream-preflight";
+
+const createReport = (migrationId: string): CaddyMigrationReport => {
+ const artifactPaths = getCaddyMigrationArtifactPaths(migrationId);
+ return {
+ migrationId,
+ serverId: null,
+ createdAt: "2026-05-22T00:00:00.000Z",
+ updatedAt: "2026-05-22T00:00:00.000Z",
+ status: "prepared",
+ sourceProvider: "traefik",
+ targetProvider: "caddy",
+ artifactPaths,
+ inputs: {
+ traefikStaticConfigPath: `${paths().MAIN_TRAEFIK_PATH}/traefik.yml`,
+ traefikStaticConfigFound: true,
+ dynamicFiles: [],
+ dbApplicationDomains: 0,
+ dbComposeDomains: 0,
+ composeFilesScanned: [],
+ composeFilesSkipped: [],
+ },
+ summary: {
+ fragments: 1,
+ routes: 1,
+ warnings: 0,
+ blockingWarnings: 0,
+ },
+ validation: { status: "passed", message: "ok" },
+ compileSettings: {
+ letsEncryptEmail: null,
+ trustedProxies: null,
+ },
+ warnings: [],
+ events: [],
+ };
+};
+
+const seedMigration = (migrationId: string) => {
+ const report = createReport(migrationId);
+ const currentPaths = paths();
+ vol.mkdirSync(report.artifactPaths.fragmentsDir, { recursive: true });
+ vol.writeFileSync(
+ `${report.artifactPaths.fragmentsDir}/app.json`,
+ JSON.stringify({ version: 1, id: "app", source: "manual", routes: [] }),
+ );
+ vol.writeFileSync(report.artifactPaths.caddyJson, '{"apps":{"http":{}}}\n');
+ vol.writeFileSync(
+ report.artifactPaths.reportJson,
+ `${JSON.stringify(report, null, 2)}\n`,
+ );
+ vol.mkdirSync(currentPaths.MAIN_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.MAIN_TRAEFIK_PATH}/traefik.yml`,
+ "entryPoints: {}\n",
+ );
+ vol.mkdirSync(currentPaths.DYNAMIC_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.DYNAMIC_TRAEFIK_PATH}/app.yml`,
+ "http: {}\n",
+ );
+ vol.mkdirSync(currentPaths.CADDY_FRAGMENTS_PATH, { recursive: true });
+ vol.writeFileSync(`${currentPaths.CADDY_FRAGMENTS_PATH}/old.json`, "{}\n");
+ vol.writeFileSync(currentPaths.CADDY_CONFIG_PATH, '{"old":true}\n');
+ return report;
+};
+
+describe("applyCaddyMigration", () => {
+ beforeEach(() => {
+ vol.reset();
+ vi.clearAllMocks();
+ vi.mocked(providerService.resolveWebServerProvider).mockResolvedValue(
+ "traefik",
+ );
+ vi.mocked(providerService.getCaddyCompileSettings).mockResolvedValue({
+ trustedProxies: null,
+ });
+ vi.mocked(settingsService.getDockerResourceSnapshot).mockImplementation(
+ async (resourceName: string) => ({
+ resourceName,
+ resourceType:
+ resourceName === "dokploy-traefik" ? "standalone" : "unknown",
+ running: resourceName === "dokploy-traefik",
+ }),
+ );
+ vi.mocked(settingsService.readEnvironmentVariables).mockResolvedValue("");
+ vi.mocked(settingsService.readPorts).mockResolvedValue([]);
+ vi.mocked(settingsService.waitForDockerResourceRunning).mockResolvedValue({
+ resourceName: "dokploy-caddy",
+ resourceType: "standalone",
+ running: true,
+ });
+ vi.mocked(
+ settingsService.ensureTraefikRunningFromSnapshot,
+ ).mockResolvedValue(undefined);
+ vi.mocked(caddyConfig.reloadCaddyAfterValidation).mockResolvedValue(
+ {} as any,
+ );
+ vi.mocked(caddyConfig.validateCaddyConfigFileWithImage).mockResolvedValue(
+ {} as any,
+ );
+ vi.mocked(caddyConfig.validateCaddyConfigWithContainer).mockResolvedValue(
+ {} as any,
+ );
+ vi.mocked(
+ upstreamPreflight.runCaddyMigrationUpstreamPreflight,
+ ).mockResolvedValue({
+ status: "passed",
+ checkedAt: "2026-05-22T00:00:00.000Z",
+ network: "dokploy-network",
+ probeImage: "busybox:1.36",
+ checks: [],
+ });
+ });
+
+ test("rejects a concurrent migration operation while a lock exists", async () => {
+ const report = seedMigration("caddy-apply-locked");
+ vol.mkdirSync(`${report.artifactPaths.root}/.operation.lock`);
+
+ await expect(
+ applyCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("already has an apply or rollback operation in progress");
+ expect(
+ upstreamPreflight.runCaddyMigrationUpstreamPreflight,
+ ).not.toHaveBeenCalled();
+ });
+
+ test("writes approved artifacts, starts Caddy, validates, then updates provider", async () => {
+ const report = seedMigration("caddy-apply-success");
+ vi.mocked(settingsService.readPorts).mockImplementation(
+ async (resourceName: string) =>
+ resourceName === "dokploy-traefik"
+ ? [
+ { targetPort: 8080, publishedPort: 8080, protocol: "tcp" },
+ { targetPort: 8082, publishedPort: 8082, protocol: "tcp" },
+ { targetPort: 2019, publishedPort: 2019, protocol: "tcp" },
+ { targetPort: 9000, publishedPort: 9000, protocol: "tcp" },
+ ]
+ : [],
+ );
+
+ const applied = await applyCaddyMigration({
+ migrationId: report.migrationId,
+ });
+
+ expect(applied.status).toBe("applied");
+ expect(settingsService.stopDockerResource).toHaveBeenCalledWith(
+ "dokploy-traefik",
+ undefined,
+ );
+ expect(
+ vi.mocked(upstreamPreflight.runCaddyMigrationUpstreamPreflight).mock
+ .invocationCallOrder[0],
+ ).toBeLessThan(
+ vi.mocked(settingsService.stopDockerResource).mock
+ .invocationCallOrder[0] ?? 0,
+ );
+ expect(caddyConfig.validateCaddyConfigFileWithImage).toHaveBeenCalledWith(
+ report.artifactPaths.caddyJson,
+ undefined,
+ );
+ expect(
+ vi.mocked(caddyConfig.validateCaddyConfigFileWithImage).mock
+ .invocationCallOrder[0],
+ ).toBeLessThan(
+ vi.mocked(settingsService.stopDockerResource).mock
+ .invocationCallOrder[0] ?? 0,
+ );
+ expect(settingsService.writeCaddySetup).toHaveBeenCalledWith(
+ expect.objectContaining({
+ additionalPorts: [
+ { targetPort: 9000, publishedPort: 9000, protocol: "tcp" },
+ ],
+ }),
+ );
+ expect(caddyConfig.validateCaddyConfigWithContainer).toHaveBeenCalled();
+ expect(providerService.updateLocalWebServerProvider).toHaveBeenCalledWith(
+ "caddy",
+ );
+ expect(
+ vi.mocked(caddyConfig.validateCaddyConfigWithContainer).mock
+ .invocationCallOrder[0],
+ ).toBeLessThan(
+ vi.mocked(providerService.updateLocalWebServerProvider).mock
+ .invocationCallOrder[0] ?? 0,
+ );
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ '{"apps":{"http":{}}}\n',
+ );
+ expect(vol.existsSync(`${paths().CADDY_FRAGMENTS_PATH}/app.json`)).toBe(
+ true,
+ );
+ });
+
+ test("applies and rolls back approved Caddy custom certificate artifacts", async () => {
+ const report = seedMigration("caddy-custom-cert-apply-rollback");
+ const certificatePath = `${paths().CERTIFICATES_PATH}/certificate-uploaded`;
+ const loadFiles = [
+ {
+ certificate: `${certificatePath}/chain.crt`,
+ key: `${certificatePath}/privkey.key`,
+ },
+ ];
+ vol.writeFileSync(
+ report.artifactPaths.caddyJson,
+ `${JSON.stringify(
+ {
+ apps: {
+ http: {},
+ tls: {
+ certificates: {
+ load_files: loadFiles,
+ },
+ },
+ },
+ },
+ null,
+ 2,
+ )}\n`,
+ );
+ vi.mocked(
+ caddyConfig.validateCaddyConfigFileWithImage,
+ ).mockImplementationOnce(async (filePath: string) => {
+ const validatedConfig = JSON.parse(
+ vol.readFileSync(filePath, "utf8") as string,
+ );
+ expect(validatedConfig.apps.tls.certificates.load_files).toEqual(
+ loadFiles,
+ );
+ return {} as any;
+ });
+
+ const applied = await applyCaddyMigration({
+ migrationId: report.migrationId,
+ });
+
+ expect(applied.status).toBe("applied");
+ expect(caddyConfig.validateCaddyConfigFileWithImage).toHaveBeenCalledWith(
+ report.artifactPaths.caddyJson,
+ undefined,
+ );
+ const activeConfig = JSON.parse(
+ vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8") as string,
+ );
+ expect(activeConfig.apps.tls.certificates.load_files).toEqual(loadFiles);
+
+ const rolledBack = await rollbackCaddyMigration({
+ migrationId: report.migrationId,
+ });
+
+ expect(rolledBack.status).toBe("rolled_back");
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ '{"old":true}\n',
+ );
+ expect(vol.existsSync(`${paths().CADDY_FRAGMENTS_PATH}/old.json`)).toBe(
+ true,
+ );
+ expect(providerService.updateLocalWebServerProvider).toHaveBeenCalledWith(
+ "traefik",
+ );
+ });
+
+ test("rejects apply when Caddy compile settings changed after prepare", async () => {
+ const report = seedMigration("caddy-apply-stale-settings");
+ vi.mocked(providerService.getCaddyCompileSettings).mockResolvedValueOnce({
+ trustedProxies: {
+ source: "cloudflare",
+ },
+ });
+
+ await expect(
+ applyCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("Caddy compile settings changed after prepare");
+
+ expect(
+ upstreamPreflight.runCaddyMigrationUpstreamPreflight,
+ ).not.toHaveBeenCalled();
+ expect(settingsService.stopDockerResource).not.toHaveBeenCalled();
+ expect(settingsService.writeCaddySetup).not.toHaveBeenCalled();
+ const failedReport = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ ) as CaddyMigrationReport;
+ expect(failedReport.status).toBe("failed");
+ expect(failedReport.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "apply-failed",
+ message: expect.stringContaining("Caddy compile settings changed"),
+ }),
+ ]),
+ );
+ });
+
+ test("rejects apply when Caddy request-log compile settings changed after prepare", async () => {
+ const report = seedMigration("caddy-apply-stale-request-logs");
+ vi.mocked(providerService.getCaddyCompileSettings).mockResolvedValueOnce({
+ trustedProxies: null,
+ accessLogs: { enabled: true },
+ });
+
+ await expect(
+ applyCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("Caddy compile settings changed after prepare");
+
+ expect(
+ upstreamPreflight.runCaddyMigrationUpstreamPreflight,
+ ).not.toHaveBeenCalled();
+ expect(settingsService.stopDockerResource).not.toHaveBeenCalled();
+ expect(settingsService.writeCaddySetup).not.toHaveBeenCalled();
+ const failedReport = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ ) as CaddyMigrationReport;
+ expect(failedReport.status).toBe("failed");
+ expect(failedReport.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "apply-failed",
+ message: expect.stringContaining("Caddy compile settings changed"),
+ }),
+ ]),
+ );
+ });
+
+ test("rewrites the mounted Caddy config file in place after setup", async () => {
+ const report = seedMigration("caddy-apply-preserves-config-inode");
+ let mountedConfigFd: number | undefined;
+ vi.mocked(settingsService.writeCaddySetup).mockImplementationOnce(
+ async () => {
+ vol.writeFileSync(
+ paths().CADDY_CONFIG_PATH,
+ '{"generatedBySetup":true}\n',
+ );
+ mountedConfigFd = fs.openSync(paths().CADDY_CONFIG_PATH, "r");
+ },
+ );
+
+ await applyCaddyMigration({
+ migrationId: report.migrationId,
+ });
+
+ expect(mountedConfigFd).toBeDefined();
+ const mountedContent = Buffer.alloc(1024);
+ const bytesRead = fs.readSync(
+ mountedConfigFd as number,
+ mountedContent,
+ 0,
+ mountedContent.length,
+ 0,
+ );
+ fs.closeSync(mountedConfigFd as number);
+ expect(mountedContent.toString("utf8", 0, bytesRead)).toBe(
+ '{"apps":{"http":{}}}\n',
+ );
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ '{"apps":{"http":{}}}\n',
+ );
+ });
+
+ test("runtime upstream preflight failure stops apply before Traefik is stopped", async () => {
+ const report = seedMigration("caddy-preflight-fails");
+ vi.mocked(
+ upstreamPreflight.runCaddyMigrationUpstreamPreflight,
+ ).mockResolvedValueOnce({
+ status: "failed",
+ checkedAt: "2026-05-22T00:00:00.000Z",
+ network: "dokploy-network",
+ probeImage: "busybox:1.36",
+ checks: [
+ {
+ dial: "missing:3000",
+ host: "missing",
+ port: 3000,
+ network: "dokploy-network",
+ status: "failed",
+ reason: "DNS resolution failed",
+ routes: [],
+ },
+ ],
+ });
+
+ await expect(
+ applyCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("Runtime upstream preflight failed");
+
+ expect(settingsService.stopDockerResource).not.toHaveBeenCalledWith(
+ "dokploy-traefik",
+ undefined,
+ );
+ expect(settingsService.writeCaddySetup).not.toHaveBeenCalled();
+ expect(
+ settingsService.ensureTraefikRunningFromSnapshot,
+ ).not.toHaveBeenCalled();
+ const finalReport = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ ) as CaddyMigrationReport;
+ expect(finalReport.status).toBe("failed");
+ expect(finalReport.runtimePreflight?.status).toBe("failed");
+ });
+
+ test("rolls back to Traefik and keeps provider Traefik when Caddy setup fails", async () => {
+ const report = seedMigration("caddy-apply-fails");
+ vi.mocked(settingsService.writeCaddySetup).mockRejectedValueOnce(
+ new Error("caddy failed"),
+ );
+
+ await expect(
+ applyCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("caddy failed");
+
+ expect(
+ providerService.updateLocalWebServerProvider,
+ ).not.toHaveBeenCalledWith("caddy");
+ expect(providerService.updateLocalWebServerProvider).toHaveBeenCalledWith(
+ "traefik",
+ );
+ expect(settingsService.stopDockerResource).toHaveBeenCalledWith(
+ "dokploy-caddy",
+ undefined,
+ );
+ expect(
+ settingsService.ensureTraefikRunningFromSnapshot,
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({ resourceName: "dokploy-traefik" }),
+ undefined,
+ );
+ const finalReport = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ ) as CaddyMigrationReport;
+ expect(finalReport.status).toBe("rolled_back");
+ });
+
+ test("uses restore-only unredacted resource snapshot during rollback", async () => {
+ const report = seedMigration("caddy-restore-snapshot");
+ vi.mocked(settingsService.getDockerResourceSnapshot).mockImplementation(
+ async (resourceName: string) => ({
+ resourceName,
+ resourceType:
+ resourceName === "dokploy-traefik" ? "standalone" : "unknown",
+ running: resourceName === "dokploy-traefik",
+ env: resourceName === "dokploy-traefik" ? "SECRET=value" : undefined,
+ image:
+ resourceName === "dokploy-traefik"
+ ? "private.registry.example/traefik:latest"
+ : undefined,
+ binds:
+ resourceName === "dokploy-traefik"
+ ? ["/etc/dokploy/traefik/acme.json:/letsencrypt/acme.json"]
+ : undefined,
+ labels:
+ resourceName === "dokploy-traefik"
+ ? { "secret.label": "private" }
+ : undefined,
+ }),
+ );
+ vi.mocked(settingsService.writeCaddySetup).mockRejectedValueOnce(
+ new Error("caddy failed"),
+ );
+
+ await expect(
+ applyCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("caddy failed");
+
+ const finalReport = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ ) as CaddyMigrationReport;
+ expect(finalReport.backup?.traefikResource?.env).toBeUndefined();
+ expect(finalReport.backup?.traefikResource?.binds).toBeUndefined();
+ expect(finalReport.backup?.traefikResource?.labels).toBeUndefined();
+ expect(finalReport.backup?.traefikResource?.image).toBeUndefined();
+ expect(finalReport.backup?.traefikResource).toMatchObject({
+ resourceName: "dokploy-traefik",
+ resourceType: "standalone",
+ running: true,
+ });
+ expect(finalReport.backup?.restoreSnapshotPath).toBeTruthy();
+ const restoreSnapshots = JSON.parse(
+ vol.readFileSync(
+ finalReport.backup?.restoreSnapshotPath ?? "",
+ "utf8",
+ ) as string,
+ ) as { traefikResource: { env?: string; binds?: string[] } };
+ expect(restoreSnapshots.traefikResource.env).toBe("SECRET=value");
+ expect(restoreSnapshots.traefikResource.binds).toEqual([
+ "/etc/dokploy/traefik/acme.json:/letsencrypt/acme.json",
+ ]);
+ expect(
+ settingsService.ensureTraefikRunningFromSnapshot,
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({
+ env: "SECRET=value",
+ binds: ["/etc/dokploy/traefik/acme.json:/letsencrypt/acme.json"],
+ }),
+ undefined,
+ );
+ });
+
+ test("rollback failure leaves provider unchanged and writes failed report", async () => {
+ const report = seedMigration("caddy-rollback-traefik-fails");
+ vi.mocked(settingsService.writeCaddySetup).mockRejectedValueOnce(
+ new Error("caddy failed"),
+ );
+ vi.mocked(
+ settingsService.ensureTraefikRunningFromSnapshot,
+ ).mockRejectedValueOnce(new Error("traefik missing"));
+
+ await expect(
+ applyCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("caddy failed");
+
+ expect(
+ providerService.updateLocalWebServerProvider,
+ ).not.toHaveBeenCalledWith("traefik");
+ const finalReport = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ ) as CaddyMigrationReport;
+ expect(finalReport.status).toBe("failed");
+ expect(finalReport.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "rollback-failed",
+ message: "traefik missing",
+ blocking: true,
+ }),
+ ]),
+ );
+ });
+
+ test("restores backed-up Caddy files when post-start validation fails", async () => {
+ const report = seedMigration("caddy-validation-fails");
+ vi.mocked(
+ caddyConfig.validateCaddyConfigWithContainer,
+ ).mockRejectedValueOnce(new Error("validation failed"));
+
+ await expect(
+ applyCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("validation failed");
+
+ expect(
+ providerService.updateLocalWebServerProvider,
+ ).not.toHaveBeenCalledWith("caddy");
+ expect(providerService.updateLocalWebServerProvider).toHaveBeenCalledWith(
+ "traefik",
+ );
+ expect(vol.readFileSync(paths().CADDY_CONFIG_PATH, "utf8")).toBe(
+ '{"old":true}\n',
+ );
+ expect(vol.existsSync(`${paths().CADDY_FRAGMENTS_PATH}/old.json`)).toBe(
+ true,
+ );
+ expect(vol.existsSync(`${paths().CADDY_FRAGMENTS_PATH}/app.json`)).toBe(
+ false,
+ );
+
+ const finalReport = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ ) as CaddyMigrationReport;
+ expect(finalReport.status).toBe("rolled_back");
+ });
+
+ test("fails closed when an expected rollback backup path is missing", async () => {
+ const report = seedMigration("caddy-missing-traefik-backup");
+ const traefikConfigPath = `${paths().MAIN_TRAEFIK_PATH}/traefik.yml`;
+ vol.writeFileSync(
+ report.artifactPaths.reportJson,
+ `${JSON.stringify(
+ {
+ ...report,
+ status: "applied",
+ backup: {
+ createdAt: "2026-05-22T00:00:00.000Z",
+ traefikResource: {
+ resourceName: "dokploy-traefik",
+ resourceType: "standalone",
+ running: true,
+ },
+ files: [
+ {
+ label: "traefik-static",
+ source: traefikConfigPath,
+ backupPath: `${report.artifactPaths.backupsDir}/missing-traefik.yml`,
+ existed: true,
+ },
+ ],
+ },
+ },
+ null,
+ 2,
+ )}\n`,
+ );
+
+ await expect(
+ rollbackCaddyMigration({ migrationId: report.migrationId }),
+ ).rejects.toThrow("backup path is missing");
+
+ expect(vol.readFileSync(traefikConfigPath, "utf8")).toBe(
+ "entryPoints: {}\n",
+ );
+ expect(
+ settingsService.ensureTraefikRunningFromSnapshot,
+ ).not.toHaveBeenCalled();
+ expect(
+ providerService.updateLocalWebServerProvider,
+ ).not.toHaveBeenCalledWith("traefik");
+
+ const finalReport = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ ) as CaddyMigrationReport;
+ expect(finalReport.status).toBe("failed");
+ expect(finalReport.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "rollback-failed",
+ blocking: true,
+ }),
+ ]),
+ );
+ });
+
+ test("rolls back older reports that have backup metadata without file entries", async () => {
+ const report = seedMigration("caddy-old-backup-report");
+ vol.writeFileSync(
+ report.artifactPaths.reportJson,
+ `${JSON.stringify(
+ {
+ ...report,
+ status: "applied",
+ backup: {
+ createdAt: "2026-05-22T00:00:00.000Z",
+ traefikResource: {
+ resourceName: "dokploy-traefik",
+ resourceType: "standalone",
+ running: true,
+ },
+ },
+ },
+ null,
+ 2,
+ )}\n`,
+ );
+
+ const rolledBack = await rollbackCaddyMigration({
+ migrationId: report.migrationId,
+ });
+
+ expect(rolledBack.status).toBe("rolled_back");
+ expect(
+ settingsService.ensureTraefikRunningFromSnapshot,
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({ resourceName: "dokploy-traefik" }),
+ undefined,
+ );
+ expect(providerService.updateLocalWebServerProvider).toHaveBeenCalledWith(
+ "traefik",
+ );
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/migration/caddy-migration-rollback-cli.test.ts b/apps/dokploy/__test__/caddy/migration/caddy-migration-rollback-cli.test.ts
new file mode 100644
index 0000000000..49a301ab44
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/caddy-migration-rollback-cli.test.ts
@@ -0,0 +1,95 @@
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+const rollbackCaddyMigrationMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server", () => ({
+ rollbackCaddyMigration: rollbackCaddyMigrationMock,
+}));
+
+import {
+ parseCaddyRollbackArgs,
+ runCaddyMigrationRollbackCli,
+} from "../../../scripts/caddy-migration-rollback";
+
+const createIo = () => ({
+ stdout: { write: vi.fn() },
+ stderr: { write: vi.fn() },
+});
+
+describe("caddy migration rollback CLI", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ test("requires a migration id", async () => {
+ expect(() => parseCaddyRollbackArgs([])).toThrow("Missing --migration-id");
+
+ const io = createIo();
+ const code = await runCaddyMigrationRollbackCli([], io);
+
+ expect(code).toBe(1);
+ expect(rollbackCaddyMigrationMock).not.toHaveBeenCalled();
+ expect(io.stderr.write).toHaveBeenCalledWith(
+ expect.stringContaining("Missing --migration-id"),
+ );
+ });
+
+ test("prints help and exits zero without invoking rollback", async () => {
+ const io = createIo();
+
+ const code = await runCaddyMigrationRollbackCli(["--help"], io);
+
+ expect(code).toBe(0);
+ expect(rollbackCaddyMigrationMock).not.toHaveBeenCalled();
+ expect(io.stdout.write).toHaveBeenCalledWith(
+ expect.stringContaining("Usage: caddy-migration-rollback"),
+ );
+ expect(io.stderr.write).not.toHaveBeenCalled();
+ });
+
+ test("awaits rollback and exits zero for rolled_back reports", async () => {
+ rollbackCaddyMigrationMock.mockResolvedValueOnce({
+ migrationId: "caddy-123",
+ status: "rolled_back",
+ warnings: [],
+ summary: { warnings: 0, blockingWarnings: 0, fragments: 1, routes: 1 },
+ artifactPaths: { reportJson: "/tmp/report.json" },
+ });
+ const io = createIo();
+
+ const code = await runCaddyMigrationRollbackCli(
+ ["--migration-id", "caddy-123", "--server-id", "server-1"],
+ io,
+ );
+
+ expect(code).toBe(0);
+ expect(rollbackCaddyMigrationMock).toHaveBeenCalledWith({
+ migrationId: "caddy-123",
+ serverId: "server-1",
+ });
+ expect(io.stdout.write).toHaveBeenCalledWith(
+ expect.stringContaining('"status": "rolled_back"'),
+ );
+ });
+
+ test("exits non-zero for non-rolled-back terminal reports", async () => {
+ rollbackCaddyMigrationMock.mockResolvedValueOnce({
+ migrationId: "caddy-123",
+ status: "failed",
+ warnings: [],
+ summary: { warnings: 1, blockingWarnings: 1, fragments: 1, routes: 1 },
+ artifactPaths: { reportJson: "/tmp/report.json" },
+ });
+ const io = createIo();
+
+ const code = await runCaddyMigrationRollbackCli(
+ ["--migration-id", "caddy-123"],
+ io,
+ );
+
+ expect(code).toBe(1);
+ expect(io.stdout.write).toHaveBeenCalledWith(
+ expect.stringContaining('"status": "failed"'),
+ );
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/migration/compose-label-translator.test.ts b/apps/dokploy/__test__/caddy/migration/compose-label-translator.test.ts
new file mode 100644
index 0000000000..4360b8e6d5
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/compose-label-translator.test.ts
@@ -0,0 +1,329 @@
+import { readFileSync } from "node:fs";
+import path from "node:path";
+import {
+ compileCaddyConfig,
+ translateTraefikComposeLabelsToCaddyFragment,
+} from "@dokploy/server";
+import { describe, expect, test } from "vitest";
+import { parse } from "yaml";
+
+const fixture = (name: string) =>
+ parse(readFileSync(path.join(__dirname, "fixtures", name), "utf8")) as any;
+
+const getServers = (config: ReturnType) => {
+ const apps = config.apps as Record;
+ return apps.http.servers as Record;
+};
+
+const composeLabels = (serviceName: string) =>
+ fixture("generic-compose-labels.yml").services[serviceName].deploy?.labels ??
+ fixture("generic-compose-labels.yml").services[serviceName].labels;
+
+describe("Traefik compose labels to Caddy migration", () => {
+ test("translates representative Dokploy-generated HTTP/HTTPS label pairs", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ composeLabels("frontend"),
+ {
+ sourceFile: "compose-sample-stack/docker-compose.yml",
+ appName: "compose-sample-stack",
+ serviceName: "frontend",
+ upstreamServiceName: "frontend",
+ },
+ );
+ const config = compileCaddyConfig({ fragments: [result.fragment] });
+ const servers = getServers(config);
+
+ expect(result.warnings).toEqual([]);
+ expect(result.classifications.some((item) => item.dokployGenerated)).toBe(
+ true,
+ );
+
+ const secureRoute = result.routes.find((route) =>
+ route.id.includes("websecure"),
+ );
+ expect(secureRoute).toMatchObject({
+ source: "traefik-compose-label",
+ hosts: ["app.example.com"],
+ https: true,
+ upstreams: ["http://frontend:3000"],
+ });
+
+ const redirectRoute = result.routes.find((route) =>
+ route.id.endsWith("-web"),
+ );
+ expect(redirectRoute).toMatchObject({
+ redirectScheme: { scheme: "https", permanent: true },
+ upstreams: [],
+ });
+ expect(servers.https.routes[0].handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "frontend:3000" }],
+ });
+ });
+
+ test("translates manual compose labels with OR host rules and file middleware definitions", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ composeLabels("cms"),
+ {
+ sourceFile: "cms-site/docker-compose.yml",
+ serviceName: "cms",
+ upstreamServiceName: "cms",
+ fileMiddlewares: {
+ "cms-security-headers": {
+ headers: {
+ stsSeconds: 31536000,
+ stsIncludeSubdomains: true,
+ stsPreload: true,
+ referrerPolicy: "strict-origin-when-cross-origin",
+ contentTypeNosniff: true,
+ customFrameOptionsValue: "SAMEORIGIN",
+ },
+ },
+ },
+ },
+ );
+
+ const prodRoute = result.routes.find((route) =>
+ route.id.includes("cms-prod"),
+ );
+ expect(result.warnings).toEqual([]);
+ expect(prodRoute).toMatchObject({
+ hosts: ["example.com", "www.example.com"],
+ https: true,
+ upstreams: ["http://cms:8080"],
+ transforms: {
+ responseHeaders: {
+ "Strict-Transport-Security":
+ "max-age=31536000; includeSubDomains; preload",
+ "Referrer-Policy": "strict-origin-when-cross-origin",
+ "X-Content-Type-Options": "nosniff",
+ "X-Frame-Options": "SAMEORIGIN",
+ },
+ },
+ });
+ });
+
+ test("translates path-specific manual labels with priority and dynamic file middleware mapping", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ composeLabels("admin"),
+ {
+ sourceFile: "admin-console/docker-compose.yml",
+ serviceName: "admin",
+ upstreamServiceName: "admin",
+ fileMiddlewares: {
+ "admin-no-store": {
+ headers: {
+ customResponseHeaders: {
+ "Cache-Control": "private, no-store",
+ },
+ },
+ },
+ },
+ },
+ );
+
+ expect(result.warnings).toEqual([]);
+ const adminRoutes = result.routes.filter((route) =>
+ route.id.includes("admin-console"),
+ );
+ expect(adminRoutes).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ priority: 100,
+ pathPrefix: "/admin",
+ transforms: expect.objectContaining({
+ responseHeaders: expect.objectContaining({
+ "Cache-Control": "private, no-store",
+ }),
+ }),
+ }),
+ expect.objectContaining({
+ priority: 100,
+ pathExact: "/login",
+ }),
+ ]),
+ );
+ });
+
+ test("treats tls=true router labels as HTTPS intent", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ [
+ "traefik.http.routers.app.rule=Host(`tls.example.com`)",
+ "traefik.http.routers.app.entrypoints=websecure",
+ "traefik.http.routers.app.tls=true",
+ "traefik.http.routers.app.service=app",
+ "traefik.http.services.app.loadbalancer.server.port=3000",
+ ],
+ {
+ sourceFile: "tls/docker-compose.yml",
+ serviceName: "app",
+ upstreamServiceName: "app",
+ },
+ );
+
+ expect(result.warnings).toEqual([]);
+ expect(result.routes[0]).toMatchObject({
+ hosts: ["tls.example.com"],
+ https: true,
+ upstreams: ["http://app:3000"],
+ });
+ });
+
+ test("translates compose-label routes that reference ipAllowList file middleware", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ [
+ "traefik.http.routers.dash.rule=Host(`dash.example.com`)",
+ "traefik.http.routers.dash.entrypoints=websecure",
+ "traefik.http.routers.dash.tls.certresolver=letsencrypt",
+ "traefik.http.routers.dash.service=dash",
+ "traefik.http.routers.dash.middlewares=internal-allowlist@file",
+ "traefik.http.services.dash.loadbalancer.server.port=8000",
+ ],
+ {
+ sourceFile: "dash/docker-compose.yml",
+ serviceName: "dash",
+ upstreamServiceName: "dash",
+ fileMiddlewares: {
+ "internal-allowlist": {
+ ipAllowList: {
+ sourceRange: ["192.0.2.0/24"],
+ },
+ } as any,
+ },
+ },
+ );
+
+ expect(result.warnings).toEqual([]);
+ expect(result.routes[0]).toMatchObject({
+ allowedRemoteIps: ["192.0.2.0/24"],
+ });
+ });
+
+ test("drops generated labels when manual labels are present after security middleware is migratable", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ [
+ "traefik.enable=true",
+ "traefik.http.routers.sample-app-42-websecure.rule=Host(`dashboard.example.com`)",
+ "traefik.http.routers.sample-app-42-websecure.entrypoints=websecure",
+ "traefik.http.routers.sample-app-42-websecure.tls.certresolver=letsencrypt",
+ "traefik.http.routers.sample-app-42-websecure.middlewares=internal-allowlist@file",
+ "traefik.http.routers.sample-app-42-websecure.service=sample-app-42-websecure",
+ "traefik.http.services.sample-app-42-websecure.loadbalancer.server.port=8000",
+ "traefik.http.routers.custom.rule=Host(`custom.example.com`)",
+ "traefik.http.routers.custom.entrypoints=websecure",
+ "traefik.http.routers.custom.tls.certresolver=letsencrypt",
+ "traefik.http.routers.custom.service=custom",
+ "traefik.http.services.custom.loadbalancer.server.port=3000",
+ ],
+ {
+ sourceFile: "sample-app/dashboard-api/labels",
+ appName: "sample-app",
+ domains: [
+ {
+ host: "dashboard.example.com",
+ uniqueConfigKey: 42,
+ https: true,
+ } as any,
+ ],
+ serviceName: "dashboard-api",
+ upstreamServiceName: "dashboard-api",
+ fileMiddlewares: {
+ "internal-allowlist": {
+ ipAllowList: {
+ sourceRange: ["192.0.2.0/24"],
+ },
+ } as any,
+ },
+ },
+ );
+
+ expect(result.routes).toEqual([
+ expect.objectContaining({
+ hosts: ["custom.example.com"],
+ upstreams: ["http://dashboard-api:3000"],
+ }),
+ ]);
+ expect(result.warnings).toEqual([]);
+ });
+
+ test("parses inline ipWhiteList labels as Caddy remote IP restrictions", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ [
+ "traefik.http.routers.admin.rule=Host(`admin.example.com`)",
+ "traefik.http.routers.admin.entrypoints=websecure",
+ "traefik.http.routers.admin.tls.certresolver=letsencrypt",
+ "traefik.http.routers.admin.service=admin",
+ "traefik.http.routers.admin.middlewares=admin-allow",
+ "traefik.http.services.admin.loadbalancer.server.port=8080",
+ "traefik.http.middlewares.admin-allow.ipWhiteList.sourceRange=192.0.2.0/24,127.0.0.1/32",
+ ],
+ {
+ sourceFile: "admin/docker-compose.yml",
+ serviceName: "admin",
+ upstreamServiceName: "admin",
+ },
+ );
+
+ expect(result.warnings).toEqual([]);
+ expect(result.routes[0]).toMatchObject({
+ allowedRemoteIps: ["192.0.2.0/24", "127.0.0.1/32"],
+ });
+ });
+
+ test("does not classify app-name-shaped labels as generated when DB domains are provided", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ [
+ "traefik.http.routers.my-compose-custom-web.rule=Host(`custom.example.com`)",
+ "traefik.http.routers.my-compose-custom-web.entrypoints=web",
+ "traefik.http.services.my-compose-custom-web.loadbalancer.server.port=8080",
+ ],
+ {
+ sourceFile: "my-compose/docker-compose.yml",
+ appName: "my-compose",
+ domains: [],
+ serviceName: "web",
+ upstreamServiceName: "web",
+ },
+ );
+
+ expect(result.classifications).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ label:
+ "traefik.http.routers.my-compose-custom-web.rule=Host(`custom.example.com`)",
+ dokployGenerated: false,
+ }),
+ ]),
+ );
+ expect(result.routes[0]).toMatchObject({
+ hosts: ["custom.example.com"],
+ upstreams: ["http://web:8080"],
+ });
+ });
+
+ test("returns blocking warnings for unsupported label rules and middleware constructs", () => {
+ const result = translateTraefikComposeLabelsToCaddyFragment(
+ composeLabels("unsupported"),
+ {
+ sourceFile: "unsupported/docker-compose.yml",
+ serviceName: "unsupported",
+ },
+ );
+
+ expect(result.routes).toHaveLength(0);
+ expect(result.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "unsupported-matcher",
+ routerName: "unsupported",
+ blocking: true,
+ }),
+ expect.objectContaining({
+ code: "unsupported-middleware",
+ middlewareName: "plugin-only",
+ blocking: true,
+ }),
+ ]),
+ );
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/migration/dynamic-file-translator.test.ts b/apps/dokploy/__test__/caddy/migration/dynamic-file-translator.test.ts
new file mode 100644
index 0000000000..a04a43c439
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/dynamic-file-translator.test.ts
@@ -0,0 +1,266 @@
+import { readFileSync } from "node:fs";
+import path from "node:path";
+import {
+ compileCaddyConfig,
+ translateTraefikDynamicConfigToCaddyFragment,
+} from "@dokploy/server";
+import { describe, expect, test } from "vitest";
+
+const fixture = (name: string) =>
+ readFileSync(path.join(__dirname, "fixtures", name), "utf8");
+
+const getServers = (config: ReturnType) => {
+ const apps = config.apps as Record;
+ return apps.http.servers as Record;
+};
+
+describe("Traefik dynamic file to Caddy migration", () => {
+ test("translates priority dynamic-file routers, headers, redirects, and external upstreams", () => {
+ const result = translateTraefikDynamicConfigToCaddyFragment(
+ fixture("priority-dynamic.yml"),
+ { sourceFile: "priority-dynamic.yml" },
+ );
+ const config = compileCaddyConfig({ fragments: [result.fragment] });
+ const servers = getServers(config);
+
+ expect(result.warnings).toEqual([]);
+ expect(result.fragment).toMatchObject({
+ id: "migration.traefik-dynamic.priority-dynamic",
+ source: "traefik-dynamic-file",
+ });
+
+ const activityRoutes = result.routes.filter((route) =>
+ route.id.includes("activity-feed"),
+ );
+ expect(activityRoutes).toHaveLength(3);
+ expect(activityRoutes.map((route) => route.priority)).toEqual([
+ 10000, 10000, 10000,
+ ]);
+ expect(activityRoutes).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ pathPrefix: "/activity",
+ upstreams: ["https://activity.example.net:443"],
+ }),
+ expect.objectContaining({ pathExact: "/.well-known/webfinger" }),
+ expect.objectContaining({ pathExact: "/.well-known/nodeinfo" }),
+ ]),
+ );
+
+ const adminRoute = result.routes.find((route) =>
+ route.id.includes("admin-console"),
+ );
+ expect(adminRoute).toMatchObject({
+ hosts: ["example.com", "www.example.com"],
+ pathPrefix: "/admin",
+ priority: 9000,
+ transforms: {
+ responseHeaders: {
+ "Cache-Control":
+ "private, no-store, no-cache, must-revalidate, max-age=0",
+ Pragma: "no-cache",
+ Expires: "0",
+ },
+ },
+ });
+
+ const redirectRoute = result.routes.find((route) =>
+ route.id.includes("site-redirect"),
+ );
+ expect(redirectRoute).toMatchObject({
+ redirectScheme: { scheme: "https", permanent: true },
+ upstreams: [],
+ });
+
+ const activityProxy = servers.https.routes.find((route: any) =>
+ route.match[0].path?.includes("/activity*"),
+ );
+ expect(activityProxy.handle.at(-1)).toMatchObject({
+ handler: "reverse_proxy",
+ upstreams: [{ dial: "activity.example.net:443" }],
+ transport: { protocol: "http", tls: {} },
+ });
+ });
+
+ test("normalizes Traefik service URLs with default scheme ports", () => {
+ const result = translateTraefikDynamicConfigToCaddyFragment(
+ [
+ "http:",
+ " routers:",
+ " admin:",
+ " rule: Host(`admin.example.com`)",
+ " entryPoints: [websecure]",
+ " service: admin",
+ " tls:",
+ " certResolver: letsencrypt",
+ " services:",
+ " admin:",
+ " loadBalancer:",
+ " servers:",
+ " - url: http://admin",
+ ].join("\n"),
+ { sourceFile: "admin.yml" },
+ );
+
+ expect(result.routes[0]).toMatchObject({
+ upstreams: ["http://admin:80"],
+ });
+ expect(() =>
+ compileCaddyConfig({ fragments: [result.fragment] }),
+ ).not.toThrow();
+ });
+
+ test("skips Traefik api@internal dashboard routers as non-migratable internals", () => {
+ const result = translateTraefikDynamicConfigToCaddyFragment(
+ [
+ "http:",
+ " routers:",
+ " traefik-dashboard:",
+ " rule: PathPrefix(`/dashboard`) || PathPrefix(`/api`)",
+ " entryPoints: [traefik]",
+ " service: api@internal",
+ " middlewares: [internal-allowlist]",
+ ].join("\n"),
+ { sourceFile: "routers.yml" },
+ );
+
+ expect(result.routes).toEqual([]);
+ expect(result.warnings).toEqual([
+ expect.objectContaining({
+ code: "unsupported-router",
+ routerName: "traefik-dashboard",
+ serviceName: "api@internal",
+ blocking: false,
+ message: expect.stringContaining("Skipped Traefik internal router"),
+ }),
+ ]);
+ });
+
+ test("translates active routes that use ipAllowList file middleware", () => {
+ const result = translateTraefikDynamicConfigToCaddyFragment(
+ [
+ "http:",
+ " routers:",
+ " dash:",
+ " rule: Host(`dash.example.com`)",
+ " entryPoints: [websecure]",
+ " service: dash",
+ " tls:",
+ " certResolver: letsencrypt",
+ " middlewares: [internal-allowlist]",
+ " services:",
+ " dash:",
+ " loadBalancer:",
+ " servers:",
+ " - url: http://dash:8000",
+ " middlewares:",
+ " internal-allowlist:",
+ " ipAllowList:",
+ " sourceRange:",
+ " - 192.0.2.0/24",
+ ].join("\n"),
+ { sourceFile: "dash.yml" },
+ );
+
+ expect(result.warnings).toEqual([]);
+ expect(result.routes[0]).toMatchObject({
+ allowedRemoteIps: ["192.0.2.0/24"],
+ });
+ const config = compileCaddyConfig({ fragments: [result.fragment] });
+ const servers = getServers(config);
+ expect(servers.https.routes).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ match: [
+ expect.objectContaining({
+ remote_ip: { ranges: ["192.0.2.0/24"] },
+ }),
+ ],
+ }),
+ expect.objectContaining({
+ handle: [
+ expect.objectContaining({
+ handler: "static_response",
+ status_code: 403,
+ }),
+ ],
+ }),
+ ]),
+ );
+ });
+
+ test("translates strip/add prefix, basicAuth, chain, and blocks unsupported constructs", () => {
+ const result = translateTraefikDynamicConfigToCaddyFragment(
+ fixture("middleware-coverage.yml"),
+ { sourceFile: "middleware-coverage.yml" },
+ );
+
+ const toolRoute = result.routes.find((route) => route.id.endsWith("-tool"));
+ expect(toolRoute).toMatchObject({
+ transforms: {
+ stripPrefix: "/public",
+ addPrefix: "/internal",
+ requestHeaders: { "X-Forwarded-Proto": "https" },
+ responseHeaders: { "X-Test": "true" },
+ },
+ basicAuth: [{ username: "admin", hash: "$2y$05$abcdef" }],
+ });
+
+ expect(result.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "unsupported-service",
+ serviceName: "tool",
+ blocking: true,
+ }),
+ expect.objectContaining({
+ code: "unsupported-middleware",
+ middlewareName: "plugin-auth",
+ blocking: true,
+ }),
+ expect.objectContaining({
+ code: "unsupported-router",
+ routerName: "unsupported-plugin",
+ message: expect.stringContaining("Custom certResolver"),
+ blocking: true,
+ }),
+ ]),
+ );
+ });
+
+ test("blocks basicAuth hashes that Caddy cannot safely consume", () => {
+ const result = translateTraefikDynamicConfigToCaddyFragment(
+ [
+ "http:",
+ " routers:",
+ " secure:",
+ " rule: Host(`secure.example.com`)",
+ " service: secure",
+ " middlewares: [legacy-auth]",
+ " services:",
+ " secure:",
+ " loadBalancer:",
+ " servers:",
+ " - url: http://secure:3000",
+ " middlewares:",
+ " legacy-auth:",
+ " basicAuth:",
+ " users:",
+ " - admin:$apr1$legacy-hash",
+ ].join("\n"),
+ { sourceFile: "legacy-auth.yml" },
+ );
+
+ expect(result.routes[0]?.basicAuth).toBeNull();
+ expect(result.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "unsupported-security-middleware",
+ middlewareName: "legacy-auth",
+ blocking: true,
+ message: expect.stringContaining("hash format"),
+ }),
+ ]),
+ );
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/migration/fixtures/generic-compose-labels.yml b/apps/dokploy/__test__/caddy/migration/fixtures/generic-compose-labels.yml
new file mode 100644
index 0000000000..2500c4eb69
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/fixtures/generic-compose-labels.yml
@@ -0,0 +1,58 @@
+# Representative Traefik compose labels for Caddy migration tests.
+services:
+ frontend:
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.compose-sample-stack-frontend-web.rule=Host(`app.example.com`)
+ - traefik.http.routers.compose-sample-stack-frontend-web.entrypoints=web
+ - traefik.http.services.compose-sample-stack-frontend-web.loadbalancer.server.port=3000
+ - traefik.http.routers.compose-sample-stack-frontend-web.service=compose-sample-stack-frontend-web
+ - traefik.http.routers.compose-sample-stack-frontend-web.middlewares=redirect-to-https@file
+ - traefik.http.routers.compose-sample-stack-frontend-websecure.rule=Host(`app.example.com`)
+ - traefik.http.routers.compose-sample-stack-frontend-websecure.entrypoints=websecure
+ - traefik.http.services.compose-sample-stack-frontend-websecure.loadbalancer.server.port=3000
+ - traefik.http.routers.compose-sample-stack-frontend-websecure.service=compose-sample-stack-frontend-websecure
+ - traefik.http.routers.compose-sample-stack-frontend-websecure.tls.certresolver=letsencrypt
+ - traefik.http.routers.compose-sample-stack-frontend-websecure.middlewares=security-headers@file
+ - traefik.docker.network=dokploy-network
+ cms:
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.docker.network=dokploy-network
+ - traefik.http.routers.cms-prod.rule=Host(`example.com`) || Host(`www.example.com`)
+ - traefik.http.routers.cms-prod.entrypoints=websecure
+ - traefik.http.routers.cms-prod.tls.certresolver=letsencrypt
+ - traefik.http.routers.cms-prod.middlewares=cms-security-headers@file
+ - traefik.http.routers.cms-prod.service=cms-prod
+ - traefik.http.routers.cms-preview.rule=Host(`cms.test.example.com`) || Host(`preview.test.example.com`)
+ - traefik.http.routers.cms-preview.entrypoints=websecure
+ - traefik.http.routers.cms-preview.tls.certresolver=letsencrypt
+ - traefik.http.routers.cms-preview.middlewares=cms-security-headers@file
+ - traefik.http.routers.cms-preview.service=cms-prod
+ - traefik.http.services.cms-prod.loadbalancer.server.port=8080
+ admin:
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.admin.rule=Host(`admin.example.com`)
+ - traefik.http.routers.admin.entrypoints=websecure
+ - traefik.http.routers.admin.service=admin
+ - traefik.http.routers.admin.tls.certresolver=letsencrypt
+ - traefik.http.routers.admin.middlewares=security-headers@file
+ - traefik.http.routers.admin-console.rule=Host(`admin.example.com`) && (PathPrefix(`/admin`) || Path(`/login`))
+ - traefik.http.routers.admin-console.entrypoints=websecure
+ - traefik.http.routers.admin-console.service=admin
+ - traefik.http.routers.admin-console.tls.certresolver=letsencrypt
+ - traefik.http.routers.admin-console.middlewares=security-headers@file,admin-no-store@file
+ - traefik.http.routers.admin-console.priority=100
+ - traefik.http.services.admin.loadbalancer.server.port=80
+ unsupported:
+ labels:
+ - traefik.http.routers.unsupported.rule=HostRegexp(`{subdomain:[a-z]+}.example.com`)
+ - traefik.http.routers.unsupported.entrypoints=websecure
+ - traefik.http.routers.unsupported.service=unsupported
+ - traefik.http.services.unsupported.loadbalancer.server.port=9000
+ - traefik.http.routers.unsupported.middlewares=plugin-only
+ - traefik.http.middlewares.plugin-only.plugin.demo.enabled=true
diff --git a/apps/dokploy/__test__/caddy/migration/fixtures/middleware-coverage.yml b/apps/dokploy/__test__/caddy/migration/fixtures/middleware-coverage.yml
new file mode 100644
index 0000000000..964c992f75
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/fixtures/middleware-coverage.yml
@@ -0,0 +1,54 @@
+http:
+ routers:
+ tool:
+ rule: "Host(`tools.example.com`) && PathPrefix(`/public`)"
+ entryPoints:
+ - websecure
+ service: tool
+ tls:
+ certResolver: letsencrypt
+ middlewares:
+ - tool-chain
+ unsupported-plugin:
+ rule: "Host(`blocked.example.com`)"
+ entryPoints:
+ - websecure
+ service: tool
+ tls:
+ certResolver: customresolver
+ middlewares:
+ - plugin-auth
+ services:
+ tool:
+ loadBalancer:
+ servers:
+ - url: http://tool:8080
+ passHostHeader: false
+ middlewares:
+ tool-chain:
+ chain:
+ middlewares:
+ - strip-public
+ - add-internal
+ - basic-users
+ - response-headers
+ strip-public:
+ stripPrefix:
+ prefixes:
+ - /public
+ add-internal:
+ addPrefix:
+ prefix: /internal
+ basic-users:
+ basicAuth:
+ users:
+ - admin:$2y$05$abcdef
+ response-headers:
+ headers:
+ customRequestHeaders:
+ X-Forwarded-Proto: "https"
+ customResponseHeaders:
+ X-Test: "true"
+ plugin-auth:
+ plugin:
+ unsupported: {}
diff --git a/apps/dokploy/__test__/caddy/migration/fixtures/priority-dynamic.yml b/apps/dokploy/__test__/caddy/migration/fixtures/priority-dynamic.yml
new file mode 100644
index 0000000000..ace2ca7a5c
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/fixtures/priority-dynamic.yml
@@ -0,0 +1,58 @@
+# Representative Traefik dynamic-file routes for Caddy migration tests.
+http:
+ routers:
+ activity-feed:
+ rule: "Host(`example.com`) && (PathPrefix(`/activity`) || Path(`/.well-known/webfinger`) || Path(`/.well-known/nodeinfo`))"
+ entryPoints:
+ - websecure
+ service: hosted-activity
+ tls:
+ certResolver: letsencrypt
+ middlewares:
+ - security-headers
+ priority: 10000
+ admin-console:
+ rule: "(Host(`example.com`) || Host(`www.example.com`)) && PathPrefix(`/admin`)"
+ entryPoints:
+ - websecure
+ service: site
+ tls:
+ certResolver: letsencrypt
+ middlewares:
+ - security-headers
+ - admin-no-store
+ priority: 9000
+ site:
+ rule: "Host(`example.com`) || Host(`www.example.com`)"
+ entryPoints:
+ - websecure
+ service: site
+ tls:
+ certResolver: letsencrypt
+ middlewares:
+ - security-headers
+ site-redirect:
+ rule: "Host(`example.com`) || Host(`www.example.com`)"
+ entryPoints:
+ - web
+ service: site
+ middlewares:
+ - redirect-to-https
+ services:
+ site:
+ loadBalancer:
+ servers:
+ - url: http://site:8080
+ passHostHeader: true
+ hosted-activity:
+ loadBalancer:
+ servers:
+ - url: https://activity.example.net:443
+ passHostHeader: true
+ middlewares:
+ admin-no-store:
+ headers:
+ customResponseHeaders:
+ Cache-Control: "private, no-store, no-cache, must-revalidate, max-age=0"
+ Pragma: "no-cache"
+ Expires: "0"
diff --git a/apps/dokploy/__test__/caddy/migration/prepare.test.ts b/apps/dokploy/__test__/caddy/migration/prepare.test.ts
new file mode 100644
index 0000000000..41702311dc
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/prepare.test.ts
@@ -0,0 +1,724 @@
+import { fs, vol } from "memfs";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+vi.mock("node:fs", () => ({
+ ...fs,
+ default: fs,
+}));
+
+vi.mock("@dokploy/server/db", () => ({
+ db: {
+ query: {
+ applications: { findMany: vi.fn() },
+ certificates: { findFirst: vi.fn() },
+ compose: { findMany: vi.fn() },
+ },
+ },
+}));
+
+vi.mock("@dokploy/server/services/web-server-settings", () => ({
+ getCaddyCompileSettings: vi.fn().mockResolvedValue({
+ letsEncryptEmail: "ops@example.com",
+ trustedProxies: null,
+ }),
+ getWebServerSettings: vi.fn().mockResolvedValue({
+ letsEncryptEmail: "ops@example.com",
+ }),
+}));
+
+vi.mock("@dokploy/server/utils/docker/domain", async (importOriginal) => {
+ const actual =
+ await importOriginal<
+ typeof import("@dokploy/server/utils/docker/domain")
+ >();
+ return {
+ ...actual,
+ loadDockerCompose: vi.fn().mockResolvedValue(null),
+ loadDockerComposeRemote: vi.fn().mockResolvedValue(null),
+ };
+});
+
+const remoteDockerMock = vi.hoisted(() => ({
+ listServices: vi.fn().mockResolvedValue([]),
+ listContainers: vi.fn().mockResolvedValue([]),
+ listTasks: vi.fn().mockResolvedValue([]),
+ getContainer: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/utils/servers/remote-docker", () => ({
+ getRemoteDocker: vi.fn().mockResolvedValue(remoteDockerMock),
+}));
+
+import { paths } from "@dokploy/server/constants";
+import { db } from "@dokploy/server/db";
+import { prepareCaddyMigration } from "@dokploy/server/utils/caddy/migration/prepare";
+
+const domain = {
+ domainId: "domain-1",
+ host: "app.example.com",
+ https: true,
+ port: 3000,
+ customEntrypoint: null,
+ path: "/",
+ serviceName: null,
+ domainType: "application",
+ uniqueConfigKey: 1,
+ createdAt: new Date().toISOString(),
+ composeId: null,
+ customCertResolver: null,
+ applicationId: "app-1",
+ previewDeploymentId: null,
+ certificateType: "letsencrypt",
+ internalPath: "/",
+ stripPath: false,
+ middlewares: [],
+};
+
+const genericComposeFixture = [
+ "services:",
+ " cms:",
+ " deploy:",
+ " labels:",
+ " - traefik.enable=true",
+ " - traefik.docker.network=dokploy-network",
+ " - traefik.http.routers.cms-prod.rule=Host(`example.com`) || Host(`www.example.com`)",
+ " - traefik.http.routers.cms-prod.entrypoints=websecure",
+ " - traefik.http.routers.cms-prod.tls.certresolver=letsencrypt",
+ " - traefik.http.routers.cms-prod.middlewares=cms-security-headers@file",
+ " - traefik.http.routers.cms-prod.service=cms-prod",
+ " - traefik.http.services.cms-prod.loadbalancer.server.port=8080",
+ " unsupported:",
+ " labels:",
+ " - traefik.http.routers.unsupported.rule=HostRegexp(`{subdomain:[a-z]+}.example.com`)",
+ " - traefik.http.routers.unsupported.entrypoints=websecure",
+ " - traefik.http.routers.unsupported.service=unsupported",
+ " - traefik.http.services.unsupported.loadbalancer.server.port=9000",
+ " - traefik.http.routers.unsupported.middlewares=plugin-only",
+ " - traefik.http.middlewares.plugin-only.plugin.demo.enabled=true",
+].join("\n");
+
+const writeCertificateFiles = (certificatePath: string) => {
+ const certDir = `${paths().CERTIFICATES_PATH}/${certificatePath}`;
+ vol.mkdirSync(certDir, { recursive: true });
+ vol.writeFileSync(`${certDir}/chain.crt`, "cert");
+ vol.writeFileSync(`${certDir}/privkey.key`, "key");
+};
+
+describe("prepareCaddyMigration", () => {
+ beforeEach(() => {
+ vol.reset();
+ vi.clearAllMocks();
+ remoteDockerMock.listServices.mockResolvedValue([]);
+ remoteDockerMock.listContainers.mockResolvedValue([]);
+ remoteDockerMock.listTasks.mockResolvedValue([]);
+ remoteDockerMock.getContainer.mockReturnValue({
+ inspect: vi.fn().mockResolvedValue({}),
+ });
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([
+ {
+ applicationId: "app-1",
+ appName: "test-app",
+ serverId: null,
+ environment: { project: { organizationId: "org-1" } },
+ domains: [domain],
+ } as any,
+ ]);
+ vi.mocked(db.query.compose.findMany).mockResolvedValue([]);
+ vi.mocked(db.query.certificates.findFirst).mockResolvedValue(undefined);
+ });
+
+ test("writes reviewable dry-run artifacts without touching live Caddy config", async () => {
+ const currentPaths = paths();
+ vol.mkdirSync(currentPaths.DYNAMIC_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.MAIN_TRAEFIK_PATH}/traefik.yml`,
+ "entryPoints:\n web:\n address: ':80'\n",
+ );
+ vol.writeFileSync(
+ `${currentPaths.DYNAMIC_TRAEFIK_PATH}/manual.yml`,
+ [
+ "http:",
+ " routers:",
+ " manual:",
+ " rule: Host(`manual.example.com`)",
+ " entryPoints: [websecure]",
+ " service: manual",
+ " tls:",
+ " certResolver: letsencrypt",
+ " services:",
+ " manual:",
+ " loadBalancer:",
+ " servers:",
+ " - url: http://manual:8080",
+ ].join("\n"),
+ );
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.status).toBe("prepared");
+ expect(report.summary.blockingWarnings).toBe(0);
+ expect(report.summary.fragments).toBe(2);
+ expect(report.inputs.dynamicFiles).toHaveLength(1);
+ expect(vol.existsSync(report.artifactPaths.reportJson)).toBe(true);
+ expect(vol.existsSync(report.artifactPaths.reportMd)).toBe(true);
+ expect(vol.existsSync(report.artifactPaths.caddyJson)).toBe(true);
+ expect(vol.existsSync(currentPaths.CADDY_CONFIG_PATH)).toBe(false);
+
+ const draft = JSON.parse(
+ vol.readFileSync(report.artifactPaths.caddyJson, "utf8") as string,
+ ) as any;
+ expect(draft.apps.tls.automation.policies[0].issuers[0].email).toBe(
+ "ops@example.com",
+ );
+ const fragmentFiles = vol.readdirSync(report.artifactPaths.fragmentsDir);
+ expect(fragmentFiles).toEqual(
+ expect.arrayContaining([
+ "application.test-app.1.json",
+ "migration.traefik-dynamic.manual.json",
+ ]),
+ );
+ });
+
+ test("blocks DB fallback routes with missing uploaded custom certificates", async () => {
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([
+ {
+ applicationId: "app-1",
+ appName: "custom-cert-app",
+ serverId: null,
+ environment: { project: { organizationId: "org-1" } },
+ domains: [
+ {
+ ...domain,
+ certificateType: "custom",
+ customCertResolver: "legacy-traefik-resolver",
+ },
+ ],
+ } as any,
+ ]);
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.summary.blockingWarnings).toBe(1);
+ expect(report.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ blocking: true,
+ code: "missing-certificate",
+ source: "custom-cert-app",
+ message: expect.stringContaining("legacy-traefik-resolver"),
+ }),
+ ]),
+ );
+ expect(report.summary.fragments).toBe(0);
+ });
+
+ test("blocks DB fallback routes with cross-organization uploaded certificates", async () => {
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([
+ {
+ applicationId: "app-1",
+ appName: "custom-cert-app",
+ serverId: null,
+ environment: { project: { organizationId: "org-1" } },
+ domains: [
+ {
+ ...domain,
+ certificateType: "custom",
+ customCertResolver: "certificate-uploaded",
+ },
+ ],
+ } as any,
+ ]);
+ vi.mocked(db.query.certificates.findFirst).mockResolvedValue({
+ certificatePath: "certificate-uploaded",
+ serverId: null,
+ organizationId: "org-2",
+ } as any);
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.summary.blockingWarnings).toBe(1);
+ expect(report.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ blocking: true,
+ code: "missing-certificate",
+ source: "custom-cert-app",
+ message: expect.stringContaining("server and organization"),
+ }),
+ ]),
+ );
+ expect(report.summary.fragments).toBe(0);
+ });
+
+ test("keeps DB fallback routes with readable uploaded custom certificates", async () => {
+ writeCertificateFiles("certificate-uploaded");
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([
+ {
+ applicationId: "app-1",
+ appName: "custom-cert-app",
+ serverId: null,
+ environment: { project: { organizationId: "org-1" } },
+ domains: [
+ {
+ ...domain,
+ certificateType: "custom",
+ customCertResolver: "certificate-uploaded",
+ },
+ ],
+ } as any,
+ ]);
+ vi.mocked(db.query.certificates.findFirst).mockResolvedValue({
+ certificatePath: "certificate-uploaded",
+ serverId: null,
+ organizationId: "org-1",
+ } as any);
+
+ const report = await prepareCaddyMigration();
+ const draft = JSON.parse(
+ vol.readFileSync(report.artifactPaths.caddyJson, "utf8") as string,
+ ) as any;
+ const certificatePath = `${paths().CERTIFICATES_PATH}/certificate-uploaded`;
+
+ expect(report.summary.blockingWarnings).toBe(0);
+ expect(report.warnings).not.toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ code: "missing-certificate" }),
+ ]),
+ );
+ expect(report.summary.fragments).toBe(1);
+ expect(draft.apps.tls.certificates.load_files).toEqual([
+ {
+ certificate: `${certificatePath}/chain.crt`,
+ key: `${certificatePath}/privkey.key`,
+ },
+ ]);
+ });
+
+ test("carries existing manual Caddy fragments into the migration draft", async () => {
+ const currentPaths = paths();
+ vol.mkdirSync(currentPaths.CADDY_FRAGMENTS_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.CADDY_FRAGMENTS_PATH}/manual.archive.json`,
+ JSON.stringify(
+ {
+ version: 1,
+ id: "manual.archive",
+ source: "manual",
+ routes: [
+ {
+ id: "archive-404",
+ source: "manual",
+ hosts: ["archive.example.com"],
+ https: true,
+ upstreams: [],
+ staticResponse: { statusCode: 404 },
+ },
+ ],
+ },
+ null,
+ 2,
+ ),
+ );
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.summary.blockingWarnings).toBe(0);
+ expect(
+ vol.existsSync(
+ `${report.artifactPaths.fragmentsDir}/manual.archive.json`,
+ ),
+ ).toBe(true);
+ const draft = JSON.parse(
+ vol.readFileSync(report.artifactPaths.caddyJson, "utf8") as string,
+ ) as any;
+ const archiveRoute = draft.apps.http.servers.https.routes.find(
+ (item: any) => JSON.stringify(item.match).includes("archive.example.com"),
+ );
+ expect(archiveRoute.handle[0]).toMatchObject({
+ handler: "static_response",
+ status_code: 404,
+ });
+ });
+
+ test("blocks carried manual Caddy fragments that overlap generated migration routes", async () => {
+ const currentPaths = paths();
+ vol.mkdirSync(currentPaths.CADDY_FRAGMENTS_PATH, { recursive: true });
+ vol.mkdirSync(currentPaths.DYNAMIC_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.CADDY_FRAGMENTS_PATH}/manual.conflict.json`,
+ JSON.stringify(
+ {
+ version: 1,
+ id: "manual.conflict",
+ source: "manual",
+ routes: [
+ {
+ id: "manual-conflict",
+ source: "manual",
+ hosts: ["manual.example.com"],
+ https: true,
+ upstreams: [],
+ staticResponse: { statusCode: 404 },
+ },
+ ],
+ },
+ null,
+ 2,
+ ),
+ );
+ vol.writeFileSync(
+ `${currentPaths.MAIN_TRAEFIK_PATH}/traefik.yml`,
+ "entryPoints:\n web:\n address: ':80'\n",
+ );
+ vol.writeFileSync(
+ `${currentPaths.DYNAMIC_TRAEFIK_PATH}/manual.yml`,
+ [
+ "http:",
+ " routers:",
+ " manual:",
+ " rule: Host(`manual.example.com`)",
+ " entryPoints: [websecure]",
+ " service: manual",
+ " tls:",
+ " certResolver: letsencrypt",
+ " services:",
+ " manual:",
+ " loadBalancer:",
+ " servers:",
+ " - url: http://manual:8080",
+ ].join("\n"),
+ );
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([]);
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.summary.blockingWarnings).toBe(1);
+ expect(report.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "conflicting-manual-fragment",
+ source: "manual.conflict",
+ blocking: true,
+ }),
+ ]),
+ );
+ expect(vol.readFileSync(report.artifactPaths.reportMd, "utf8")).toContain(
+ "conflicting-manual-fragment",
+ );
+ });
+
+ test("reports compose-label warnings with source references during dry run", async () => {
+ const currentPaths = paths();
+ vol.mkdirSync(currentPaths.DYNAMIC_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.MAIN_TRAEFIK_PATH}/traefik.yml`,
+ "entryPoints:\n web:\n address: ':80'\n",
+ );
+ vol.writeFileSync(
+ `${currentPaths.DYNAMIC_TRAEFIK_PATH}/middlewares.yml`,
+ [
+ "http:",
+ " middlewares:",
+ " cms-security-headers:",
+ " headers:",
+ " stsSeconds: 31536000",
+ " stsIncludeSubdomains: true",
+ " stsPreload: true",
+ " referrerPolicy: strict-origin-when-cross-origin",
+ " contentTypeNosniff: true",
+ ].join("\n"),
+ );
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([]);
+ vi.mocked(db.query.compose.findMany).mockResolvedValue([
+ {
+ appName: "generic-fixture",
+ serverId: null,
+ composeFile: genericComposeFixture,
+ composeType: "docker-compose",
+ randomize: false,
+ isolatedDeployment: false,
+ suffix: null,
+ domains: [],
+ } as any,
+ ]);
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.inputs.composeFilesScanned).toEqual(["generic-fixture"]);
+ expect(report.summary.fragments).toBe(1);
+ expect(report.summary.blockingWarnings).toBeGreaterThan(0);
+ expect(report.warnings).not.toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "unresolved-middleware",
+ source: "generic-fixture/cms/deploy.labels",
+ middlewareName: "cms-security-headers@file",
+ }),
+ ]),
+ );
+ expect(report.warnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "unsupported-matcher",
+ source: "generic-fixture/unsupported/labels",
+ routerName: "unsupported",
+ blocking: true,
+ }),
+ ]),
+ );
+
+ const reportJson = JSON.parse(
+ vol.readFileSync(report.artifactPaths.reportJson, "utf8") as string,
+ );
+ expect(reportJson.inputs.composeFilesScanned).toEqual(["generic-fixture"]);
+ const reportMd = vol.readFileSync(
+ report.artifactPaths.reportMd,
+ "utf8",
+ ) as string;
+ expect(reportMd).toContain("Blocking warnings");
+ expect(reportMd).not.toContain("cms-security-headers@file");
+ expect(reportMd).toContain("generic-fixture/unsupported/labels");
+ });
+
+ test("normalizes dynamic-file upstream URLs with default scheme ports", async () => {
+ const currentPaths = paths();
+ vol.mkdirSync(currentPaths.DYNAMIC_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.MAIN_TRAEFIK_PATH}/traefik.yml`,
+ "entryPoints:\n web:\n address: ':80'\n",
+ );
+ vol.writeFileSync(
+ `${currentPaths.DYNAMIC_TRAEFIK_PATH}/admin.yml`,
+ [
+ "http:",
+ " routers:",
+ " admin:",
+ " rule: Host(`admin.example.com`)",
+ " entryPoints: [websecure]",
+ " service: admin",
+ " tls:",
+ " certResolver: letsencrypt",
+ " services:",
+ " admin:",
+ " loadBalancer:",
+ " servers:",
+ " - url: http://admin",
+ ].join("\n"),
+ );
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([]);
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.validation.status).toBe("passed");
+ expect(report.summary.blockingWarnings).toBe(0);
+ expect(report.warnings).toEqual([]);
+ expect(vol.readFileSync(report.artifactPaths.caddyJson, "utf8")).toContain(
+ '"dial": "admin:80"',
+ );
+ });
+
+ test("preserves generated-only compose label IP restrictions before discarding generated labels", async () => {
+ const currentPaths = paths();
+ vol.mkdirSync(currentPaths.DYNAMIC_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.MAIN_TRAEFIK_PATH}/traefik.yml`,
+ "entryPoints:\n web:\n address: ':80'\n",
+ );
+ vol.writeFileSync(
+ `${currentPaths.DYNAMIC_TRAEFIK_PATH}/middlewares.yml`,
+ [
+ "http:",
+ " middlewares:",
+ " internal-allowlist:",
+ " ipAllowList:",
+ " sourceRange:",
+ " - 192.0.2.0/24",
+ ].join("\n"),
+ );
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([]);
+ vi.mocked(db.query.compose.findMany).mockResolvedValue([
+ {
+ appName: "sample-dashboard",
+ serverId: null,
+ composeFile: [
+ "services:",
+ " dash-api:",
+ " image: dash",
+ " labels:",
+ " - traefik.enable=true",
+ " - traefik.docker.network=dokploy-network",
+ " - traefik.http.routers.sample-dashboard-42-websecure.rule=Host(`dashboard.example.com`)",
+ " - traefik.http.routers.sample-dashboard-42-websecure.entrypoints=websecure",
+ " - traefik.http.routers.sample-dashboard-42-websecure.tls.certresolver=letsencrypt",
+ " - traefik.http.routers.sample-dashboard-42-websecure.middlewares=internal-allowlist@file",
+ " - traefik.http.routers.sample-dashboard-42-websecure.service=sample-dashboard-42-websecure",
+ " - traefik.http.services.sample-dashboard-42-websecure.loadbalancer.server.port=8000",
+ ].join("\n"),
+ composeType: "docker-compose",
+ randomize: false,
+ isolatedDeployment: false,
+ suffix: null,
+ domains: [
+ {
+ ...domain,
+ domainId: "dash-domain",
+ applicationId: null,
+ composeId: "dash-compose",
+ domainType: "compose",
+ host: "dashboard.example.com",
+ port: 8000,
+ serviceName: "dash-api",
+ uniqueConfigKey: 42,
+ },
+ ],
+ } as any,
+ ]);
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.summary.blockingWarnings).toBe(0);
+ expect(report.warnings).toEqual([
+ expect.objectContaining({
+ code: "shadowed-route",
+ blocking: false,
+ }),
+ ]);
+ expect(report.summary.fragments).toBe(1);
+ const caddyJson = vol.readFileSync(report.artifactPaths.caddyJson, "utf8");
+ expect(caddyJson).toContain('"remote_ip"');
+ expect(caddyJson).toContain('"status_code": 403');
+ });
+
+ test("skips live Docker Traefik label services with no running tasks", async () => {
+ const currentPaths = paths();
+ vol.mkdirSync(currentPaths.DYNAMIC_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.MAIN_TRAEFIK_PATH}/traefik.yml`,
+ "entryPoints:\n web:\n address: ':80'\n",
+ );
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([]);
+ vi.mocked(db.query.compose.findMany).mockResolvedValue([]);
+ remoteDockerMock.listServices.mockResolvedValue([
+ {
+ ID: "stopped-service-id",
+ Spec: {
+ Name: "stopped-stack_web",
+ Mode: { Replicated: { Replicas: 1 } },
+ Labels: {
+ "traefik.http.routers.stopped.rule": "Host(`stopped.example.com`)",
+ "traefik.http.routers.stopped.entrypoints": "websecure",
+ "traefik.http.routers.stopped.service": "stopped",
+ "traefik.http.routers.stopped.tls.certresolver": "letsencrypt",
+ "traefik.http.services.stopped.loadbalancer.server.port": "8080",
+ },
+ },
+ },
+ {
+ ID: "running-service-id",
+ Spec: {
+ Name: "running-stack_web",
+ Mode: { Replicated: { Replicas: 1 } },
+ Labels: {
+ "traefik.http.routers.running.rule": "Host(`running.example.com`)",
+ "traefik.http.routers.running.entrypoints": "websecure",
+ "traefik.http.routers.running.service": "running",
+ "traefik.http.routers.running.tls.certresolver": "letsencrypt",
+ "traefik.http.services.running.loadbalancer.server.port": "8080",
+ },
+ },
+ },
+ ]);
+ remoteDockerMock.listTasks.mockResolvedValue([
+ {
+ ServiceID: "running-service-id",
+ DesiredState: "running",
+ Status: { State: "running" },
+ },
+ ]);
+
+ const report = await prepareCaddyMigration();
+ const caddyJson = vol.readFileSync(report.artifactPaths.caddyJson, "utf8");
+
+ expect(report.summary.blockingWarnings).toBe(0);
+ expect(caddyJson).toContain("running.example.com");
+ expect(caddyJson).toContain("running-stack_web:8080");
+ expect(caddyJson).not.toContain("stopped.example.com");
+ expect(caddyJson).not.toContain("stopped-stack_web:8080");
+ });
+
+ test("uses inspected container network aliases for reachability checks", async () => {
+ const currentPaths = paths();
+ vol.mkdirSync(currentPaths.DYNAMIC_TRAEFIK_PATH, { recursive: true });
+ vol.writeFileSync(
+ `${currentPaths.MAIN_TRAEFIK_PATH}/traefik.yml`,
+ "entryPoints:\n web:\n address: ':80'\n",
+ );
+ vol.writeFileSync(
+ `${currentPaths.DYNAMIC_TRAEFIK_PATH}/admin.yml`,
+ [
+ "http:",
+ " routers:",
+ " admin:",
+ " rule: Host(`admin.example.com`)",
+ " entryPoints: [websecure]",
+ " service: admin",
+ " tls:",
+ " certResolver: letsencrypt",
+ " services:",
+ " admin:",
+ " loadBalancer:",
+ " servers:",
+ " - url: http://admin:80",
+ ].join("\n"),
+ );
+ vi.mocked(db.query.applications.findMany).mockResolvedValue([]);
+ vi.mocked(db.query.compose.findMany).mockResolvedValue([]);
+ remoteDockerMock.listContainers.mockResolvedValue([
+ {
+ Id: "traefik-container",
+ Names: ["/dokploy-traefik"],
+ NetworkSettings: { Networks: {} },
+ Labels: {},
+ },
+ {
+ Id: "admin-container",
+ Names: ["/admin-console-admin-1"],
+ NetworkSettings: { Networks: {} },
+ Labels: {},
+ },
+ ]);
+ remoteDockerMock.getContainer.mockImplementation((id: string) => ({
+ inspect: vi.fn().mockResolvedValue(
+ id === "admin-container"
+ ? {
+ NetworkSettings: {
+ Networks: {
+ "dokploy-network": {
+ Aliases: ["admin-console-admin-1", "admin"],
+ },
+ },
+ },
+ }
+ : {
+ NetworkSettings: {
+ Networks: {
+ "dokploy-network": { Aliases: ["dokploy-traefik"] },
+ },
+ },
+ },
+ ),
+ }));
+
+ const report = await prepareCaddyMigration();
+
+ expect(report.summary.blockingWarnings).toBe(0);
+ expect(report.warnings).not.toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ code: "unreachable-upstream" }),
+ ]),
+ );
+ expect(vol.readFileSync(report.artifactPaths.caddyJson, "utf8")).toContain(
+ '"dial": "admin:80"',
+ );
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/migration/traefik-recovery.test.ts b/apps/dokploy/__test__/caddy/migration/traefik-recovery.test.ts
new file mode 100644
index 0000000000..56720c50ae
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/traefik-recovery.test.ts
@@ -0,0 +1,290 @@
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+const execAsyncMock = vi.hoisted(() => vi.fn());
+const execAsyncRemoteMock = vi.hoisted(() => vi.fn());
+const initializeTraefikServiceMock = vi.hoisted(() => vi.fn());
+const initializeStandaloneTraefikMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server/utils/process/execAsync", () => ({
+ execAsync: execAsyncMock,
+ execAsyncRemote: execAsyncRemoteMock,
+}));
+
+vi.mock("@dokploy/server/db", () => ({
+ db: {
+ query: {
+ compose: { findMany: vi.fn().mockResolvedValue([]) },
+ },
+ },
+}));
+
+vi.mock("@dokploy/server/setup/caddy-setup", () => ({
+ initializeCaddyService: vi.fn(),
+ initializeStandaloneCaddy: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/setup/traefik-setup", () => ({
+ initializeTraefikService: initializeTraefikServiceMock,
+ initializeStandaloneTraefik: initializeStandaloneTraefikMock,
+}));
+
+import {
+ ensureTraefikRunningFromSnapshot,
+ getDockerResourceSnapshot,
+} from "@dokploy/server/services/settings";
+
+describe("Traefik rollback recovery", () => {
+ let resourceTypes: Record;
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ resourceTypes = {
+ "dokploy-traefik": "unknown",
+ dokploy: "service",
+ };
+ initializeTraefikServiceMock.mockImplementation(async () => {
+ resourceTypes["dokploy-traefik"] = "service";
+ });
+ initializeStandaloneTraefikMock.mockImplementation(async () => {
+ resourceTypes["dokploy-traefik"] = "standalone";
+ });
+ execAsyncMock.mockImplementation(async (command: string) => {
+ const resourceName = command.match(/RESOURCE_NAME="([^"]+)"/)?.[1];
+ if (resourceName) {
+ return {
+ stdout: `${resourceTypes[resourceName] ?? "unknown"}\n`,
+ stderr: "",
+ };
+ }
+ if (command.includes("docker start dokploy-traefik")) {
+ throw new Error("No such container: dokploy-traefik");
+ }
+ if (command.includes("docker service inspect dokploy-traefik")) {
+ return {
+ stdout: JSON.stringify({ Replicated: { Replicas: 1 } }),
+ stderr: "",
+ };
+ }
+ if (command.includes("docker service ps dokploy-traefik")) {
+ return { stdout: "Running 1 second ago\n", stderr: "" };
+ }
+ if (
+ command.includes("docker container inspect dokploy-traefik") &&
+ command.includes("'{{json .}}'")
+ ) {
+ return {
+ stdout: `${JSON.stringify({
+ State: {
+ Running: resourceTypes["dokploy-traefik"] === "standalone",
+ },
+ HostConfig: { Binds: [], RestartPolicy: { Name: "always" } },
+ NetworkSettings: { Networks: { "dokploy-network": {} } },
+ Config: { Labels: {} },
+ })}\n`,
+ stderr: "",
+ };
+ }
+ if (
+ command.includes("docker container inspect dokploy-traefik") &&
+ command.includes(".State.Running")
+ ) {
+ return { stdout: "true\n", stderr: "" };
+ }
+ return { stdout: "", stderr: "" };
+ });
+ execAsyncRemoteMock.mockImplementation(execAsyncMock);
+ });
+
+ test("recreates missing standalone Traefik with captured snapshot metadata", async () => {
+ await ensureTraefikRunningFromSnapshot({
+ resourceName: "dokploy-traefik",
+ resourceType: "standalone",
+ running: true,
+ env: "FOO=bar\nBAZ=qux",
+ additionalPorts: [
+ { targetPort: 8080, publishedPort: 8080, protocol: "tcp" },
+ ],
+ image: "traefik:v3.7.1",
+ binds: [
+ "/etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml:ro",
+ "/etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic",
+ "/etc/dokploy/traefik/acme.json:/letsencrypt/acme.json",
+ "/var/run/docker.sock:/var/run/docker.sock:ro",
+ ],
+ networks: ["dokploy-network"],
+ labels: { "example.label": "preserved" },
+ restartPolicy: { Name: "unless-stopped" },
+ });
+
+ expect(initializeStandaloneTraefikMock).toHaveBeenCalledWith({
+ env: ["FOO=bar", "BAZ=qux"],
+ additionalPorts: [
+ { targetPort: 8080, publishedPort: 8080, protocol: "tcp" },
+ ],
+ image: "traefik:v3.7.1",
+ serverId: undefined,
+ binds: [
+ "/etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml:ro",
+ "/etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic",
+ "/etc/dokploy/traefik/acme.json:/letsencrypt/acme.json",
+ "/var/run/docker.sock:/var/run/docker.sock:ro",
+ ],
+ networks: ["dokploy-network"],
+ labels: { "example.label": "preserved" },
+ serviceMounts: undefined,
+ restartPolicy: { Name: "unless-stopped" },
+ serviceNetworks: [],
+ servicePlacement: undefined,
+ serviceLabels: { "example.label": "preserved" },
+ serviceEndpointPorts: undefined,
+ });
+ expect(initializeTraefikServiceMock).not.toHaveBeenCalled();
+ });
+
+ test("captures standalone Traefik binds and networks in rollback snapshot", async () => {
+ resourceTypes["dokploy-traefik"] = "standalone";
+ execAsyncMock.mockImplementation(async (command: string) => {
+ const resourceName = command.match(/RESOURCE_NAME="([^"]+)"/)?.[1];
+ if (resourceName) {
+ return {
+ stdout: `${resourceTypes[resourceName] ?? "unknown"}\n`,
+ stderr: "",
+ };
+ }
+ if (command.includes("docker container inspect dokploy-traefik")) {
+ if (command.includes("'{{json .}}'")) {
+ return {
+ stdout: `${JSON.stringify({
+ State: { Running: true },
+ HostConfig: {
+ Binds: [
+ "/etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml:ro",
+ "/etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic",
+ "/etc/dokploy/traefik/acme.json:/letsencrypt/acme.json",
+ "/var/run/docker.sock:/var/run/docker.sock:ro",
+ ],
+ RestartPolicy: { Name: "always" },
+ },
+ NetworkSettings: { Networks: { "dokploy-network": {} } },
+ Config: { Labels: { "example.label": "preserved" } },
+ })}\n`,
+ stderr: "",
+ };
+ }
+ if (command.includes(".Config.Image")) {
+ return { stdout: "traefik:v3.7.1\n", stderr: "" };
+ }
+ }
+ if (command.includes("docker container inspect dokploy-traefik")) {
+ return { stdout: "{}\n", stderr: "" };
+ }
+ return { stdout: "[]\n", stderr: "" };
+ });
+
+ const snapshot = await getDockerResourceSnapshot("dokploy-traefik");
+
+ expect(snapshot.binds).toEqual([
+ "/etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml:ro",
+ "/etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic",
+ "/etc/dokploy/traefik/acme.json:/letsencrypt/acme.json",
+ "/var/run/docker.sock:/var/run/docker.sock:ro",
+ ]);
+ expect(snapshot.networks).toEqual(["dokploy-network"]);
+ expect(snapshot.labels).toEqual({ "example.label": "preserved" });
+ expect(snapshot.restartPolicy).toEqual({ Name: "always" });
+ });
+
+ test("recreates missing Traefik service with captured service shape", async () => {
+ await ensureTraefikRunningFromSnapshot({
+ resourceName: "dokploy-traefik",
+ resourceType: "service",
+ running: false,
+ replicas: 2,
+ env: "FOO=bar",
+ additionalPorts: [
+ { targetPort: 8080, publishedPort: 8080, protocol: "tcp" },
+ ],
+ image: "traefik:v3.7.1",
+ mounts: [
+ {
+ Type: "bind",
+ Source: "/etc/dokploy/traefik/acme.json",
+ Target: "/letsencrypt/acme.json",
+ },
+ ],
+ networks: [{ Target: "dokploy-network" }],
+ labels: { "service.label": "preserved" },
+ containerLabels: { "container.label": "preserved" },
+ placement: { Constraints: ["node.role==manager"] },
+ endpointPorts: [
+ {
+ TargetPort: 443,
+ PublishedPort: 443,
+ Protocol: "tcp",
+ PublishMode: "host",
+ },
+ ],
+ });
+
+ expect(initializeTraefikServiceMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ env: ["FOO=bar"],
+ replicas: 2,
+ serviceMounts: [
+ {
+ Type: "bind",
+ Source: "/etc/dokploy/traefik/acme.json",
+ Target: "/letsencrypt/acme.json",
+ },
+ ],
+ serviceNetworks: [{ Target: "dokploy-network" }],
+ serviceLabels: { "service.label": "preserved" },
+ serviceContainerLabels: { "container.label": "preserved" },
+ servicePlacement: { Constraints: ["node.role==manager"] },
+ serviceEndpointPorts: [
+ {
+ TargetPort: 443,
+ PublishedPort: 443,
+ Protocol: "tcp",
+ PublishMode: "host",
+ },
+ ],
+ }),
+ );
+ });
+
+ test("falls back to generic setup when exact recreation fails", async () => {
+ initializeStandaloneTraefikMock.mockRejectedValueOnce(
+ new Error("stale mount"),
+ );
+
+ await ensureTraefikRunningFromSnapshot({
+ resourceName: "dokploy-traefik",
+ resourceType: "standalone",
+ running: true,
+ image: "traefik:v3.7.1",
+ });
+
+ expect(initializeStandaloneTraefikMock).toHaveBeenCalledOnce();
+ expect(initializeTraefikServiceMock).toHaveBeenCalledWith(
+ expect.objectContaining({ image: "traefik:v3.7.1" }),
+ );
+ });
+
+ test("falls back to standalone recreation when no service shape can be inferred", async () => {
+ resourceTypes.dokploy = "unknown";
+
+ await ensureTraefikRunningFromSnapshot({
+ resourceName: "dokploy-traefik",
+ resourceType: "unknown",
+ running: false,
+ });
+
+ expect(initializeStandaloneTraefikMock).toHaveBeenCalledWith({
+ env: undefined,
+ additionalPorts: [],
+ image: undefined,
+ serverId: undefined,
+ });
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/migration/upstream-preflight.test.ts b/apps/dokploy/__test__/caddy/migration/upstream-preflight.test.ts
new file mode 100644
index 0000000000..0ae1d2d7f2
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/migration/upstream-preflight.test.ts
@@ -0,0 +1,270 @@
+import { fs, vol } from "memfs";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+vi.mock("node:fs", () => ({
+ ...fs,
+ default: fs,
+}));
+
+const execAsyncMock = vi.hoisted(() => vi.fn());
+const execAsyncRemoteMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server/utils/process/execAsync", () => ({
+ execAsync: execAsyncMock,
+ execAsyncRemote: execAsyncRemoteMock,
+}));
+
+import type { CaddyMigrationReport, CaddyRouteFragment } from "@dokploy/server";
+import {
+ getCaddyMigrationArtifactPaths,
+ runCaddyMigrationUpstreamPreflight,
+} from "@dokploy/server";
+
+const createReport = (migrationId: string): CaddyMigrationReport => {
+ const artifactPaths = getCaddyMigrationArtifactPaths(migrationId);
+ return {
+ migrationId,
+ serverId: null,
+ createdAt: "2026-05-22T00:00:00.000Z",
+ updatedAt: "2026-05-22T00:00:00.000Z",
+ status: "prepared",
+ sourceProvider: "traefik",
+ targetProvider: "caddy",
+ artifactPaths,
+ inputs: {
+ traefikStaticConfigPath: "",
+ traefikStaticConfigFound: true,
+ dynamicFiles: [],
+ dbApplicationDomains: 0,
+ dbComposeDomains: 0,
+ composeFilesScanned: [],
+ composeFilesSkipped: [],
+ },
+ summary: {
+ fragments: 1,
+ routes: 1,
+ warnings: 0,
+ blockingWarnings: 0,
+ },
+ validation: { status: "passed", message: "ok" },
+ warnings: [],
+ events: [],
+ };
+};
+
+const writeFragment = (
+ report: CaddyMigrationReport,
+ fragment: CaddyRouteFragment,
+) => {
+ vol.mkdirSync(report.artifactPaths.fragmentsDir, { recursive: true });
+ vol.writeFileSync(
+ `${report.artifactPaths.fragmentsDir}/${fragment.id}.json`,
+ `${JSON.stringify(fragment, null, 2)}\n`,
+ );
+};
+
+describe("Caddy migration upstream preflight", () => {
+ beforeEach(() => {
+ vol.reset();
+ vi.clearAllMocks();
+ execAsyncMock.mockResolvedValue({ stdout: "passed\n", stderr: "" });
+ execAsyncRemoteMock.mockResolvedValue({ stdout: "passed\n", stderr: "" });
+ });
+
+ test("deduplicates upstream probes while preserving every route reference", async () => {
+ const report = createReport("caddy-preflight-dedupe");
+ writeFragment(report, {
+ version: 1,
+ id: "manual",
+ source: "manual",
+ routes: [
+ {
+ id: "route-a",
+ source: "manual",
+ hosts: ["a.example.com"],
+ upstreams: ["http://app:3000"],
+ },
+ {
+ id: "route-b",
+ source: "manual",
+ hosts: ["b.example.com"],
+ upstreams: ["http://app:3000"],
+ },
+ ],
+ });
+
+ const preflight = await runCaddyMigrationUpstreamPreflight(report);
+
+ expect(preflight.status).toBe("passed");
+ expect(execAsyncMock).toHaveBeenCalledTimes(1);
+ expect(preflight.checks).toHaveLength(1);
+ expect(preflight.checks[0]).toMatchObject({
+ dial: "app:3000",
+ host: "app",
+ port: 3000,
+ network: "dokploy-network",
+ status: "passed",
+ routes: [
+ expect.objectContaining({ routeId: "route-a" }),
+ expect.objectContaining({ routeId: "route-b" }),
+ ],
+ });
+ });
+
+ test("reports invalid no-port upstreams without running Docker", async () => {
+ const report = createReport("caddy-preflight-invalid");
+ writeFragment(report, {
+ version: 1,
+ id: "manual",
+ source: "manual",
+ routes: [
+ {
+ id: "admin",
+ source: "manual",
+ hosts: ["admin.example.com"],
+ upstreams: ["http://admin"],
+ },
+ ],
+ });
+
+ const preflight = await runCaddyMigrationUpstreamPreflight(report);
+
+ expect(preflight.status).toBe("failed");
+ expect(execAsyncMock).not.toHaveBeenCalled();
+ expect(preflight.checks[0]).toMatchObject({
+ dial: "http://admin",
+ network: "dokploy-network",
+ status: "failed",
+ reason: expect.stringContaining("explicit port"),
+ routes: [expect.objectContaining({ routeId: "admin" })],
+ });
+ });
+
+ test("records DNS and TCP probe failures with route context", async () => {
+ const report = createReport("caddy-preflight-dns");
+ writeFragment(report, {
+ version: 1,
+ id: "manual",
+ source: "manual",
+ routes: [
+ {
+ id: "missing",
+ source: "manual",
+ hosts: ["missing.example.com"],
+ upstreams: ["http://missing:3000"],
+ },
+ ],
+ });
+ execAsyncMock.mockRejectedValueOnce(
+ Object.assign(new Error("probe failed"), { stdout: "dns_failed\n" }),
+ );
+
+ const preflight = await runCaddyMigrationUpstreamPreflight(report);
+
+ expect(preflight.status).toBe("failed");
+ expect(preflight.checks[0]).toMatchObject({
+ dial: "missing:3000",
+ host: "missing",
+ port: 3000,
+ network: "dokploy-network",
+ status: "failed",
+ reason: "DNS resolution failed",
+ routes: [expect.objectContaining({ routeId: "missing" })],
+ });
+ });
+
+ test("probes identical dials separately when route networks differ", async () => {
+ const report = createReport("caddy-preflight-networks");
+ writeFragment(report, {
+ version: 1,
+ id: "manual",
+ source: "manual",
+ routes: [
+ {
+ id: "shared-a",
+ source: "manual",
+ hosts: ["a.example.com"],
+ upstreams: ["http://web:8080"],
+ upstreamNetwork: "project-a",
+ },
+ {
+ id: "shared-b",
+ source: "manual",
+ hosts: ["b.example.com"],
+ upstreams: ["http://web:8080"],
+ upstreamNetwork: "project-b",
+ },
+ ],
+ });
+
+ const preflight = await runCaddyMigrationUpstreamPreflight(report);
+
+ expect(preflight.status).toBe("passed");
+ expect(preflight.network).toBe("mixed");
+ expect(preflight.networks).toEqual(["project-a", "project-b"]);
+ expect(preflight.checks).toHaveLength(2);
+ expect(execAsyncMock).toHaveBeenCalledTimes(2);
+ expect(execAsyncMock.mock.calls[0]?.[0]).toContain("--network project-a");
+ expect(execAsyncMock.mock.calls[1]?.[0]).toContain("--network project-b");
+ });
+
+ test("uses a one-shot container probe on the route network", async () => {
+ const report = createReport("caddy-preflight-service");
+ writeFragment(report, {
+ version: 1,
+ id: "manual",
+ source: "manual",
+ routes: [
+ {
+ id: "app",
+ source: "manual",
+ hosts: ["app.example.com"],
+ upstreams: ["http://app:3000"],
+ },
+ ],
+ });
+ execAsyncMock.mockResolvedValueOnce({ stdout: "passed\n", stderr: "" });
+
+ const preflight = await runCaddyMigrationUpstreamPreflight(report);
+
+ expect(preflight.status).toBe("passed");
+ expect(preflight.probeMode).toBe("standalone");
+ expect(execAsyncMock.mock.calls[0]?.[0]).toContain("docker run --rm");
+ expect(execAsyncMock.mock.calls[0]?.[0]).toContain(
+ "--network dokploy-network",
+ );
+ });
+
+ test("falls back to a temporary service probe when the overlay network is not attachable", async () => {
+ const report = createReport("caddy-preflight-service-fallback");
+ writeFragment(report, {
+ version: 1,
+ id: "manual",
+ source: "manual",
+ routes: [
+ {
+ id: "app",
+ source: "manual",
+ hosts: ["app.example.com"],
+ upstreams: ["http://app:3000"],
+ },
+ ],
+ });
+ execAsyncMock
+ .mockRejectedValueOnce(
+ Object.assign(new Error("docker run failed"), {
+ stderr:
+ "Error response from daemon: network dokploy-network is not manually attachable",
+ }),
+ )
+ .mockResolvedValueOnce({ stdout: "passed\n", stderr: "" });
+
+ const preflight = await runCaddyMigrationUpstreamPreflight(report);
+
+ expect(preflight.status).toBe("passed");
+ expect(preflight.probeMode).toBe("service");
+ expect(execAsyncMock).toHaveBeenCalledTimes(2);
+ expect(execAsyncMock.mock.calls[0]?.[0]).toContain("docker run --rm");
+ expect(execAsyncMock.mock.calls[1]?.[0]).toContain("docker service create");
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/preview-deployment.test.ts b/apps/dokploy/__test__/caddy/preview-deployment.test.ts
new file mode 100644
index 0000000000..c5d5db3cfa
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/preview-deployment.test.ts
@@ -0,0 +1,300 @@
+import { beforeEach, expect, test, vi } from "vitest";
+
+const previewDeploymentRow = {
+ previewDeploymentId: "preview-1",
+ applicationId: "app-1",
+ appName: "preview-my-app-fixedpw",
+ pullRequestCommentId: "comment-1",
+ domainId: null,
+};
+
+const insertedPreviewDeployment = { ...previewDeploymentRow };
+
+const dbMock = vi.hoisted(() => ({
+ query: {
+ organization: { findFirst: vi.fn() },
+ previewDeployments: { findFirst: vi.fn() },
+ },
+ insert: vi.fn(),
+ update: vi.fn(),
+ delete: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/db", () => ({
+ db: dbMock,
+}));
+
+vi.mock("@dokploy/server/templates", () => ({
+ generatePassword: vi.fn(() => "fixedpw"),
+}));
+
+vi.mock("@dokploy/server/services/application", () => ({
+ findApplicationById: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/services/deployment", () => ({
+ removeDeploymentsByPreviewDeploymentId: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/services/domain", () => ({
+ createDomain: vi.fn(),
+ removeDomainById: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/services/github", () => ({
+ getIssueComment: vi.fn(() => "preview comment"),
+}));
+
+vi.mock("@dokploy/server/services/web-server-settings", () => ({
+ getWebServerSettings: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/utils/docker/utils", () => ({
+ removeService: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/utils/filesystem/directory", () => ({
+ removeDirectoryCode: vi.fn(),
+}));
+
+const createCommentMock = vi.hoisted(() => vi.fn());
+const deleteCommentMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server/utils/providers/github", () => ({
+ authGithub: vi.fn(() => ({
+ rest: {
+ issues: {
+ createComment: createCommentMock,
+ deleteComment: deleteCommentMock,
+ },
+ },
+ })),
+}));
+
+vi.mock("@dokploy/server/utils/traefik/application", () => ({
+ removeTraefikConfig: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/utils/web-server/domain", () => ({
+ manageWebServerDomain: vi.fn(),
+ removeWebServerDomain: vi.fn(),
+}));
+
+import { findApplicationById } from "@dokploy/server/services/application";
+import {
+ createDomain,
+ removeDomainById,
+} from "@dokploy/server/services/domain";
+import {
+ createPreviewDeployment,
+ removePreviewDeployment,
+} from "@dokploy/server/services/preview-deployment";
+import {
+ manageWebServerDomain,
+ removeWebServerDomain,
+} from "@dokploy/server/utils/web-server/domain";
+
+const createApplication = () => ({
+ applicationId: "app-1",
+ appName: "my-app",
+ name: "My App",
+ owner: "dokploy",
+ repository: "dokploy",
+ github: {},
+ serverId: null,
+ server: {
+ ipAddress: "192.0.2.10",
+ },
+ previewWildcard: "*.sslip.io",
+ previewPath: "/",
+ previewPort: 3000,
+ previewHttps: true,
+ previewCertificateType: "letsencrypt",
+ previewCustomCertResolver: null,
+ environment: {
+ project: {
+ organizationId: "org-1",
+ },
+ },
+});
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ Object.assign(insertedPreviewDeployment, previewDeploymentRow);
+ dbMock.query.organization.findFirst.mockResolvedValue({ ownerId: "user-1" });
+ createCommentMock.mockResolvedValue({ data: { id: 12345 } });
+ dbMock.insert.mockReturnValue({
+ values: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([insertedPreviewDeployment]),
+ }),
+ });
+ dbMock.update.mockReturnValue({
+ set: vi.fn().mockReturnValue({
+ where: vi.fn().mockResolvedValue([]),
+ }),
+ });
+ dbMock.delete.mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([]),
+ }),
+ });
+ vi.mocked(removeDomainById).mockResolvedValue(undefined as never);
+ vi.mocked(removeWebServerDomain).mockResolvedValue(undefined as never);
+ deleteCommentMock.mockResolvedValue(undefined);
+});
+
+test("creates preview domains through the active web server provider", async () => {
+ const application = createApplication();
+ const domain = {
+ domainId: "domain-1",
+ host: "preview-my-app-fixedpw-192-0-2-10.sslip.io",
+ uniqueConfigKey: 17,
+ };
+ vi.mocked(findApplicationById).mockResolvedValue(application as never);
+ vi.mocked(createDomain).mockResolvedValue(domain as never);
+
+ await createPreviewDeployment({
+ applicationId: "app-1",
+ pullRequestId: "pr-1",
+ pullRequestNumber: "42",
+ branch: "feature/caddy-preview",
+ } as never);
+
+ expect(createDomain).toHaveBeenCalledWith(
+ expect.objectContaining({
+ host: "preview-my-app-fixedpw-192-0-2-10.sslip.io",
+ path: "/",
+ port: 3000,
+ https: true,
+ certificateType: "letsencrypt",
+ domainType: "preview",
+ previewDeploymentId: "preview-1",
+ }),
+ );
+ expect(manageWebServerDomain).toHaveBeenCalledWith(
+ expect.objectContaining({
+ applicationId: "app-1",
+ appName: "preview-my-app-fixedpw",
+ }),
+ domain,
+ );
+});
+
+test("cleans up preview domain state when provider route creation fails", async () => {
+ const application = createApplication();
+ const domain = {
+ domainId: "domain-1",
+ host: "preview-my-app-fixedpw-192-0-2-10.sslip.io",
+ uniqueConfigKey: 17,
+ };
+ vi.mocked(findApplicationById).mockResolvedValue(application as never);
+ vi.mocked(createDomain).mockResolvedValue(domain as never);
+ vi.mocked(manageWebServerDomain).mockRejectedValueOnce(
+ new Error("caddy reload failed") as never,
+ );
+
+ await expect(
+ createPreviewDeployment({
+ applicationId: "app-1",
+ pullRequestId: "pr-1",
+ pullRequestNumber: "42",
+ branch: "feature/caddy-preview",
+ } as never),
+ ).rejects.toThrow("caddy reload failed");
+
+ expect(removeDomainById).toHaveBeenCalledWith("domain-1");
+ expect(removeWebServerDomain).not.toHaveBeenCalled();
+ expect(dbMock.delete).toHaveBeenCalled();
+ expect(deleteCommentMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ owner: "dokploy",
+ repo: "dokploy",
+ comment_id: 12345,
+ }),
+ );
+});
+
+test("removes preview routes when preview domain linking fails after route creation", async () => {
+ const application = createApplication();
+ const domain = {
+ domainId: "domain-1",
+ host: "preview-my-app-fixedpw-192-0-2-10.sslip.io",
+ uniqueConfigKey: 17,
+ };
+ vi.mocked(findApplicationById).mockResolvedValue(application as never);
+ vi.mocked(createDomain).mockResolvedValue(domain as never);
+ dbMock.update.mockReturnValueOnce({
+ set: vi.fn().mockReturnValue({
+ where: vi.fn().mockRejectedValue(new Error("db update failed")),
+ }),
+ });
+
+ await expect(
+ createPreviewDeployment({
+ applicationId: "app-1",
+ pullRequestId: "pr-1",
+ pullRequestNumber: "42",
+ branch: "feature/caddy-preview",
+ } as never),
+ ).rejects.toThrow("db update failed");
+
+ expect(removeWebServerDomain).toHaveBeenCalledWith(
+ expect.objectContaining({
+ appName: "preview-my-app-fixedpw",
+ }),
+ 17,
+ );
+ expect(removeDomainById).toHaveBeenCalledWith("domain-1");
+ expect(dbMock.delete).toHaveBeenCalled();
+});
+
+test("removes preview domains through the active web server provider", async () => {
+ const application = createApplication();
+ vi.mocked(findApplicationById).mockResolvedValue(application as never);
+ dbMock.query.previewDeployments.findFirst.mockResolvedValue({
+ ...previewDeploymentRow,
+ domain: {
+ domainId: "domain-1",
+ uniqueConfigKey: 17,
+ },
+ application: {
+ applicationId: "app-1",
+ serverId: null,
+ },
+ });
+
+ await removePreviewDeployment("preview-1");
+
+ expect(removeWebServerDomain).toHaveBeenCalledWith(
+ expect.objectContaining({
+ applicationId: "app-1",
+ appName: "preview-my-app-fixedpw",
+ }),
+ 17,
+ );
+});
+
+test("keeps preview deployment row when provider route removal fails", async () => {
+ const application = createApplication();
+ vi.mocked(findApplicationById).mockResolvedValue(application as never);
+ dbMock.query.previewDeployments.findFirst.mockResolvedValue({
+ ...previewDeploymentRow,
+ domain: {
+ domainId: "domain-1",
+ uniqueConfigKey: 17,
+ },
+ application: {
+ applicationId: "app-1",
+ serverId: null,
+ },
+ });
+ vi.mocked(removeWebServerDomain).mockRejectedValueOnce(
+ new Error("caddy route cleanup failed") as never,
+ );
+
+ await expect(removePreviewDeployment("preview-1")).rejects.toThrow(
+ "caddy route cleanup failed",
+ );
+
+ expect(dbMock.delete).not.toHaveBeenCalled();
+});
diff --git a/apps/dokploy/__test__/caddy/provider-neutral-ui-contract.test.ts b/apps/dokploy/__test__/caddy/provider-neutral-ui-contract.test.ts
new file mode 100644
index 0000000000..f7411927c8
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/provider-neutral-ui-contract.test.ts
@@ -0,0 +1,114 @@
+import { readFileSync } from "node:fs";
+import { describe, expect, test } from "vitest";
+
+const readSource = (relativePath: string) =>
+ readFileSync(new URL(relativePath, import.meta.url), "utf8");
+
+describe("provider-neutral Caddy UI contract", () => {
+ test("labels the account menu file-browser link as web-server files", () => {
+ const source = readSource("../../components/layouts/user-nav.tsx");
+
+ expect(source).toContain("permissions?.traefikFiles.read");
+ expect(source).toContain('router.push("/dashboard/traefik")');
+ expect(source).toContain("Web Server Files");
+ expect(source).not.toMatch(/>\s*Traefik\s*);
+ });
+
+ test("keeps custom-role file-browser permission copy provider-neutral", () => {
+ const source = readSource(
+ "../../components/proprietary/roles/manage-custom-roles.tsx",
+ );
+
+ expect(source).toContain("traefikFiles:");
+ expect(source).toContain('label: "Web Server Files"');
+ expect(source).toContain(
+ 'description: "Access to the active web server file browser"',
+ );
+ expect(source).toContain(
+ 'description: "View active web server configuration files"',
+ );
+ expect(source).toContain(
+ 'description: "Edit and save active web server configuration files"',
+ );
+ expect(source).not.toContain("Traefik Files");
+ expect(source).not.toContain("Traefik file system configuration");
+ expect(source).not.toContain("Traefik configuration files");
+ });
+
+ test("uses generic copy while active web-server provider is unresolved", () => {
+ const actions = readSource(
+ "../../components/dashboard/settings/servers/actions/show-traefik-actions.tsx",
+ );
+ const envEditor = readSource(
+ "../../components/dashboard/settings/web-server/edit-web-server-env.tsx",
+ );
+ const fileSystem = readSource(
+ "../../components/dashboard/file-system/show-traefik-system.tsx",
+ );
+
+ expect(actions).toContain("EditWebServerEnv");
+ expect(actions).not.toContain("EditTraefikEnv");
+ expect(envEditor).toContain("readWebServerEnv");
+ expect(envEditor).toContain("writeWebServerEnv");
+ expect(envEditor).not.toContain("readTraefikEnv");
+ expect(envEditor).not.toContain("writeTraefikEnv");
+ expect(actions).toContain(': "Web Server";');
+ expect(actions).toMatch(
+ /const resourceName =[\s\S]*activeProvider === "caddy"[\s\S]*"dokploy-caddy"[\s\S]*activeProvider === "traefik"[\s\S]*"dokploy-traefik"[\s\S]*: null;/,
+ );
+ expect(actions).toContain("!activeProvider ||");
+ expect(actions).toContain("{resourceName && (");
+ expect(actions).toContain('activeProvider === "traefik" && (');
+ expect(fileSystem).toContain('const isTraefik = provider === "traefik";');
+ expect(fileSystem).toContain(
+ "Provider-specific edit controls appear after Dokploy resolves the active provider.",
+ );
+ });
+
+ test("keeps application advanced web-server config provider-neutral while provider is unresolved", () => {
+ const source = readSource(
+ "../../components/dashboard/application/advanced/traefik/show-traefik-config.tsx",
+ );
+
+ expect(source).toContain('const isCaddy = activeProvider === "caddy";');
+ expect(source).toContain('const isTraefik = activeProvider === "traefik";');
+ expect(source).toContain(': "Web Server";');
+ expect(source).toContain(
+ "Provider-specific edit controls appear after Dokploy resolves the active provider.",
+ );
+ expect(source).toContain("{isTraefik && (");
+ expect(source).not.toContain('activeProvider !== "caddy"');
+ expect(source).not.toContain(
+ 'activeProvider === "caddy" ? "Caddy" : "Traefik"',
+ );
+ });
+
+ test("keeps read-only web-server files scrollable", () => {
+ const editor = readSource("../../components/shared/code-editor.tsx");
+ const fileEditor = readSource(
+ "../../components/dashboard/file-system/show-traefik-file.tsx",
+ );
+
+ expect(editor).toContain("pointer-events-none absolute");
+ expect(fileEditor).toContain(
+ 'activeProvider === "caddy" ? "json" : "yaml"',
+ );
+ expect(fileEditor).toContain("disabled={!isTraefik || canEdit}");
+ });
+
+ test("keeps Caddy web-server settings cards padded", () => {
+ const providerSelector = readSource(
+ "../../components/dashboard/settings/web-server/web-server-provider-selector.tsx",
+ );
+ const migrationPanel = readSource(
+ "../../components/dashboard/settings/web-server/caddy-migration-panel.tsx",
+ );
+
+ expect(providerSelector).toContain('CardHeader className="pb-3"');
+ expect(providerSelector).toContain('CardContent className="space-y-3"');
+ expect(migrationPanel).toContain('CardHeader className="pb-3"');
+ expect(migrationPanel).toContain('CardContent className="space-y-4"');
+ expect(providerSelector).not.toContain('CardHeader className="px-0 pt-0"');
+ expect(migrationPanel).not.toContain('CardHeader className="px-0"');
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/request-log-cleanup.test.ts b/apps/dokploy/__test__/caddy/request-log-cleanup.test.ts
new file mode 100644
index 0000000000..888e3cf335
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/request-log-cleanup.test.ts
@@ -0,0 +1,102 @@
+import { beforeEach, expect, test, vi } from "vitest";
+
+const existsSyncMock = vi.hoisted(() => vi.fn());
+const execAsyncMock = vi.hoisted(() => vi.fn());
+const getWebServerSettingsMock = vi.hoisted(() => vi.fn());
+const updateWebServerSettingsMock = vi.hoisted(() => vi.fn());
+const resolveWebServerProviderMock = vi.hoisted(() => vi.fn());
+const scheduleJobMock = vi.hoisted(() => vi.fn());
+const scheduledJobsMock = vi.hoisted(() => ({}) as Record);
+let scheduledCallback: (() => Promise) | undefined;
+
+vi.mock("node:fs", () => ({
+ default: {
+ existsSync: existsSyncMock,
+ },
+ existsSync: existsSyncMock,
+}));
+
+vi.mock("node-schedule", () => ({
+ scheduledJobs: scheduledJobsMock,
+ scheduleJob: scheduleJobMock,
+}));
+
+vi.mock("@dokploy/server/constants", () => ({
+ ACCESS_LOG_RETAINED_LINES: 1000,
+ paths: () => ({
+ DYNAMIC_TRAEFIK_PATH: "/etc/dokploy/traefik/dynamic",
+ CADDY_ACCESS_LOG_PATH: "/etc/dokploy/caddy/access.log",
+ }),
+}));
+
+vi.mock("@dokploy/server/services/web-server-settings", () => ({
+ getWebServerSettings: getWebServerSettingsMock,
+ updateWebServerSettings: updateWebServerSettingsMock,
+ resolveWebServerProvider: resolveWebServerProviderMock,
+}));
+
+vi.mock("@dokploy/server/utils/process/execAsync", () => ({
+ execAsync: execAsyncMock,
+}));
+
+import { startLogCleanup } from "@dokploy/server/utils/access-log/handler";
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ for (const key of Object.keys(scheduledJobsMock)) {
+ delete scheduledJobsMock[key];
+ }
+ scheduledCallback = undefined;
+ existsSyncMock.mockReturnValue(true);
+ execAsyncMock.mockResolvedValue({ stdout: "", stderr: "" });
+ updateWebServerSettingsMock.mockResolvedValue({});
+ scheduleJobMock.mockImplementation((name, _cron, callback) => {
+ if (name === "access-log-cleanup") {
+ scheduledCallback = callback;
+ }
+ return { cancel: vi.fn() };
+ });
+});
+
+test("keeps Traefik access-log cleanup and signal behavior", async () => {
+ resolveWebServerProviderMock.mockResolvedValue("traefik");
+
+ await startLogCleanup("0 0 * * *");
+ await scheduledCallback?.();
+
+ expect(execAsyncMock).toHaveBeenNthCalledWith(
+ 1,
+ "tail -n 1000 /etc/dokploy/traefik/dynamic/access.log > /etc/dokploy/traefik/dynamic/access.log.tmp && mv /etc/dokploy/traefik/dynamic/access.log.tmp /etc/dokploy/traefik/dynamic/access.log",
+ );
+ expect(execAsyncMock).toHaveBeenNthCalledWith(
+ 2,
+ "docker exec dokploy-traefik kill -USR1 1",
+ );
+});
+
+test("cleans Caddy access logs without signaling Traefik", async () => {
+ resolveWebServerProviderMock.mockResolvedValue("caddy");
+
+ await startLogCleanup("0 0 * * *");
+ await scheduledCallback?.();
+
+ expect(execAsyncMock).toHaveBeenCalledTimes(1);
+ expect(execAsyncMock).toHaveBeenCalledWith(
+ "tail -n 1000 /etc/dokploy/caddy/access.log > /etc/dokploy/caddy/access.log.tmp && cat /etc/dokploy/caddy/access.log.tmp > /etc/dokploy/caddy/access.log && rm /etc/dokploy/caddy/access.log.tmp",
+ );
+ expect(execAsyncMock).not.toHaveBeenCalledWith(
+ "docker exec dokploy-traefik kill -USR1 1",
+ );
+});
+
+test("does not persist invalid cleanup cron schedules", async () => {
+ const existingCancel = vi.fn();
+ scheduledJobsMock["access-log-cleanup"] = { cancel: existingCancel };
+ scheduleJobMock.mockReturnValueOnce(null);
+
+ const result = await startLogCleanup("not a cron");
+
+ expect(result).toBe(false);
+ expect(existingCancel).not.toHaveBeenCalled();
+ expect(updateWebServerSettingsMock).not.toHaveBeenCalled();
+});
diff --git a/apps/dokploy/__test__/caddy/request-log-utils.test.ts b/apps/dokploy/__test__/caddy/request-log-utils.test.ts
new file mode 100644
index 0000000000..fdb328e993
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/request-log-utils.test.ts
@@ -0,0 +1,110 @@
+import { parseRawConfig, processLogs } from "@dokploy/server";
+import { expect, test } from "vitest";
+
+const caddyLogEntry = JSON.stringify({
+ level: "info",
+ ts: 1_724_603_677.306,
+ logger: "http.log.access",
+ msg: "handled request",
+ server_name: "http",
+ request: {
+ remote_ip: "192.0.2.10",
+ remote_port: "54321",
+ client_ip: "198.51.100.20",
+ proto: "HTTP/2.0",
+ method: "GET",
+ host: "app.example.com",
+ uri: "/dashboard?_rsc=1",
+ tls: {
+ resumed: false,
+ },
+ headers: {
+ "User-Agent": ["curl/8.0"],
+ },
+ },
+ bytes_read: 7,
+ duration: 0.014729375,
+ size: 1234,
+ status: 200,
+});
+
+const traefikLogEntry = `{"ClientAddr":"172.19.0.1:56732","ClientHost":"172.19.0.1","ClientPort":"56732","ClientUsername":"-","DownstreamContentSize":0,"DownstreamStatus":304,"Duration":14729375,"OriginContentSize":0,"OriginDuration":14051833,"OriginStatus":304,"Overhead":677542,"RequestAddr":"app.traefik.test","RequestContentSize":0,"RequestCount":122,"RequestHost":"app.traefik.test","RequestMethod":"GET","RequestPath":"/dashboard?_rsc=1rugv","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"app-router@docker","ServiceAddr":"10.0.1.15:3000","ServiceName":"app-service@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.15:3000","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T04:34:37.306691884Z","StartUTC":"2024-08-25T04:34:37.306691884Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T04:34:37Z"}`;
+
+test("normalizes Caddy JSON access logs into the Requests table shape", () => {
+ const result = parseRawConfig(caddyLogEntry);
+
+ expect(result.totalCount).toBe(1);
+ expect(result.data[0]).toMatchObject({
+ Provider: "caddy",
+ ClientAddr: "192.0.2.10:54321",
+ ClientHost: "198.51.100.20",
+ ClientPort: "54321",
+ DownstreamStatus: 200,
+ Duration: 14_729_375,
+ RequestHost: "app.example.com",
+ RequestMethod: "GET",
+ RequestPath: "/dashboard?_rsc=1",
+ RequestPort: "443",
+ RequestProtocol: "HTTP/2.0",
+ RequestScheme: "https",
+ ServiceName: "http",
+ request_User_Agent: "curl/8.0",
+ });
+});
+
+test("normalizes Caddy user-agent headers case-insensitively", () => {
+ const result = parseRawConfig(
+ JSON.stringify({
+ ...JSON.parse(caddyLogEntry),
+ request: {
+ ...JSON.parse(caddyLogEntry).request,
+ headers: {
+ "user-agent": ["lowercase-agent"],
+ },
+ },
+ }),
+ );
+
+ expect(result.data[0]?.request_User_Agent).toBe("lowercase-agent");
+});
+
+test("filters and groups normalized Caddy request logs", () => {
+ const rawConfig = `${caddyLogEntry}\n${JSON.stringify({
+ ...JSON.parse(caddyLogEntry),
+ ts: 1_724_607_277.306,
+ status: 404,
+ request: {
+ ...JSON.parse(caddyLogEntry).request,
+ host: "api.example.com",
+ uri: "/missing",
+ tls: undefined,
+ },
+ })}`;
+
+ expect(
+ parseRawConfig(rawConfig, undefined, undefined, "app.example", ["success"])
+ .totalCount,
+ ).toBe(1);
+ expect(
+ parseRawConfig(rawConfig, undefined, undefined, "api.example", ["client"])
+ .totalCount,
+ ).toBe(1);
+ expect(
+ processLogs(rawConfig, {
+ start: "2024-08-25T16:00:00.000Z",
+ end: "2024-08-25T17:00:00.000Z",
+ }),
+ ).toEqual([{ hour: "2024-08-25T16:00:00Z", count: 1 }]);
+});
+
+test("preserves existing Traefik request log parsing", () => {
+ const result = parseRawConfig(traefikLogEntry);
+
+ expect(result.totalCount).toBe(1);
+ expect(result.data[0]).toMatchObject({
+ Provider: "traefik",
+ RequestHost: "app.traefik.test",
+ DownstreamStatus: 304,
+ Duration: 14_729_375,
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/request-logs-router.test.ts b/apps/dokploy/__test__/caddy/request-logs-router.test.ts
new file mode 100644
index 0000000000..dbd0880208
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/request-logs-router.test.ts
@@ -0,0 +1,378 @@
+import { mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { afterEach, beforeEach, expect, test, vi } from "vitest";
+
+const serverMocks = vi.hoisted(() => ({
+ applyCaddyMigration: vi.fn(),
+ checkGPUStatus: vi.fn(),
+ checkPortInUse: vi.fn(),
+ checkPostgresHealth: vi.fn(),
+ checkRedisHealth: vi.fn(),
+ checkWebServerHealth: vi.fn(),
+ cleanupAll: vi.fn(),
+ cleanupAllBackground: vi.fn(),
+ cleanupBuilders: vi.fn(),
+ cleanupContainers: vi.fn(),
+ cleanupImages: vi.fn(),
+ cleanupSystem: vi.fn(),
+ cleanupVolumes: vi.fn(),
+ compileAndWriteCaddyConfig: vi.fn(),
+ compileWriteAndValidateCaddyConfigSafely: vi.fn(),
+ compileWriteAndReloadCaddyConfigSafely: vi.fn(),
+ execAsync: vi.fn(),
+ findServerById: vi.fn(),
+ getCaddyCompileSettings: vi.fn(),
+ getCaddyMigrationReport: vi.fn(),
+ getCaddyTrustedProxySettings: vi.fn(),
+ getDockerDiskUsage: vi.fn(),
+ getDokployImageTag: vi.fn(),
+ getLogCleanupStatus: vi.fn(),
+ getUpdateData: vi.fn(),
+ getWebServerPaths: vi.fn(),
+ getWebServerResourceName: vi.fn(),
+ getWebServerSettings: vi.fn(),
+ isCaddyAdminAdditionalPort: vi.fn(),
+ isCaddyReservedAdditionalPort: vi.fn(),
+ parseRawConfig: vi.fn(),
+ paths: vi.fn(),
+ prepareCaddyMigration: vi.fn(),
+ prepareEnvironmentVariables: vi.fn(),
+ processLogs: vi.fn(),
+ readCaddyConfigFileIfExists: vi.fn(),
+ readConfig: vi.fn(),
+ readConfigInPath: vi.fn(),
+ readDirectory: vi.fn(),
+ readEnvironmentVariables: vi.fn(),
+ readMainConfig: vi.fn(),
+ readMonitoringConfig: vi.fn(),
+ readPorts: vi.fn(),
+ recreateDirectory: vi.fn(),
+ reloadCaddyAfterValidation: vi.fn(),
+ reloadDockerResource: vi.fn(),
+ resolveWebServerProvider: vi.fn(),
+ rollbackCaddyMigration: vi.fn(),
+ sendDockerCleanupNotifications: vi.fn(),
+ setupGPUSupport: vi.fn(),
+ spawnAsync: vi.fn(),
+ startLogCleanup: vi.fn(),
+ stopLogCleanup: vi.fn(),
+ updateCaddyTrustedProxySettings: vi.fn(),
+ updateLetsEncryptEmail: vi.fn(),
+ updateLocalWebServerProvider: vi.fn(),
+ updateRemoteWebServerProvider: vi.fn(),
+ updateServerById: vi.fn(),
+ updateServerCaddy: vi.fn(),
+ updateServerTraefik: vi.fn(),
+ updateWebServerSettings: vi.fn(),
+ writeConfig: vi.fn(),
+ writeMainConfig: vi.fn(),
+ writeTraefikConfigInPath: vi.fn(),
+ writeTraefikSetup: vi.fn(),
+ writeWebServerSetup: vi.fn(),
+}));
+
+vi.mock("@dokploy/server", () => ({
+ ...serverMocks,
+ ACCESS_LOG_RETAINED_LINES: 1000,
+ CLEANUP_CRON_JOB: "cleanup",
+ DEFAULT_UPDATE_DATA: { latestVersion: null, updateAvailable: false },
+ IS_CLOUD: false,
+}));
+
+vi.mock("@dokploy/trpc-openapi", () => ({
+ generateOpenApiDocument: vi.fn(),
+}));
+
+vi.mock("@/server/api/root", () => ({
+ appRouter: {},
+}));
+
+vi.mock("@/server/api/utils/audit", () => ({
+ audit: vi.fn(),
+}));
+
+vi.mock("@/server/queues/queueSetup", () => ({
+ cleanAllDeploymentQueue: vi.fn(),
+}));
+
+vi.mock("@/server/utils/backup", () => ({
+ removeJob: vi.fn(),
+ schedule: vi.fn(),
+}));
+
+import {
+ checkPortInUse,
+ compileAndWriteCaddyConfig,
+ compileWriteAndValidateCaddyConfigSafely,
+ getCaddyCompileSettings,
+ getWebServerResourceName,
+ getWebServerSettings,
+ parseRawConfig,
+ paths,
+ prepareEnvironmentVariables,
+ processLogs,
+ readEnvironmentVariables,
+ readMainConfig,
+ readMonitoringConfig,
+ readPorts,
+ resolveWebServerProvider,
+ updateWebServerSettings,
+ writeMainConfig,
+ writeWebServerSetup,
+} from "@dokploy/server";
+import { apiUpdateWebServerSettings } from "@dokploy/server/db/schema";
+import { settingsRouter } from "@/server/api/routers/settings";
+import { audit } from "@/server/api/utils/audit";
+
+const caller = settingsRouter.createCaller({
+ session: {
+ userId: "user-1",
+ activeOrganizationId: "org-1",
+ },
+ user: {
+ id: "user-1",
+ role: "owner",
+ ownerId: "user-1",
+ email: "owner@example.com",
+ enableEnterpriseFeatures: true,
+ isValidEnterpriseLicense: true,
+ },
+ req: { headers: {} },
+ res: {},
+} as never);
+
+const tempRoot = join(tmpdir(), "dokploy-caddy-request-logs-test");
+const caddyAccessLogPath = join(tempRoot, "caddy", "access.log");
+
+let requestLogsEnabled = false;
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ rmSync(tempRoot, { recursive: true, force: true });
+ mkdirSync(dirname(caddyAccessLogPath), { recursive: true });
+ requestLogsEnabled = false;
+ vi.mocked(paths).mockReturnValue({
+ DYNAMIC_TRAEFIK_PATH: join(tempRoot, "traefik", "dynamic"),
+ CADDY_ACCESS_LOG_PATH: caddyAccessLogPath,
+ } as never);
+ vi.mocked(getWebServerResourceName).mockImplementation((provider) =>
+ provider === "caddy" ? "dokploy-caddy" : "dokploy-traefik",
+ );
+ vi.mocked(getWebServerSettings).mockImplementation(
+ async () => ({ requestLogsEnabled }) as never,
+ );
+ vi.mocked(updateWebServerSettings).mockImplementation(async (updates) => {
+ if ("requestLogsEnabled" in updates) {
+ requestLogsEnabled = Boolean(updates.requestLogsEnabled);
+ }
+ return { requestLogsEnabled } as never;
+ });
+ vi.mocked(getCaddyCompileSettings).mockImplementation(async () => ({
+ letsEncryptEmail: "ops@example.com",
+ accessLogs: requestLogsEnabled ? { enabled: true } : null,
+ }));
+ vi.mocked(compileAndWriteCaddyConfig).mockResolvedValue({} as never);
+ vi.mocked(compileWriteAndValidateCaddyConfigSafely).mockResolvedValue(
+ {} as never,
+ );
+ vi.mocked(checkPortInUse).mockResolvedValue({ isInUse: false } as never);
+ vi.mocked(readEnvironmentVariables).mockResolvedValue("CADDY_ENV=1");
+ vi.mocked(prepareEnvironmentVariables).mockReturnValue(["CADDY_ENV=1"]);
+ vi.mocked(readPorts).mockResolvedValue([
+ { targetPort: 80, publishedPort: 80, protocol: "tcp" },
+ ] as never);
+ vi.mocked(writeWebServerSetup).mockResolvedValue(undefined as never);
+ vi.mocked(readMainConfig).mockReturnValue("entryPoints: {}\n");
+ vi.mocked(readMonitoringConfig).mockResolvedValue("traefik-log\n");
+ vi.mocked(parseRawConfig).mockReturnValue({
+ data: [],
+ totalCount: 1,
+ } as never);
+ vi.mocked(processLogs).mockReturnValue([
+ { hour: "2026-06-02T00:00:00Z", count: 1 },
+ ] as never);
+});
+
+afterEach(() => {
+ rmSync(tempRoot, { recursive: true, force: true });
+});
+
+test("keeps Traefik request toggles on the existing Traefik YAML path", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("traefik");
+
+ await caller.toggleRequests({ enable: true });
+
+ expect(writeMainConfig).toHaveBeenCalledWith(
+ expect.stringContaining("/etc/dokploy/traefik/dynamic/access.log"),
+ );
+ expect(compileAndWriteCaddyConfig).not.toHaveBeenCalled();
+ expect(compileWriteAndValidateCaddyConfigSafely).not.toHaveBeenCalled();
+ expect(audit).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ resourceName: "toggle-requests" }),
+ );
+});
+
+test("reports Caddy request analytics state from persisted settings", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ requestLogsEnabled = true;
+
+ const state = await caller.getRequestAnalyticsState();
+
+ expect(state).toEqual({
+ provider: "caddy",
+ enabled: true,
+ reloadResourceName: "dokploy-caddy",
+ accessLogPath: caddyAccessLogPath,
+ });
+ expect(readMainConfig).not.toHaveBeenCalled();
+});
+
+test("toggles Caddy request logs without writing Traefik main config", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+
+ await caller.toggleRequests({ enable: true });
+
+ expect(updateWebServerSettings).toHaveBeenCalledWith({
+ requestLogsEnabled: true,
+ });
+ expect(getCaddyCompileSettings).toHaveBeenCalledWith();
+ expect(compileWriteAndValidateCaddyConfigSafely).toHaveBeenCalledWith({
+ letsEncryptEmail: "ops@example.com",
+ accessLogs: { enabled: true },
+ });
+ expect(compileAndWriteCaddyConfig).not.toHaveBeenCalled();
+ expect(writeMainConfig).not.toHaveBeenCalled();
+ expect(requestLogsEnabled).toBe(true);
+});
+
+test("preserves Caddy request-log settings when rewriting web-server setup", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ requestLogsEnabled = true;
+
+ await caller.writeWebServerEnv({ env: "CADDY_ENV=1" });
+
+ expect(writeWebServerSetup).toHaveBeenCalledWith(
+ "caddy",
+ expect.objectContaining({
+ env: ["CADDY_ENV=1"],
+ additionalPorts: [{ targetPort: 80, publishedPort: 80, protocol: "tcp" }],
+ letsEncryptEmail: "ops@example.com",
+ accessLogs: { enabled: true },
+ }),
+ );
+
+ vi.mocked(writeWebServerSetup).mockClear();
+ await caller.updateWebServerPorts({
+ additionalPorts: [
+ { targetPort: 9000, publishedPort: 9000, protocol: "tcp" },
+ ],
+ });
+
+ expect(checkPortInUse).toHaveBeenCalledWith(9000, undefined);
+ expect(writeWebServerSetup).toHaveBeenCalledWith(
+ "caddy",
+ expect.objectContaining({
+ env: ["CADDY_ENV=1"],
+ additionalPorts: [
+ { targetPort: 9000, publishedPort: 9000, protocol: "tcp" },
+ ],
+ letsEncryptEmail: "ops@example.com",
+ accessLogs: { enabled: true },
+ }),
+ );
+});
+
+test("restores Caddy request-log setting when generated config write fails", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ requestLogsEnabled = true;
+ vi.mocked(compileWriteAndValidateCaddyConfigSafely).mockRejectedValueOnce(
+ new Error("caddy write failed") as never,
+ );
+
+ await expect(caller.toggleRequests({ enable: false })).rejects.toThrow(
+ "caddy write failed",
+ );
+
+ expect(updateWebServerSettings).toHaveBeenNthCalledWith(1, {
+ requestLogsEnabled: false,
+ });
+ expect(updateWebServerSettings).toHaveBeenNthCalledWith(2, {
+ requestLogsEnabled: true,
+ });
+ expect(requestLogsEnabled).toBe(true);
+ expect(audit).not.toHaveBeenCalled();
+});
+
+test("does not allow generic web-server settings updates to toggle Caddy request logs", () => {
+ const parsed = apiUpdateWebServerSettings.parse({
+ requestLogsEnabled: true,
+ });
+
+ expect(parsed).not.toHaveProperty("requestLogsEnabled");
+});
+
+test("reads Caddy request stats from the Caddy access-log path", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ writeFileSync(caddyAccessLogPath, '{"caddy":true}\n');
+
+ const logs = await caller.readStatsLogs({
+ page: { pageIndex: 0, pageSize: 10 },
+ });
+ const stats = await caller.readStats();
+
+ expect(readMonitoringConfig).not.toHaveBeenCalled();
+ expect(parseRawConfig).toHaveBeenCalledWith(
+ '{"caddy":true}\n',
+ { pageIndex: 0, pageSize: 10 },
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ );
+ expect(processLogs).toHaveBeenCalledWith('{"caddy":true}\n', undefined);
+ expect(logs.totalCount).toBe(1);
+ expect(stats).toEqual([{ hour: "2026-06-02T00:00:00Z", count: 1 }]);
+});
+
+test("reads the latest Caddy request log entries for paginated stats", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ writeFileSync(
+ caddyAccessLogPath,
+ Array.from({ length: 1001 }, (_, index) =>
+ JSON.stringify({ caddy: true, index }),
+ ).join("\n"),
+ );
+
+ await caller.readStatsLogs({
+ page: { pageIndex: 0, pageSize: 10 },
+ });
+
+ const rawLog = vi.mocked(parseRawConfig).mock.calls[0]?.[0] as string;
+ expect(rawLog.split("\n").filter(Boolean)).toHaveLength(1000);
+ expect(rawLog).not.toContain('"index":0');
+ expect(rawLog).toContain('"index":1000');
+});
+
+test("bounds Caddy date-range request stats reads to the retained line window", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ writeFileSync(
+ caddyAccessLogPath,
+ Array.from({ length: 1001 }, (_, index) =>
+ JSON.stringify({ caddy: true, index }),
+ ).join("\n"),
+ );
+
+ await caller.readStats({
+ dateRange: {
+ start: "2026-06-02T00:00:00.000Z",
+ end: "2026-06-02T23:59:59.999Z",
+ },
+ });
+
+ const rawLog = vi.mocked(processLogs).mock.calls[0]?.[0] as string;
+ expect(rawLog.split("\n").filter(Boolean)).toHaveLength(1000);
+ expect(rawLog).not.toContain('"index":0');
+ expect(rawLog).toContain('"index":1000');
+});
diff --git a/apps/dokploy/__test__/caddy/setup.test.ts b/apps/dokploy/__test__/caddy/setup.test.ts
new file mode 100644
index 0000000000..586ea9b805
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/setup.test.ts
@@ -0,0 +1,230 @@
+import { afterEach, describe, expect, test, vi } from "vitest";
+
+const validateCaddyConfigWithContainerMock = vi.hoisted(() => vi.fn());
+const ensureDefaultCaddyConfigMock = vi.hoisted(() => vi.fn());
+const getRemoteDockerMock = vi.hoisted(() => vi.fn());
+
+vi.mock("@dokploy/server/utils/caddy/config", () => ({
+ ensureDefaultCaddyConfig: ensureDefaultCaddyConfigMock,
+ reloadCaddyAfterValidation: vi.fn(),
+ validateCaddyConfigWithContainer: validateCaddyConfigWithContainerMock,
+}));
+
+vi.mock("@dokploy/server/utils/servers/remote-docker", () => ({
+ getRemoteDocker: getRemoteDockerMock,
+}));
+
+const loadCaddySetup = async () => {
+ vi.resetModules();
+ vi.unstubAllEnvs();
+ vi.stubEnv("CADDY_VERSION", "");
+ return import("@dokploy/server/setup/caddy-setup");
+};
+
+afterEach(() => {
+ vi.unstubAllEnvs();
+ vi.clearAllMocks();
+});
+
+describe("Caddy runtime setup", () => {
+ test("defaults to the pinned Caddy 2.11.3 image tag", async () => {
+ const { CADDY_VERSION } = await loadCaddySetup();
+
+ expect(CADDY_VERSION).toBe("2.11.3");
+ });
+
+ test("uses the pinned default image for standalone Caddy", async () => {
+ const createContainer = vi.fn();
+ const start = vi.fn();
+ const remove = vi.fn().mockRejectedValue(new Error("missing"));
+ const docker = {
+ pull: vi.fn(
+ (_imageName: string, callback: (error: Error | null) => void) => {
+ callback(null);
+ },
+ ),
+ modem: { followProgress: vi.fn() },
+ createContainer,
+ getContainer: vi.fn(() => ({ remove, start })),
+ };
+ getRemoteDockerMock.mockResolvedValue(docker);
+ ensureDefaultCaddyConfigMock.mockResolvedValue(undefined);
+ validateCaddyConfigWithContainerMock.mockResolvedValue(undefined);
+ const { initializeStandaloneCaddy } = await loadCaddySetup();
+
+ await initializeStandaloneCaddy();
+
+ expect(docker.pull).toHaveBeenCalledWith(
+ "caddy:2.11.3",
+ expect.any(Function),
+ );
+ expect(createContainer).toHaveBeenCalledWith(
+ expect.objectContaining({
+ Image: "caddy:2.11.3",
+ HostConfig: expect.objectContaining({
+ Binds: expect.arrayContaining([
+ expect.stringMatching(/\/caddy:\/etc\/caddy$/),
+ expect.stringMatching(/\/certificates:.*\/certificates:ro$/),
+ ]),
+ }),
+ }),
+ );
+ const binds = (createContainer.mock.calls[0]?.[0] as any).HostConfig.Binds;
+ expect(binds).toEqual(
+ expect.arrayContaining([expect.stringMatching(/\/caddy:\/etc\/caddy$/)]),
+ );
+ expect(binds).not.toEqual(
+ expect.arrayContaining([
+ expect.stringMatching(
+ /\/caddy\/caddy\.json:\/etc\/caddy\/caddy\.json$/,
+ ),
+ ]),
+ );
+ });
+
+ test("does not publish the Caddy admin port for standalone Caddy", async () => {
+ const createContainer = vi.fn();
+ const start = vi.fn();
+ const remove = vi.fn().mockRejectedValue(new Error("missing"));
+ const docker = {
+ pull: vi.fn(
+ (_imageName: string, callback: (error: Error | null) => void) => {
+ callback(null);
+ },
+ ),
+ modem: { followProgress: vi.fn() },
+ createContainer,
+ getContainer: vi.fn(() => ({ remove, start })),
+ };
+ getRemoteDockerMock.mockResolvedValue(docker);
+ ensureDefaultCaddyConfigMock.mockResolvedValue(undefined);
+ validateCaddyConfigWithContainerMock.mockResolvedValue(undefined);
+ const { initializeStandaloneCaddy } = await loadCaddySetup();
+
+ await initializeStandaloneCaddy({
+ additionalPorts: [
+ { targetPort: 2019, publishedPort: 2019, protocol: "tcp" },
+ { targetPort: 8080, publishedPort: 18080, protocol: "tcp" },
+ { targetPort: 8082, publishedPort: 18082, protocol: "tcp" },
+ { targetPort: 9000, publishedPort: 9000, protocol: "tcp" },
+ ],
+ });
+
+ const createOptions = createContainer.mock.calls[0]?.[0] as any;
+ expect(createOptions.ExposedPorts).not.toHaveProperty("2019/tcp");
+ expect(createOptions.HostConfig.PortBindings).not.toHaveProperty(
+ "2019/tcp",
+ );
+ expect(createOptions.ExposedPorts).toHaveProperty("8080/tcp");
+ expect(createOptions.HostConfig.PortBindings["8080/tcp"]).toEqual([
+ { HostPort: "18080" },
+ ]);
+ expect(createOptions.ExposedPorts).toHaveProperty("8082/tcp");
+ expect(createOptions.HostConfig.PortBindings["8082/tcp"]).toEqual([
+ { HostPort: "18082" },
+ ]);
+ expect(createOptions.ExposedPorts).toHaveProperty("9000/tcp");
+ expect(createOptions.HostConfig.PortBindings["9000/tcp"]).toEqual([
+ { HostPort: "9000" },
+ ]);
+ });
+
+ test("passes access-log settings into standalone Caddy config generation", async () => {
+ const createContainer = vi.fn();
+ const start = vi.fn();
+ const remove = vi.fn().mockRejectedValue(new Error("missing"));
+ const docker = {
+ pull: vi.fn(
+ (_imageName: string, callback: (error: Error | null) => void) => {
+ callback(null);
+ },
+ ),
+ modem: { followProgress: vi.fn() },
+ createContainer,
+ getContainer: vi.fn(() => ({ remove, start })),
+ };
+ getRemoteDockerMock.mockResolvedValue(docker);
+ ensureDefaultCaddyConfigMock.mockResolvedValue(undefined);
+ validateCaddyConfigWithContainerMock.mockResolvedValue(undefined);
+ const { initializeStandaloneCaddy } = await loadCaddySetup();
+
+ await initializeStandaloneCaddy({
+ letsEncryptEmail: "ops@example.com",
+ accessLogs: { enabled: true },
+ });
+
+ expect(ensureDefaultCaddyConfigMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ letsEncryptEmail: "ops@example.com",
+ accessLogs: { enabled: true },
+ }),
+ );
+ });
+
+ test("does not publish the Caddy admin port for Caddy services", async () => {
+ const createService = vi.fn();
+ const docker = {
+ pull: vi.fn(
+ (_imageName: string, callback: (error: Error | null) => void) => {
+ callback(null);
+ },
+ ),
+ modem: { followProgress: vi.fn() },
+ createService,
+ getService: vi.fn(() => ({
+ inspect: vi.fn().mockRejectedValue(new Error("missing")),
+ })),
+ };
+ getRemoteDockerMock.mockResolvedValue(docker);
+ ensureDefaultCaddyConfigMock.mockResolvedValue(undefined);
+ const { initializeCaddyService } = await loadCaddySetup();
+
+ await initializeCaddyService({
+ accessLogs: { enabled: true },
+ additionalPorts: [
+ { targetPort: 2019, publishedPort: 2019, protocol: "tcp" },
+ { targetPort: 8080, publishedPort: 18080, protocol: "tcp" },
+ { targetPort: 8082, publishedPort: 18082, protocol: "tcp" },
+ { targetPort: 9000, publishedPort: 9000, protocol: "tcp" },
+ ],
+ });
+
+ const createOptions = createService.mock.calls[0]?.[0] as any;
+ expect(createOptions.TaskTemplate.ContainerSpec.Mounts).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ Type: "bind",
+ Source: expect.stringMatching(/\/caddy$/),
+ Target: "/etc/caddy",
+ }),
+ ]),
+ );
+ expect(createOptions.EndpointSpec.Ports).not.toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ TargetPort: 2019, Protocol: "tcp" }),
+ ]),
+ );
+ expect(createOptions.EndpointSpec.Ports).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ TargetPort: 8080,
+ PublishedPort: 18080,
+ Protocol: "tcp",
+ }),
+ expect.objectContaining({
+ TargetPort: 8082,
+ PublishedPort: 18082,
+ Protocol: "tcp",
+ }),
+ expect.objectContaining({
+ TargetPort: 9000,
+ PublishedPort: 9000,
+ Protocol: "tcp",
+ }),
+ ]),
+ );
+ expect(ensureDefaultCaddyConfigMock).toHaveBeenCalledWith(
+ expect.objectContaining({ accessLogs: { enabled: true } }),
+ );
+ });
+});
diff --git a/apps/dokploy/__test__/caddy/trusted-proxy-settings-router.test.ts b/apps/dokploy/__test__/caddy/trusted-proxy-settings-router.test.ts
new file mode 100644
index 0000000000..12aed5ac98
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/trusted-proxy-settings-router.test.ts
@@ -0,0 +1,534 @@
+import { beforeEach, expect, test, vi } from "vitest";
+
+const serverMocks = vi.hoisted(() => ({
+ applyCaddyMigration: vi.fn(),
+ checkGPUStatus: vi.fn(),
+ checkPortInUse: vi.fn(),
+ checkPostgresHealth: vi.fn(),
+ checkRedisHealth: vi.fn(),
+ checkWebServerHealth: vi.fn(),
+ cleanupAll: vi.fn(),
+ cleanupAllBackground: vi.fn(),
+ cleanupBuilders: vi.fn(),
+ cleanupContainers: vi.fn(),
+ cleanupImages: vi.fn(),
+ cleanupSystem: vi.fn(),
+ cleanupVolumes: vi.fn(),
+ compileWriteAndReloadCaddyConfigSafely: vi.fn(),
+ execAsync: vi.fn(),
+ findServerById: vi.fn(),
+ getCaddyCompileSettings: vi.fn(),
+ getCaddyMigrationReport: vi.fn(),
+ getCaddyTrustedProxySettings: vi.fn(),
+ getDockerDiskUsage: vi.fn(),
+ getDokployImageTag: vi.fn(),
+ getLogCleanupStatus: vi.fn(),
+ getUpdateData: vi.fn(),
+ getWebServerPaths: vi.fn(),
+ getWebServerResourceName: vi.fn(),
+ getWebServerSettings: vi.fn(),
+ isCaddyAdminAdditionalPort: vi.fn(),
+ isCaddyReservedAdditionalPort: vi.fn(),
+ parseRawConfig: vi.fn(),
+ paths: vi.fn(),
+ prepareCaddyMigration: vi.fn(),
+ prepareEnvironmentVariables: vi.fn(),
+ processLogs: vi.fn(),
+ readCaddyConfigFileIfExists: vi.fn(),
+ readConfig: vi.fn(),
+ readConfigInPath: vi.fn(),
+ readDirectory: vi.fn(),
+ readEnvironmentVariables: vi.fn(),
+ readMainConfig: vi.fn(),
+ readMonitoringConfig: vi.fn(),
+ readPorts: vi.fn(),
+ recreateDirectory: vi.fn(),
+ reloadCaddyAfterValidation: vi.fn(),
+ reloadDockerResource: vi.fn(),
+ resolveWebServerProvider: vi.fn(),
+ rollbackCaddyMigration: vi.fn(),
+ sendDockerCleanupNotifications: vi.fn(),
+ setupGPUSupport: vi.fn(),
+ spawnAsync: vi.fn(),
+ startLogCleanup: vi.fn(),
+ stopLogCleanup: vi.fn(),
+ updateCaddyTrustedProxySettings: vi.fn(),
+ updateLetsEncryptEmail: vi.fn(),
+ updateLocalWebServerProvider: vi.fn(),
+ updateRemoteWebServerProvider: vi.fn(),
+ updateServerById: vi.fn(),
+ updateServerCaddy: vi.fn(),
+ updateServerTraefik: vi.fn(),
+ updateWebServerSettings: vi.fn(),
+ writeConfig: vi.fn(),
+ writeMainConfig: vi.fn(),
+ writeTraefikConfigInPath: vi.fn(),
+ writeTraefikSetup: vi.fn(),
+ writeWebServerSetup: vi.fn(),
+}));
+
+vi.mock("@dokploy/server", () => ({
+ ...serverMocks,
+ CLEANUP_CRON_JOB: "cleanup",
+ DEFAULT_UPDATE_DATA: { latestVersion: null, updateAvailable: false },
+ IS_CLOUD: false,
+}));
+
+vi.mock("@dokploy/trpc-openapi", () => ({
+ generateOpenApiDocument: vi.fn(),
+}));
+
+vi.mock("@/server/api/root", () => ({
+ appRouter: {},
+}));
+
+vi.mock("@/server/api/utils/audit", () => ({
+ audit: vi.fn(),
+}));
+
+vi.mock("@/server/queues/queueSetup", () => ({
+ cleanAllDeploymentQueue: vi.fn(),
+}));
+
+vi.mock("@/server/utils/backup", () => ({
+ removeJob: vi.fn(),
+ schedule: vi.fn(),
+}));
+
+import {
+ checkPortInUse,
+ compileWriteAndReloadCaddyConfigSafely,
+ findServerById,
+ getCaddyCompileSettings,
+ getCaddyTrustedProxySettings,
+ getWebServerResourceName,
+ getWebServerSettings,
+ isCaddyAdminAdditionalPort,
+ prepareEnvironmentVariables,
+ readEnvironmentVariables,
+ readPorts,
+ resolveWebServerProvider,
+ updateCaddyTrustedProxySettings,
+ updateServerCaddy,
+ updateWebServerSettings,
+ writeTraefikSetup,
+ writeWebServerSetup,
+} from "@dokploy/server";
+import { settingsRouter } from "@/server/api/routers/settings";
+import { audit } from "@/server/api/utils/audit";
+
+const caller = settingsRouter.createCaller({
+ session: {
+ userId: "user-1",
+ activeOrganizationId: "org-1",
+ },
+ user: {
+ id: "user-1",
+ role: "owner",
+ ownerId: "user-1",
+ email: "owner@example.com",
+ enableEnterpriseFeatures: true,
+ isValidEnterpriseLicense: true,
+ },
+ req: { headers: {} },
+ res: {},
+} as never);
+
+const staticInput = {
+ mode: "static" as const,
+ ranges: ["192.0.2.0/24"],
+ clientIpHeaders: ["X-Forwarded-For"],
+ strict: true,
+};
+
+type TrustedProxySettings =
+ | typeof staticInput
+ | {
+ mode: "cloudflare";
+ ranges?: string[] | null;
+ clientIpHeaders?: string[] | null;
+ strict?: boolean | null;
+ }
+ | null;
+
+let persistedTrustedProxySettings: TrustedProxySettings;
+let trustedProxyCallOrder: string[];
+
+const trustedProxyCompileConfig = (settings: TrustedProxySettings) => {
+ if (!settings) return null;
+ if (settings.mode === "cloudflare") {
+ return {
+ source: "cloudflare" as const,
+ clientIpHeaders: settings.clientIpHeaders ?? undefined,
+ strict: settings.strict ?? true,
+ };
+ }
+ return {
+ source: "static" as const,
+ ranges: settings.ranges,
+ clientIpHeaders: settings.clientIpHeaders,
+ strict: settings.strict,
+ };
+};
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ persistedTrustedProxySettings = null;
+ trustedProxyCallOrder = [];
+ vi.mocked(getCaddyCompileSettings).mockImplementation(async () => {
+ trustedProxyCallOrder.push("read-compile-settings");
+ return {
+ letsEncryptEmail: "ops@example.com",
+ trustedProxies: trustedProxyCompileConfig(persistedTrustedProxySettings),
+ } as never;
+ });
+ vi.mocked(getCaddyTrustedProxySettings).mockImplementation(
+ async () => persistedTrustedProxySettings as never,
+ );
+ vi.mocked(updateCaddyTrustedProxySettings).mockImplementation(
+ async (settings) => {
+ trustedProxyCallOrder.push("persist-settings");
+ persistedTrustedProxySettings = settings as TrustedProxySettings;
+ return {} as never;
+ },
+ );
+ vi.mocked(compileWriteAndReloadCaddyConfigSafely).mockResolvedValue(
+ undefined as never,
+ );
+ vi.mocked(readEnvironmentVariables).mockResolvedValue("");
+ vi.mocked(prepareEnvironmentVariables).mockReturnValue([]);
+ vi.mocked(getWebServerResourceName).mockImplementation((provider) =>
+ provider === "caddy" ? "dokploy-caddy" : "dokploy-traefik",
+ );
+ vi.mocked(checkPortInUse).mockResolvedValue({ isInUse: false } as never);
+ vi.mocked(findServerById).mockResolvedValue({
+ serverId: "server-1",
+ organizationId: "org-1",
+ } as never);
+ vi.mocked(isCaddyAdminAdditionalPort).mockImplementation(
+ (port) => port.targetPort === 2019 && (port.protocol ?? "tcp") === "tcp",
+ );
+ vi.mocked(writeWebServerSetup).mockResolvedValue(undefined as never);
+ vi.mocked(writeTraefikSetup).mockResolvedValue(undefined as never);
+});
+
+test("persists Caddy trusted proxy settings without rebuilding when Traefik is active", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("traefik");
+
+ const result = await caller.updateCaddyTrustedProxySettings(staticInput);
+
+ expect(updateCaddyTrustedProxySettings).toHaveBeenCalledTimes(1);
+ expect(updateCaddyTrustedProxySettings).toHaveBeenCalledWith(
+ staticInput,
+ undefined,
+ );
+ expect(compileWriteAndReloadCaddyConfigSafely).not.toHaveBeenCalled();
+ expect(audit).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ resourceName: "caddy-trusted-proxy" }),
+ );
+ expect(result).toEqual(staticInput);
+});
+
+test("rebuilds Caddy with persisted compile settings when Caddy is active", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+
+ await caller.updateCaddyTrustedProxySettings(staticInput);
+
+ expect(getCaddyCompileSettings).toHaveBeenCalledWith(undefined);
+ expect(compileWriteAndReloadCaddyConfigSafely).toHaveBeenCalledWith({
+ serverId: undefined,
+ letsEncryptEmail: "ops@example.com",
+ trustedProxies: {
+ source: "static",
+ ranges: ["192.0.2.0/24"],
+ clientIpHeaders: ["X-Forwarded-For"],
+ strict: true,
+ },
+ });
+ expect(trustedProxyCallOrder).toEqual([
+ "persist-settings",
+ "read-compile-settings",
+ ]);
+ expect(audit).toHaveBeenCalled();
+});
+
+test("rebuilds remote Caddy with the remote persisted compile settings", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+
+ await caller.updateCaddyTrustedProxySettings({
+ ...staticInput,
+ serverId: "server-1",
+ });
+
+ expect(resolveWebServerProvider).toHaveBeenCalledWith("server-1");
+ expect(updateCaddyTrustedProxySettings).toHaveBeenCalledWith(
+ staticInput,
+ "server-1",
+ );
+ expect(getCaddyCompileSettings).toHaveBeenCalledWith("server-1");
+ expect(compileWriteAndReloadCaddyConfigSafely).toHaveBeenCalledWith({
+ serverId: "server-1",
+ letsEncryptEmail: "ops@example.com",
+ trustedProxies: {
+ source: "static",
+ ranges: ["192.0.2.0/24"],
+ clientIpHeaders: ["X-Forwarded-For"],
+ strict: true,
+ },
+ });
+});
+
+test("restores previous trusted proxy settings when active Caddy rebuild fails", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ persistedTrustedProxySettings = {
+ mode: "cloudflare",
+ clientIpHeaders: ["CF-Connecting-IP"],
+ strict: true,
+ };
+ vi.mocked(compileWriteAndReloadCaddyConfigSafely).mockRejectedValueOnce(
+ new Error("caddy reload failed") as never,
+ );
+
+ await expect(
+ caller.updateCaddyTrustedProxySettings(staticInput),
+ ).rejects.toThrow("caddy reload failed");
+
+ expect(updateCaddyTrustedProxySettings).toHaveBeenNthCalledWith(
+ 1,
+ staticInput,
+ undefined,
+ );
+ expect(updateCaddyTrustedProxySettings).toHaveBeenNthCalledWith(
+ 2,
+ {
+ mode: "cloudflare",
+ clientIpHeaders: ["CF-Connecting-IP"],
+ strict: true,
+ },
+ undefined,
+ );
+ expect(persistedTrustedProxySettings).toEqual({
+ mode: "cloudflare",
+ clientIpHeaders: ["CF-Connecting-IP"],
+ strict: true,
+ });
+ expect(audit).not.toHaveBeenCalled();
+});
+
+test("restores remote trusted proxy settings when remote Caddy rebuild fails", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ persistedTrustedProxySettings = {
+ mode: "cloudflare",
+ clientIpHeaders: ["CF-Connecting-IP"],
+ strict: true,
+ };
+ vi.mocked(compileWriteAndReloadCaddyConfigSafely).mockRejectedValueOnce(
+ new Error("remote caddy reload failed") as never,
+ );
+
+ await expect(
+ caller.updateCaddyTrustedProxySettings({
+ ...staticInput,
+ serverId: "server-1",
+ }),
+ ).rejects.toThrow("remote caddy reload failed");
+
+ expect(resolveWebServerProvider).toHaveBeenCalledWith("server-1");
+ expect(getCaddyCompileSettings).toHaveBeenCalledWith("server-1");
+ expect(compileWriteAndReloadCaddyConfigSafely).toHaveBeenCalledWith({
+ serverId: "server-1",
+ letsEncryptEmail: "ops@example.com",
+ trustedProxies: {
+ source: "static",
+ ranges: ["192.0.2.0/24"],
+ clientIpHeaders: ["X-Forwarded-For"],
+ strict: true,
+ },
+ });
+ expect(updateCaddyTrustedProxySettings).toHaveBeenNthCalledWith(
+ 1,
+ staticInput,
+ "server-1",
+ );
+ expect(updateCaddyTrustedProxySettings).toHaveBeenNthCalledWith(
+ 2,
+ {
+ mode: "cloudflare",
+ clientIpHeaders: ["CF-Connecting-IP"],
+ strict: true,
+ },
+ "server-1",
+ );
+ expect(persistedTrustedProxySettings).toEqual({
+ mode: "cloudflare",
+ clientIpHeaders: ["CF-Connecting-IP"],
+ strict: true,
+ });
+ expect(audit).not.toHaveBeenCalled();
+});
+
+test("reports Caddy dashboard disabled instead of exposing the Caddy admin API", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+
+ const result = await caller.getWebServerDashboardState({});
+
+ expect(result).toEqual({ provider: "caddy", enabled: false });
+ expect(readPorts).not.toHaveBeenCalled();
+});
+
+test("keeps Traefik dashboard state based on the Traefik dashboard port", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("traefik");
+ vi.mocked(readPorts).mockResolvedValue([
+ { targetPort: 8080, publishedPort: 8080, protocol: "tcp" },
+ ] as never);
+
+ const result = await caller.getWebServerDashboardState({});
+
+ expect(readPorts).toHaveBeenCalledWith("dokploy-traefik", undefined);
+ expect(result).toEqual({ provider: "traefik", enabled: true });
+});
+
+test("rejects direct dashboard toggles when Caddy is active", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+
+ await expect(
+ caller.toggleDashboard({ enableDashboard: true }),
+ ).rejects.toThrow("Caddy admin API is kept local-only");
+
+ expect(readPorts).not.toHaveBeenCalled();
+ expect(readEnvironmentVariables).not.toHaveBeenCalled();
+ expect(prepareEnvironmentVariables).not.toHaveBeenCalled();
+ expect(checkPortInUse).not.toHaveBeenCalled();
+ expect(writeTraefikSetup).not.toHaveBeenCalled();
+ expect(audit).not.toHaveBeenCalled();
+});
+
+test("keeps dashboard toggles available when Traefik is active", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("traefik");
+ vi.mocked(readPorts).mockResolvedValue([]);
+ vi.mocked(readEnvironmentVariables).mockResolvedValue("TRAEFIK_ENV=1");
+ vi.mocked(prepareEnvironmentVariables).mockReturnValue(["TRAEFIK_ENV=1"]);
+
+ await caller.toggleDashboard({ enableDashboard: true });
+
+ expect(readPorts).toHaveBeenCalledWith("dokploy-traefik", undefined);
+ expect(readEnvironmentVariables).toHaveBeenCalledWith(
+ "dokploy-traefik",
+ undefined,
+ );
+ expect(checkPortInUse).toHaveBeenCalledWith(8080, undefined);
+ expect(writeTraefikSetup).toHaveBeenCalledWith({
+ env: ["TRAEFIK_ENV=1"],
+ additionalPorts: [
+ { targetPort: 8080, publishedPort: 8080, protocol: "tcp" },
+ ],
+ serverId: undefined,
+ });
+ expect(audit).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ resourceName: "toggle-dashboard" }),
+ );
+});
+
+test("restores dashboard settings when active Caddy domain rewrite fails", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ const previousSettings = {
+ host: "old-dashboard.example.com",
+ letsEncryptEmail: "old-ops@example.com",
+ certificateType: "letsencrypt",
+ https: true,
+ };
+ const updatedSettings = {
+ ...previousSettings,
+ host: "new-dashboard.example.com",
+ letsEncryptEmail: "new-ops@example.com",
+ };
+ vi.mocked(getWebServerSettings).mockResolvedValue(previousSettings as never);
+ vi.mocked(updateWebServerSettings)
+ .mockResolvedValueOnce(updatedSettings as never)
+ .mockResolvedValueOnce(previousSettings as never);
+ vi.mocked(updateServerCaddy).mockRejectedValueOnce(
+ new Error("caddy reload failed") as never,
+ );
+
+ await expect(
+ caller.assignDomainServer({
+ host: "new-dashboard.example.com",
+ letsEncryptEmail: "new-ops@example.com",
+ certificateType: "letsencrypt",
+ https: true,
+ }),
+ ).rejects.toThrow("caddy reload failed");
+
+ expect(updateWebServerSettings).toHaveBeenNthCalledWith(1, {
+ host: "new-dashboard.example.com",
+ letsEncryptEmail: "new-ops@example.com",
+ certificateType: "letsencrypt",
+ https: true,
+ });
+ expect(updateWebServerSettings).toHaveBeenNthCalledWith(2, previousSettings);
+ expect(updateServerCaddy).toHaveBeenCalledWith(
+ updatedSettings,
+ "new-dashboard.example.com",
+ );
+ expect(audit).not.toHaveBeenCalled();
+});
+
+test("reads remote Traefik dashboard state from the remote web server", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("traefik");
+ vi.mocked(readPorts).mockResolvedValue([
+ { targetPort: 8080, publishedPort: 8080, protocol: "tcp" },
+ ] as never);
+
+ const result = await caller.getWebServerDashboardState({
+ serverId: "server-1",
+ });
+
+ expect(resolveWebServerProvider).toHaveBeenCalledWith("server-1");
+ expect(readPorts).toHaveBeenCalledWith("dokploy-traefik", "server-1");
+ expect(result).toEqual({ provider: "traefik", enabled: true });
+});
+
+test("rejects Caddy admin port publishing before rebuilding the web server", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+
+ await expect(
+ caller.updateWebServerPorts({
+ additionalPorts: [
+ { targetPort: 2019, publishedPort: 2019, protocol: "tcp" },
+ ],
+ }),
+ ).rejects.toThrow("reserved and cannot be published");
+
+ expect(checkPortInUse).not.toHaveBeenCalled();
+ expect(writeWebServerSetup).not.toHaveBeenCalled();
+});
+
+test("allows non-admin Caddy additional ports before rebuilding the web server", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+
+ await caller.updateWebServerPorts({
+ additionalPorts: [
+ { targetPort: 8080, publishedPort: 18080, protocol: "tcp" },
+ { targetPort: 8082, publishedPort: 18082, protocol: "tcp" },
+ ],
+ });
+
+ expect(checkPortInUse).toHaveBeenCalledWith(18080, undefined);
+ expect(checkPortInUse).toHaveBeenCalledWith(18082, undefined);
+ expect(writeWebServerSetup).toHaveBeenCalledWith(
+ "caddy",
+ expect.objectContaining({
+ additionalPorts: [
+ { targetPort: 8080, publishedPort: 18080, protocol: "tcp" },
+ { targetPort: 8082, publishedPort: 18082, protocol: "tcp" },
+ ],
+ serverId: undefined,
+ }),
+ );
+ expect(audit).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ resourceName: "web-server-ports" }),
+ );
+});
diff --git a/apps/dokploy/__test__/caddy/web-server-file-system-router.test.ts b/apps/dokploy/__test__/caddy/web-server-file-system-router.test.ts
new file mode 100644
index 0000000000..8da82ddd0a
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/web-server-file-system-router.test.ts
@@ -0,0 +1,335 @@
+import { beforeEach, expect, test, vi } from "vitest";
+
+const serverMocks = vi.hoisted(() => ({
+ applyCaddyMigration: vi.fn(),
+ checkGPUStatus: vi.fn(),
+ checkPortInUse: vi.fn(),
+ checkPostgresHealth: vi.fn(),
+ checkRedisHealth: vi.fn(),
+ checkWebServerHealth: vi.fn(),
+ cleanupAll: vi.fn(),
+ cleanupAllBackground: vi.fn(),
+ cleanupBuilders: vi.fn(),
+ cleanupContainers: vi.fn(),
+ cleanupImages: vi.fn(),
+ cleanupSystem: vi.fn(),
+ cleanupVolumes: vi.fn(),
+ compileWriteAndReloadCaddyConfigSafely: vi.fn(),
+ execAsync: vi.fn(),
+ findServerById: vi.fn(),
+ getCaddyCompileSettings: vi.fn(),
+ getCaddyMigrationReport: vi.fn(),
+ getCaddyTrustedProxySettings: vi.fn(),
+ getDockerDiskUsage: vi.fn(),
+ getDokployImageTag: vi.fn(),
+ getLogCleanupStatus: vi.fn(),
+ getUpdateData: vi.fn(),
+ getWebServerPaths: vi.fn(),
+ getWebServerResourceName: vi.fn(),
+ getWebServerSettings: vi.fn(),
+ isCaddyAdminAdditionalPort: vi.fn(),
+ isCaddyReservedAdditionalPort: vi.fn(),
+ parseRawConfig: vi.fn(),
+ paths: vi.fn(),
+ prepareCaddyMigration: vi.fn(),
+ prepareEnvironmentVariables: vi.fn(),
+ processLogs: vi.fn(),
+ readCaddyConfigFileIfExists: vi.fn(),
+ readConfig: vi.fn(),
+ readConfigInPath: vi.fn(),
+ readDirectory: vi.fn(),
+ readEnvironmentVariables: vi.fn(),
+ readMainConfig: vi.fn(),
+ readMonitoringConfig: vi.fn(),
+ readPorts: vi.fn(),
+ recreateDirectory: vi.fn(),
+ reloadCaddyAfterValidation: vi.fn(),
+ reloadDockerResource: vi.fn(),
+ resolveWebServerProvider: vi.fn(),
+ rollbackCaddyMigration: vi.fn(),
+ sendDockerCleanupNotifications: vi.fn(),
+ setupGPUSupport: vi.fn(),
+ spawnAsync: vi.fn(),
+ startLogCleanup: vi.fn(),
+ stopLogCleanup: vi.fn(),
+ updateCaddyTrustedProxySettings: vi.fn(),
+ updateLetsEncryptEmail: vi.fn(),
+ updateLocalWebServerProvider: vi.fn(),
+ updateRemoteWebServerProvider: vi.fn(),
+ updateServerById: vi.fn(),
+ updateServerCaddy: vi.fn(),
+ updateServerTraefik: vi.fn(),
+ updateWebServerSettings: vi.fn(),
+ writeConfig: vi.fn(),
+ writeMainConfig: vi.fn(),
+ writeTraefikConfigInPath: vi.fn(),
+ writeTraefikSetup: vi.fn(),
+ writeWebServerSetup: vi.fn(),
+}));
+
+vi.mock("@dokploy/server", () => ({
+ ...serverMocks,
+ CLEANUP_CRON_JOB: "cleanup",
+ DEFAULT_UPDATE_DATA: { latestVersion: null, updateAvailable: false },
+ IS_CLOUD: false,
+}));
+
+vi.mock("@dokploy/trpc-openapi", () => ({
+ generateOpenApiDocument: vi.fn(),
+}));
+
+vi.mock("@/server/api/root", () => ({
+ appRouter: {},
+}));
+
+vi.mock("@dokploy/server/services/permission", () => ({
+ checkPermission: vi.fn(),
+}));
+
+vi.mock("@/server/api/utils/audit", () => ({
+ audit: vi.fn(),
+}));
+
+vi.mock("@/server/queues/queueSetup", () => ({
+ cleanAllDeploymentQueue: vi.fn(),
+}));
+
+vi.mock("@/server/utils/backup", () => ({
+ removeJob: vi.fn(),
+ schedule: vi.fn(),
+}));
+
+import {
+ findServerById,
+ getWebServerPaths,
+ paths,
+ readConfigInPath,
+ readDirectory,
+ resolveWebServerProvider,
+ writeTraefikConfigInPath,
+} from "@dokploy/server";
+import { settingsRouter } from "@/server/api/routers/settings";
+import { audit } from "@/server/api/utils/audit";
+
+const caller = settingsRouter.createCaller({
+ session: {
+ userId: "user-1",
+ activeOrganizationId: "org-1",
+ },
+ user: {
+ id: "user-1",
+ role: "owner",
+ ownerId: "user-1",
+ email: "owner@example.com",
+ enableEnterpriseFeatures: true,
+ isValidEnterpriseLicense: true,
+ },
+ req: { headers: {} },
+ res: {},
+} as never);
+
+const caddyPaths = {
+ MAIN_CADDY_PATH: "/etc/dokploy/caddy",
+ CADDY_CONFIG_PATH: "/etc/dokploy/caddy/caddy.json",
+ CADDY_FRAGMENTS_PATH: "/etc/dokploy/caddy/fragments",
+ CADDY_MIGRATIONS_PATH: "/etc/dokploy/caddy/migrations",
+};
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ vi.mocked(findServerById).mockResolvedValue({
+ serverId: "server-1",
+ organizationId: "org-1",
+ } as never);
+ vi.mocked(paths).mockReturnValue({
+ MAIN_TRAEFIK_PATH: "/etc/dokploy/traefik",
+ ...caddyPaths,
+ } as never);
+ vi.mocked(getWebServerPaths).mockImplementation(
+ (provider) =>
+ ({
+ basePath:
+ provider === "caddy"
+ ? caddyPaths.MAIN_CADDY_PATH
+ : "/etc/dokploy/traefik",
+ activeConfigPath:
+ provider === "caddy"
+ ? caddyPaths.CADDY_CONFIG_PATH
+ : "/etc/dokploy/traefik/traefik.yml",
+ fragmentsPath:
+ provider === "caddy"
+ ? caddyPaths.CADDY_FRAGMENTS_PATH
+ : "/etc/dokploy/traefik/dynamic",
+ }) as never,
+ );
+ vi.mocked(readConfigInPath).mockImplementation(
+ async (filePath: string) => `contents:${filePath}`,
+ );
+});
+
+test("reads and updates Traefik web-server files through provider-aware endpoints", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("traefik");
+ vi.mocked(readDirectory).mockResolvedValue([
+ {
+ id: "/etc/dokploy/traefik/dynamic/app.yml",
+ name: "app.yml",
+ type: "file",
+ },
+ ] as never);
+
+ const directories = await caller.readWebServerDirectories({
+ serverId: "server-1",
+ });
+ const file = await caller.readWebServerFile({
+ serverId: "server-1",
+ path: "dynamic/app.yml",
+ });
+ const updated = await caller.updateWebServerFile({
+ serverId: "server-1",
+ path: "dynamic/app.yml",
+ webServerConfig: "http: {}\n",
+ });
+
+ expect(directories).toHaveLength(1);
+ expect(readDirectory).toHaveBeenCalledWith(
+ "/etc/dokploy/traefik",
+ "server-1",
+ );
+ expect(readConfigInPath).toHaveBeenCalledWith(
+ "/etc/dokploy/traefik/dynamic/app.yml",
+ "server-1",
+ );
+ expect(writeTraefikConfigInPath).toHaveBeenCalledWith(
+ "/etc/dokploy/traefik/dynamic/app.yml",
+ "http: {}\n",
+ "server-1",
+ );
+ expect(audit).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ resourceName: "web-server-file" }),
+ );
+ expect(file).toBe("contents:/etc/dokploy/traefik/dynamic/app.yml");
+ expect(updated).toBe(true);
+});
+
+test("limits Caddy web-server file access to safe generated artifacts", async () => {
+ vi.mocked(resolveWebServerProvider).mockResolvedValue("caddy");
+ vi.mocked(readDirectory).mockImplementation(async (dirPath: string) => {
+ if (dirPath === caddyPaths.CADDY_FRAGMENTS_PATH) {
+ return [
+ {
+ id: `${caddyPaths.CADDY_FRAGMENTS_PATH}/app.json`,
+ name: "app.json",
+ type: "file",
+ },
+ ] as never;
+ }
+ if (dirPath === caddyPaths.CADDY_MIGRATIONS_PATH) {
+ return [
+ {
+ id: `${caddyPaths.CADDY_MIGRATIONS_PATH}/caddy-1`,
+ name: "caddy-1",
+ type: "directory",
+ children: [
+ {
+ id: `${caddyPaths.CADDY_MIGRATIONS_PATH}/caddy-1/caddy.json`,
+ name: "caddy.json",
+ type: "file",
+ },
+ {
+ id: `${caddyPaths.CADDY_MIGRATIONS_PATH}/caddy-1/backups`,
+ name: "backups",
+ type: "directory",
+ children: [
+ {
+ id: `${caddyPaths.CADDY_MIGRATIONS_PATH}/caddy-1/backups/traefik.yml`,
+ name: "traefik.yml",
+ type: "file",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ id: `${caddyPaths.CADDY_MIGRATIONS_PATH}/backups`,
+ name: "backups",
+ type: "directory",
+ children: [],
+ },
+ ] as never;
+ }
+ return [] as never;
+ });
+
+ const directories = await caller.readWebServerDirectories({
+ serverId: "server-1",
+ });
+ const file = await caller.readWebServerFile({
+ serverId: "server-1",
+ path: caddyPaths.CADDY_CONFIG_PATH,
+ });
+
+ expect(directories).toEqual([
+ {
+ id: caddyPaths.CADDY_CONFIG_PATH,
+ name: "caddy.json",
+ type: "file",
+ },
+ {
+ id: caddyPaths.CADDY_FRAGMENTS_PATH,
+ name: "fragments",
+ type: "directory",
+ children: [
+ {
+ id: `${caddyPaths.CADDY_FRAGMENTS_PATH}/app.json`,
+ name: "app.json",
+ type: "file",
+ },
+ ],
+ },
+ {
+ id: caddyPaths.CADDY_MIGRATIONS_PATH,
+ name: "migrations",
+ type: "directory",
+ children: [
+ {
+ id: `${caddyPaths.CADDY_MIGRATIONS_PATH}/caddy-1`,
+ name: "caddy-1",
+ type: "directory",
+ children: [
+ {
+ id: `${caddyPaths.CADDY_MIGRATIONS_PATH}/caddy-1/caddy.json`,
+ name: "caddy.json",
+ type: "file",
+ },
+ ],
+ },
+ ],
+ },
+ ]);
+ expect(readConfigInPath).toHaveBeenCalledWith(
+ caddyPaths.CADDY_CONFIG_PATH,
+ "server-1",
+ );
+ expect(file).toBe(`contents:${caddyPaths.CADDY_CONFIG_PATH}`);
+ await expect(
+ caller.readWebServerFile({
+ serverId: "server-1",
+ path: `${caddyPaths.CADDY_MIGRATIONS_PATH}/caddy-1/backups/traefik.yml`,
+ }),
+ ).rejects.toThrow("migration backups");
+ await expect(
+ caller.readWebServerFile({
+ serverId: "server-1",
+ path: "/etc/dokploy/traefik/traefik.yml",
+ }),
+ ).rejects.toThrow("outside of active web server directory");
+ await expect(
+ caller.updateWebServerFile({
+ serverId: "server-1",
+ path: caddyPaths.CADDY_CONFIG_PATH,
+ webServerConfig: "{}",
+ }),
+ ).rejects.toThrow("read-only");
+ expect(writeTraefikConfigInPath).not.toHaveBeenCalled();
+});
diff --git a/apps/dokploy/__test__/caddy/web-server-health.test.ts b/apps/dokploy/__test__/caddy/web-server-health.test.ts
new file mode 100644
index 0000000000..b7509ee9c6
--- /dev/null
+++ b/apps/dokploy/__test__/caddy/web-server-health.test.ts
@@ -0,0 +1,83 @@
+import { beforeEach, expect, test, vi } from "vitest";
+
+const dockerMock = vi.hoisted(() => ({
+ getContainer: vi.fn(),
+ getService: vi.fn(),
+ listTasks: vi.fn(),
+}));
+
+vi.mock("@dokploy/server/constants", () => ({
+ docker: dockerMock,
+ paths: vi.fn(() => ({})),
+}));
+
+import {
+ checkTraefikHealth,
+ checkWebServerHealth,
+} from "@dokploy/server/utils/docker/utils";
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ dockerMock.getContainer.mockReturnValue({
+ inspect: vi.fn().mockResolvedValue({ State: { Running: true } }),
+ });
+ dockerMock.getService.mockReturnValue({
+ inspect: vi.fn().mockResolvedValue({
+ Spec: { Mode: { Replicated: { Replicas: 1 } } },
+ }),
+ });
+ dockerMock.listTasks.mockResolvedValue([
+ {
+ Status: {
+ State: "running",
+ ContainerStatus: { ContainerID: "container-1" },
+ },
+ },
+ ]);
+});
+
+test("checks the Caddy resource when the active web server provider is Caddy", async () => {
+ const result = await checkWebServerHealth("caddy");
+
+ expect(result).toEqual({ provider: "caddy", status: "healthy" });
+ expect(dockerMock.getContainer).toHaveBeenCalledWith("dokploy-caddy");
+ expect(dockerMock.getService).not.toHaveBeenCalled();
+});
+
+test("checks the Traefik resource when the active web server provider is Traefik", async () => {
+ const result = await checkWebServerHealth("traefik");
+
+ expect(result).toEqual({ provider: "traefik", status: "healthy" });
+ expect(dockerMock.getContainer).toHaveBeenCalledWith("dokploy-traefik");
+});
+
+test("falls back to the active Caddy swarm service when no standalone Caddy container exists", async () => {
+ dockerMock.getContainer.mockReturnValueOnce({
+ inspect: vi.fn().mockRejectedValue(new Error("missing container")),
+ });
+
+ const result = await checkWebServerHealth("caddy");
+
+ expect(result).toEqual({ provider: "caddy", status: "healthy" });
+ expect(dockerMock.getService).toHaveBeenCalledWith("dokploy-caddy");
+ expect(dockerMock.listTasks).toHaveBeenCalledWith({
+ filters: JSON.stringify({
+ service: ["dokploy-caddy"],
+ "desired-state": ["running"],
+ }),
+ });
+});
+
+test("keeps the Traefik-specific helper on the Traefik resource", async () => {
+ dockerMock.getContainer.mockReturnValueOnce({
+ inspect: vi.fn().mockResolvedValue({ State: { Running: false } }),
+ });
+
+ const result = await checkTraefikHealth();
+
+ expect(result).toEqual({
+ status: "unhealthy",
+ message: "Container is not running",
+ });
+ expect(dockerMock.getContainer).toHaveBeenCalledWith("dokploy-traefik");
+});
diff --git a/apps/dokploy/__test__/db/runtime-migration.test.ts b/apps/dokploy/__test__/db/runtime-migration.test.ts
new file mode 100644
index 0000000000..b7ddbca00e
--- /dev/null
+++ b/apps/dokploy/__test__/db/runtime-migration.test.ts
@@ -0,0 +1,85 @@
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+const mocks = vi.hoisted(() => {
+ const db = { id: "db" };
+ const sql = { end: vi.fn().mockResolvedValue(undefined) };
+
+ return {
+ db,
+ drizzle: vi.fn(() => db),
+ logger: {
+ error: vi.fn(),
+ log: vi.fn(),
+ },
+ migrate: vi.fn().mockResolvedValue(undefined),
+ postgres: vi.fn(() => sql),
+ sql,
+ };
+});
+
+vi.mock("@dokploy/server/db/constants", () => ({
+ dbUrl: "postgres://dokploy:test@localhost:5432/dokploy",
+}));
+
+vi.mock("postgres", () => ({
+ default: mocks.postgres,
+}));
+
+vi.mock("drizzle-orm/postgres-js", () => ({
+ drizzle: mocks.drizzle,
+}));
+
+vi.mock("drizzle-orm/postgres-js/migrator", () => ({
+ migrate: mocks.migrate,
+}));
+
+import { runRuntimeMigrations } from "../../server/db/run-migrations";
+
+describe("runtime migrations", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mocks.sql.end.mockResolvedValue(undefined);
+ mocks.migrate.mockResolvedValue(undefined);
+ });
+
+ test("runs the default migration folder and closes the connection once", async () => {
+ await runRuntimeMigrations({ logger: mocks.logger });
+
+ expect(mocks.postgres).toHaveBeenCalledWith(
+ "postgres://dokploy:test@localhost:5432/dokploy",
+ { max: 1 },
+ );
+ expect(mocks.drizzle).toHaveBeenCalledWith(mocks.sql);
+ expect(mocks.migrate).toHaveBeenCalledWith(mocks.db, {
+ migrationsFolder: "drizzle",
+ });
+ expect(mocks.logger.log).toHaveBeenCalledWith("Migration complete");
+ expect(mocks.logger.error).not.toHaveBeenCalled();
+ expect(mocks.sql.end).toHaveBeenCalledTimes(1);
+ });
+
+ test("supports an explicit migration folder", async () => {
+ await runRuntimeMigrations({
+ logger: mocks.logger,
+ migrationsFolder: "/app/drizzle",
+ });
+
+ expect(mocks.migrate).toHaveBeenCalledWith(mocks.db, {
+ migrationsFolder: "/app/drizzle",
+ });
+ expect(mocks.sql.end).toHaveBeenCalledTimes(1);
+ });
+
+ test("rejects migration failures and still closes the connection once", async () => {
+ const error = new Error("migration boom");
+ mocks.migrate.mockRejectedValueOnce(error);
+
+ await expect(runRuntimeMigrations({ logger: mocks.logger })).rejects.toBe(
+ error,
+ );
+
+ expect(mocks.logger.error).toHaveBeenCalledWith("Migration failed", error);
+ expect(mocks.logger.log).not.toHaveBeenCalled();
+ expect(mocks.sql.end).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts
index eda4dace58..562111b723 100644
--- a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts
+++ b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts
@@ -18,7 +18,10 @@ type WebServerSettings = typeof webServerSettings.$inferSelect;
const baseSettings: WebServerSettings = {
id: "",
+ webServerProvider: "traefik",
+ caddyTrustedProxyConfig: null,
https: false,
+ requestLogsEnabled: false,
certificateType: "none",
host: null,
serverIp: null,
diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx
index 683e0ebbaf..7774cf8f75 100644
--- a/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx
+++ b/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx
@@ -36,6 +36,7 @@ import {
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
+import { invalidateApplicationWebServerConfig } from "../../web-server-config-cache";
const AddRedirectSchema = z.object({
regex: z.string().min(1, "Regex required"),
@@ -133,9 +134,7 @@ export const HandleRedirect = ({
applicationId,
});
refetch();
- await utils.application.readTraefikConfig.invalidate({
- applicationId,
- });
+ await invalidateApplicationWebServerConfig(utils, applicationId);
onDialogToggle(false);
})
.catch(() => {
diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx
index a14074ec59..e057af7677 100644
--- a/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx
+++ b/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx
@@ -10,6 +10,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
+import { invalidateApplicationWebServerConfig } from "../../web-server-config-cache";
import { HandleRedirect } from "./handle-redirect";
interface Props {
@@ -97,11 +98,12 @@ export const ShowRedirects = ({ applicationId }: Props) => {
await deleteRedirect({
redirectId: redirect.redirectId,
})
- .then(() => {
- refetch();
- utils.application.readTraefikConfig.invalidate({
+ .then(async () => {
+ await refetch();
+ await invalidateApplicationWebServerConfig(
+ utils,
applicationId,
- });
+ );
toast.success("Redirect deleted successfully");
})
.catch(() => {
diff --git a/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx
index 49a126881a..de1e9e0c37 100644
--- a/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx
+++ b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx
@@ -25,6 +25,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
+import { invalidateApplicationWebServerConfig } from "../../web-server-config-cache";
const AddSecuritychema = z.object({
username: z.string().min(1, "Username is required"),
@@ -85,9 +86,7 @@ export const HandleSecurity = ({
await utils.application.one.invalidate({
applicationId,
});
- await utils.application.readTraefikConfig.invalidate({
- applicationId,
- });
+ await invalidateApplicationWebServerConfig(utils, applicationId);
await refetch();
setIsOpen(false);
})
diff --git a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx
index 724953afec..888d54bc5f 100644
--- a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx
+++ b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx
@@ -13,6 +13,7 @@ import {
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
+import { invalidateApplicationWebServerConfig } from "../../web-server-config-cache";
import { HandleSecurity } from "./handle-security";
interface Props {
@@ -88,11 +89,12 @@ export const ShowSecurity = ({ applicationId }: Props) => {
await deleteSecurity({
securityId: security.securityId,
})
- .then(() => {
- refetch();
- utils.application.readTraefikConfig.invalidate({
+ .then(async () => {
+ await refetch();
+ await invalidateApplicationWebServerConfig(
+ utils,
applicationId,
- });
+ );
toast.success("Security deleted successfully");
})
.catch(() => {
diff --git a/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx b/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx
index 94efbc285c..2f0ab08d20 100644
--- a/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx
+++ b/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx
@@ -1,4 +1,5 @@
import { File, Loader2 } from "lucide-react";
+import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import {
Card,
@@ -17,7 +18,23 @@ interface Props {
export const ShowTraefikConfig = ({ applicationId }: Props) => {
const { data: permissions } = api.user.getPermissions.useQuery();
const canRead = permissions?.traefikFiles.read ?? false;
- const { data, isPending } = api.application.readTraefikConfig.useQuery(
+ const { data: application } = api.application.one.useQuery(
+ { applicationId },
+ { enabled: !!applicationId && canRead },
+ );
+ const { data: activeProvider } =
+ api.settings.getActiveWebServerProvider.useQuery(
+ { serverId: application?.serverId || undefined },
+ { enabled: canRead && !!application },
+ );
+ const isCaddy = activeProvider === "caddy";
+ const isTraefik = activeProvider === "traefik";
+ const providerLabel = isCaddy
+ ? "Caddy"
+ : isTraefik
+ ? "Traefik"
+ : "Web Server";
+ const { data, isPending } = api.application.readWebServerConfig.useQuery(
{
applicationId,
},
@@ -30,15 +47,24 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
- Traefik
+ {providerLabel}
- Modify the traefik config, in rare cases you may need to add
- specific config, be careful because modifying incorrectly can break
- traefik and your application
+ {isCaddy
+ ? "Review generated Caddy route fragments for this application. Caddy manages certificates for HTTPS domains; Traefik YAML and custom certificate resolvers do not apply."
+ : isTraefik
+ ? "Modify the Traefik config in rare cases. Be careful: invalid Traefik YAML can break the application route."
+ : "Review the active web server configuration for this application. Provider-specific edit controls appear after Dokploy resolves the active provider."}
+ {isCaddy && (
+
+ For Caddy, domain changes generate JSON fragments and Caddy handles
+ ACME certificates automatically. Use Settings → Web Server for the
+ active Caddy config and migration artifacts.
+
+ )}
{isPending ? (
Loading...
@@ -48,7 +74,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
- No traefik config detected
+ No {providerLabel} config detected
) : (
@@ -60,9 +86,11 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
disabled
className="font-mono"
/>
-
-
-
+ {isTraefik && (
+
+
+
+ )}
)}
diff --git a/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx
index b232591e4b..c51ee531a4 100644
--- a/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx
+++ b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import z from "zod";
+import { invalidateApplicationWebServerConfig } from "@/components/dashboard/application/web-server-config-cache";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -80,7 +81,11 @@ export const domain = z
});
}
- if (input.certificateType === "custom" && !input.customCertResolver) {
+ if (
+ input.https &&
+ input.certificateType === "custom" &&
+ !input.customCertResolver
+ ) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["customCertResolver"],
@@ -178,6 +183,22 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
const { mutateAsync: generateDomain, isPending: isLoadingGenerate } =
api.domain.generateDomain.useMutation();
+ const { data: activeProvider, isLoading: isLoadingProvider } =
+ api.settings.getActiveWebServerProvider.useQuery(
+ { serverId: application?.serverId || undefined },
+ { enabled: !!application },
+ );
+ const isCaddyProvider = activeProvider === "caddy";
+
+ const { data: certificates } = api.certificates.all.useQuery(undefined, {
+ enabled: isOpen && isCaddyProvider,
+ });
+ const caddyCertificates = (certificates ?? []).filter((certificate) =>
+ application?.serverId
+ ? certificate.serverId === application.serverId
+ : !certificate.serverId,
+ );
+
const { data: canGenerateTraefikMeDomains } =
api.domain.canGenerateTraefikMeDomains.useQuery({
serverId: application?.serverId || "",
@@ -264,12 +285,15 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
}
}, [form, data, isPending, domainId]);
- // Separate effect for handling custom cert resolver validation
+ // Separate effect for handling provider-specific certificate fields
useEffect(() => {
if (certificateType === "custom") {
form.trigger("customCertResolver");
}
- }, [certificateType, form]);
+ if (isCaddyProvider && certificateType !== "custom") {
+ form.setValue("customCertResolver", undefined);
+ }
+ }, [certificateType, form, isCaddyProvider]);
const dictionary = {
success: domainId ? "Domain Updated" : "Domain Created",
@@ -299,9 +323,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
await utils.domain.byApplicationId.invalidate({
applicationId: id,
});
- await utils.application.readTraefikConfig.invalidate({
- applicationId: id,
- });
+ await invalidateApplicationWebServerConfig(utils, id);
} else if (data.domainType === "compose") {
await utils.domain.byComposeId.invalidate({
composeId: id,
@@ -337,6 +359,15 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
)}
+ {isCaddyProvider && (
+
+ This server uses Caddy. Dokploy will generate Caddy route fragments,
+ Caddy can manage HTTPS certificates for public DNS names or load an
+ uploaded certificate, and Traefik-only custom entrypoints and
+ middleware references are hidden.
+
+ )}
+
-
+
{dictionary.submit}
diff --git a/apps/dokploy/components/dashboard/application/web-server-config-cache.ts b/apps/dokploy/components/dashboard/application/web-server-config-cache.ts
new file mode 100644
index 0000000000..f2cf9ed234
--- /dev/null
+++ b/apps/dokploy/components/dashboard/application/web-server-config-cache.ts
@@ -0,0 +1,21 @@
+type ConfigCacheUtils = {
+ application: {
+ readTraefikConfig: {
+ invalidate(input: { applicationId: string }): Promise | unknown;
+ };
+ readWebServerConfig: {
+ invalidate(input: { applicationId: string }): Promise | unknown;
+ };
+ };
+};
+
+export const invalidateApplicationWebServerConfig = async (
+ utils: ConfigCacheUtils,
+ applicationId: string,
+) => {
+ const input = { applicationId };
+ await Promise.all([
+ utils.application.readTraefikConfig.invalidate(input),
+ utils.application.readWebServerConfig.invalidate(input),
+ ]);
+};
diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx
index 288208fb1b..1a8b4c71b6 100644
--- a/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx
+++ b/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx
@@ -22,7 +22,7 @@ import { api } from "@/utils/api";
import { validateAndFormatYAML } from "../application/advanced/traefik/update-traefik-config";
const UpdateServerMiddlewareConfigSchema = z.object({
- traefikConfig: z.string(),
+ webServerConfig: z.string(),
});
type UpdateServerMiddlewareConfig = z.infer<
@@ -32,14 +32,24 @@ type UpdateServerMiddlewareConfig = z.infer<
interface Props {
path: string;
serverId?: string;
+ activeProvider?: "traefik" | "caddy";
}
-export const ShowTraefikFile = ({ path, serverId }: Props) => {
+export const ShowTraefikFile = ({ path, serverId, activeProvider }: Props) => {
+ const providerLabel =
+ activeProvider === "caddy"
+ ? "Caddy"
+ : activeProvider === "traefik"
+ ? "Traefik"
+ : "Web Server";
+ const isTraefik = activeProvider === "traefik";
const {
data,
refetch,
isLoading: isLoadingFile,
- } = api.settings.readTraefikFile.useQuery(
+ error: readError,
+ isError: isReadError,
+ } = api.settings.readWebServerFile.useQuery(
{
path,
serverId,
@@ -52,51 +62,64 @@ export const ShowTraefikFile = ({ path, serverId }: Props) => {
const [skipYamlValidation, setSkipYamlValidation] = useState(false);
const { mutateAsync, isPending, error, isError } =
- api.settings.updateTraefikFile.useMutation();
+ api.settings.updateWebServerFile.useMutation();
const form = useForm({
defaultValues: {
- traefikConfig: "",
+ webServerConfig: "",
},
- disabled: canEdit,
+ disabled: !isTraefik || canEdit,
resolver: zodResolver(UpdateServerMiddlewareConfigSchema),
});
useEffect(() => {
form.reset({
- traefikConfig: data || "",
+ webServerConfig: data || "",
});
}, [form, form.reset, data]);
const onSubmit = async (data: UpdateServerMiddlewareConfig) => {
+ if (!isTraefik) {
+ return;
+ }
if (!skipYamlValidation) {
- const { valid, error } = validateAndFormatYAML(data.traefikConfig);
+ const { valid, error } = validateAndFormatYAML(data.webServerConfig);
if (!valid) {
- form.setError("traefikConfig", {
+ form.setError("webServerConfig", {
type: "manual",
message: error || "Invalid YAML",
});
return;
}
}
- form.clearErrors("traefikConfig");
+ form.clearErrors("webServerConfig");
await mutateAsync({
- traefikConfig: data.traefikConfig,
+ webServerConfig: data.webServerConfig,
path,
serverId,
})
.then(async () => {
- toast.success("Traefik config Updated");
+ toast.success(`${providerLabel} config updated`);
refetch();
})
.catch(() => {
- toast.error("Error updating the Traefik config");
+ toast.error(`Error updating the ${providerLabel} config`);
});
};
return (
+ {isReadError && (
+
{readError?.message}
+ )}
{isError &&
{error?.message} }
+ {activeProvider === "caddy" && (
+
+ Caddy generated files are read-only from this view. Use Dokploy
+ settings, domains, and migration controls to change generated Caddy
+ config.
+
+ )}
-
-
-
- setSkipYamlValidation(checked === true)
- }
- />
-
- Skip YAML validation (for Go templating)
-
-
-
- Traefik supports Go templating in dynamic configs (e.g.{" "}
- {"{{range}}"}). Configs using
- templates will fail standard YAML validation. Check this to save
- without validation.
-
-
-
- Update
-
+ {isTraefik && (
+
+
+
+ setSkipYamlValidation(checked === true)
+ }
+ />
+
+ Skip YAML validation (for Go templating)
+
+
+
+ Traefik supports Go templating in dynamic configs (e.g.{" "}
+ {"{{range}}"}). Configs using
+ templates will fail standard YAML validation. Check this to save
+ without validation.
+
+
+
+ Update
+
+
-
+ )}
diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx
index 94a5c72a6d..1630959989 100644
--- a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx
+++ b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx
@@ -14,16 +14,39 @@ import { ShowTraefikFile } from "./show-traefik-file";
interface Props {
serverId?: string;
+ activeProvider?: "traefik" | "caddy";
}
-export const ShowTraefikSystem = ({ serverId }: Props) => {
+export const ShowTraefikSystem = ({ serverId, activeProvider }: Props) => {
const [file, setFile] = React.useState(null);
+ const { data: resolvedProvider } =
+ api.settings.getActiveWebServerProvider.useQuery(
+ { serverId },
+ { enabled: !activeProvider },
+ );
+ const provider = activeProvider ?? resolvedProvider;
+ const providerLabel =
+ provider === "caddy"
+ ? "Caddy"
+ : provider === "traefik"
+ ? "Traefik"
+ : "Web Server";
+ const isCaddy = provider === "caddy";
+ const isTraefik = provider === "traefik";
+
+ React.useEffect(() => {
+ setFile(null);
+ }, []);
+
+ React.useEffect(() => {
+ setFile(null);
+ }, [provider]);
const {
data: directories,
isLoading,
error,
isError,
- } = api.settings.readDirectories.useQuery(
+ } = api.settings.readWebServerDirectories.useQuery(
{
serverId,
},
@@ -39,16 +62,22 @@ export const ShowTraefikSystem = ({ serverId }: Props) => {
- Traefik File System
+ {providerLabel} File System
- Manage all the files and directories in {"'/etc/dokploy/traefik'"}
- .
+ {isCaddy
+ ? "Review generated Caddy artifacts such as caddy.json, route fragments, and non-backup migration artifacts."
+ : provider === "traefik"
+ ? "Manage files and directories for the active Traefik web server."
+ : "Review files and directories for the active web server."}
-
- Adding invalid configuration to existing files, can break your
- Traefik instance, preventing access to your applications.
+
+ {isCaddy
+ ? "Caddy generated config is read-only here. Use Dokploy settings, domains, and migration controls to change generated Caddy config."
+ : isTraefik
+ ? "Adding invalid configuration to existing files can break your Traefik instance, preventing access to your applications."
+ : "Review active web server files here. Provider-specific edit controls appear after Dokploy resolves the active provider."}
@@ -70,8 +99,8 @@ export const ShowTraefikSystem = ({ serverId }: Props) => {
{directories?.length === 0 && (
- No directories or files detected in{" "}
- {"'/etc/dokploy/traefik'"}
+ No directories or files detected for the active{" "}
+ {providerLabel} web server.
@@ -87,7 +116,11 @@ export const ShowTraefikSystem = ({ serverId }: Props) => {
/>
{file ? (
-
+
) : (
diff --git a/apps/dokploy/components/dashboard/requests/show-requests.tsx b/apps/dokploy/components/dashboard/requests/show-requests.tsx
index cc4f1764a0..0213b27263 100644
--- a/apps/dokploy/components/dashboard/requests/show-requests.tsx
+++ b/apps/dokploy/components/dashboard/requests/show-requests.tsx
@@ -41,10 +41,13 @@ export type LogEntry = NonNullable<
>[0];
export const ShowRequests = () => {
- const { data: isActive, refetch } =
- api.settings.haveActivateRequests.useQuery();
+ const { data: requestAnalyticsState, refetch } =
+ api.settings.getRequestAnalyticsState.useQuery();
const { mutateAsync: toggleRequests } =
api.settings.toggleRequests.useMutation();
+ const isActive = requestAnalyticsState?.enabled ?? false;
+ const providerLabel =
+ requestAnalyticsState?.provider === "caddy" ? "Caddy" : "Traefik";
const { data: logCleanupStatus } =
api.settings.getLogCleanupStatus.useQuery();
@@ -101,13 +104,13 @@ export const ShowRequests = () => {
Requests
- See all the incoming requests that pass trough Traefik
+ See incoming requests handled by the active web server.
{shouldShowWarning && (
- When you activate, you need to reload traefik to apply the
- changes, you can reload traefik in{" "}
+ After activation, reload {providerLabel} to apply access-log
+ changes. You can reload the active web server in{" "}
{
At the scheduled time, the cleanup job will keep
only the last 1000 entries in the access log file
- and signal Traefik to reopen its log files. The
- default schedule is daily at midnight (0 0 * * *).
+ for the active web server. The default schedule is
+ daily at midnight (0 0 * * *).
@@ -174,7 +177,7 @@ export const ShowRequests = () => {
{
await toggleRequests({ enable: !isActive })
@@ -256,8 +259,8 @@ export const ShowRequests = () => {
Activate requests to see incoming traffic statistics and
- monitor your application's usage. After activation, you'll
- need to reload Traefik for the changes to take effect.
+ monitor your application's usage. After activation, reload
+ the active web server for the changes to take effect.
diff --git a/apps/dokploy/components/dashboard/search-command.tsx b/apps/dokploy/components/dashboard/search-command.tsx
index 841728dc10..d24eb3d51a 100644
--- a/apps/dokploy/components/dashboard/search-command.tsx
+++ b/apps/dokploy/components/dashboard/search-command.tsx
@@ -197,7 +197,7 @@ export const SearchCommand = () => {
setOpen(false);
}}
>
- Traefik
+ Web Server Files
{
diff --git a/apps/dokploy/components/dashboard/settings/certificates/show-certificates.tsx b/apps/dokploy/components/dashboard/settings/certificates/show-certificates.tsx
index c69451c895..d158b381bc 100644
--- a/apps/dokploy/components/dashboard/settings/certificates/show-certificates.tsx
+++ b/apps/dokploy/components/dashboard/settings/certificates/show-certificates.tsx
@@ -46,14 +46,13 @@ export const ShowCertificates = () => {
Certificates
- Create certificates in the Traefik directory
+ Create uploaded certificates for web server domains
- Certificates are created in the Traefik directory. Traefik uses
- these certificates to secure your applications. Using invalid
- certificates can break your Traefik instance, preventing access to
- your applications.
+ Uploaded certificates can be used by supported web server
+ providers to secure your applications. Invalid certificates can
+ break domain access for the services that use them.
diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx
index 65957a881c..5c6d7867d4 100644
--- a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx
+++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx
@@ -13,7 +13,8 @@ import {
} from "@/components/ui/dropdown-menu";
import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation";
import { api } from "@/utils/api";
-import { EditTraefikEnv } from "../../web-server/edit-traefik-env";
+import { CaddyTrustedProxySettings } from "../../web-server/caddy-trusted-proxy-settings";
+import { EditWebServerEnv } from "../../web-server/edit-web-server-env";
import { ManageTraefikPorts } from "../../web-server/manage-traefik-ports";
import { ShowModalLogs } from "../../web-server/show-modal-logs";
@@ -21,16 +22,34 @@ interface Props {
serverId?: string;
}
export const ShowTraefikActions = ({ serverId }: Props) => {
- const { mutateAsync: reloadTraefik, isPending: reloadTraefikIsLoading } =
- api.settings.reloadTraefik.useMutation();
+ const { data: activeProvider, isLoading: isLoadingProvider } =
+ api.settings.getActiveWebServerProvider.useQuery({ serverId });
+ const providerLabel =
+ activeProvider === "caddy"
+ ? "Caddy"
+ : activeProvider === "traefik"
+ ? "Traefik"
+ : "Web Server";
+ const resourceName =
+ activeProvider === "caddy"
+ ? "dokploy-caddy"
+ : activeProvider === "traefik"
+ ? "dokploy-traefik"
+ : null;
+
+ const { mutateAsync: reloadWebServer, isPending: reloadTraefikIsLoading } =
+ api.settings.reloadWebServer.useMutation();
const { mutateAsync: toggleDashboard, isPending: toggleDashboardIsLoading } =
api.settings.toggleDashboard.useMutation();
- const { data: haveTraefikDashboardPortEnabled, refetch: refetchDashboard } =
- api.settings.haveTraefikDashboardPortEnabled.useQuery({
+ const { data: webServerDashboardState, refetch: refetchDashboard } =
+ api.settings.getWebServerDashboardState.useQuery({
serverId,
});
+ const haveTraefikDashboardPortEnabled =
+ webServerDashboardState?.provider === "traefik" &&
+ webServerDashboardState.enabled;
const {
execute: executeWithHealthCheck,
@@ -50,7 +69,7 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
} = useHealthCheckAfterMutation({
initialDelay: 5000,
pollInterval: 4000,
- successMessage: "Traefik Reloaded",
+ successMessage: `${providerLabel} reloaded`,
});
return (
@@ -58,6 +77,8 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
{
>
{
}
variant="outline"
>
- Traefik
+ {providerLabel}
@@ -84,12 +106,12 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
onClick={async () => {
try {
await executeReloadWithHealthCheck(() =>
- reloadTraefik({ serverId }),
+ reloadWebServer({ serverId }),
);
} catch (error) {
const errorMessage =
(error as Error)?.message ||
- "Failed to reload Traefik. Please try again.";
+ `Failed to reload ${providerLabel}. Please try again.`;
toast.error(errorMessage);
}
}}
@@ -98,75 +120,90 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
>
Reload
-
- e.preventDefault()}
- className="cursor-pointer"
+ {resourceName && (
+
- View Logs
-
-
-
+ e.preventDefault()}
+ className="cursor-pointer"
+ >
+ View Logs
+
+
+ )}
+
e.preventDefault()}
className="cursor-pointer"
>
Modify Environment
-
+
-
-
- The Traefik container will be recreated from scratch. This
- means the container will be deleted and created again, which
- may cause downtime in your applications.
-
-
- Are you sure you want to{" "}
- {haveTraefikDashboardPortEnabled ? "disable" : "enable"} the
- Traefik dashboard?
-
-
- }
- onClick={async () => {
- try {
- await executeWithHealthCheck(() =>
- toggleDashboard({
- enableDashboard: !haveTraefikDashboardPortEnabled,
- serverId: serverId,
- }),
- );
- } catch (error) {
- const errorMessage =
- (error as Error)?.message ||
- "Failed to toggle dashboard. Please check if port 8080 is available.";
- toast.error(errorMessage);
+ {activeProvider === "caddy" && (
+
+ e.preventDefault()}
+ className="cursor-pointer"
+ >
+ Trusted Proxies
+
+
+ )}
+
+ {activeProvider === "traefik" && (
+
- e.preventDefault()}
- className="w-full cursor-pointer space-x-3"
+ description={
+
+
+ The Traefik container will be recreated from scratch. This
+ means the container will be deleted and created again, which
+ may cause downtime in your applications.
+
+
+ Are you sure you want to{" "}
+ {haveTraefikDashboardPortEnabled ? "disable" : "enable"} the
+ Traefik dashboard?
+
+
+ }
+ onClick={async () => {
+ try {
+ await executeWithHealthCheck(() =>
+ toggleDashboard({
+ enableDashboard: !haveTraefikDashboardPortEnabled,
+ serverId: serverId,
+ }),
+ );
+ } catch (error) {
+ const errorMessage =
+ (error as Error)?.message ||
+ "Failed to toggle dashboard. Please check if port 8080 is available.";
+ toast.error(errorMessage);
+ }
+ }}
+ disabled={toggleDashboardIsLoading || isHealthCheckExecuting}
+ type="default"
>
-
- {haveTraefikDashboardPortEnabled ? "Disable" : "Enable"}{" "}
- Dashboard
-
-
-
+ e.preventDefault()}
+ className="w-full cursor-pointer space-x-3"
+ >
+
+ {haveTraefikDashboardPortEnabled ? "Disable" : "Enable"}{" "}
+ Dashboard
+
+
+
+ )}
e.preventDefault()}
diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx
index 832d047593..034941e823 100644
--- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx
+++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx
@@ -298,8 +298,9 @@ export const ShowServers = () => {
Configure and initialize your
- server with Docker, Traefik, and
- other essential services
+ server with Docker, the web
+ server, and other essential
+ services
diff --git a/apps/dokploy/components/dashboard/settings/servers/show-traefik-file-system-modal.tsx b/apps/dokploy/components/dashboard/settings/servers/show-traefik-file-system-modal.tsx
index c7f135acd9..34f88278fc 100644
--- a/apps/dokploy/components/dashboard/settings/servers/show-traefik-file-system-modal.tsx
+++ b/apps/dokploy/components/dashboard/settings/servers/show-traefik-file-system-modal.tsx
@@ -1,6 +1,7 @@
import { useState } from "react";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
+import { api } from "@/utils/api";
import { ShowTraefikSystem } from "../../file-system/show-traefik-system";
interface Props {
@@ -9,6 +10,16 @@ interface Props {
export const ShowTraefikFileSystemModal = ({ serverId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
+ const { data: activeProvider } =
+ api.settings.getActiveWebServerProvider.useQuery({
+ serverId,
+ });
+ const providerLabel =
+ activeProvider === "caddy"
+ ? "Caddy"
+ : activeProvider === "traefik"
+ ? "Traefik"
+ : "Web Server";
return (
@@ -17,11 +28,14 @@ export const ShowTraefikFileSystemModal = ({ serverId }: Props) => {
className="w-full cursor-pointer "
onSelect={(e) => e.preventDefault()}
>
- Show Traefik File System
+ Show {providerLabel} File System
-
+
);
diff --git a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx
index e54140bbfa..3b01982432 100644
--- a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx
+++ b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx
@@ -457,9 +457,10 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
render={({ field }) => (
- Access to Traefik Files
+ Access to Web Server Files
- Allow the user to access to the Traefik Tab Files
+ Allow the user to access the active web server file
+ browser
diff --git a/apps/dokploy/components/dashboard/settings/web-server.tsx b/apps/dokploy/components/dashboard/settings/web-server.tsx
index a383fbf7d6..e2f9a85e42 100644
--- a/apps/dokploy/components/dashboard/settings/web-server.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server.tsx
@@ -13,7 +13,10 @@ import { ShowDokployActions } from "./servers/actions/show-dokploy-actions";
import { ShowStorageActions } from "./servers/actions/show-storage-actions";
import { ShowTraefikActions } from "./servers/actions/show-traefik-actions";
import { ToggleDockerCleanup } from "./servers/actions/toggle-docker-cleanup";
+import { CaddyMigrationPanel } from "./web-server/caddy-migration-panel";
+import { CaddyTrustedProxySettings } from "./web-server/caddy-trusted-proxy-settings";
import { UpdateServer } from "./web-server/update-server";
+import { WebServerProviderSelector } from "./web-server/web-server-provider-selector";
export const WebServer = () => {
const { data: webServerSettings } =
@@ -42,6 +45,10 @@ export const WebServer = () => {
*/}
+
+
+
+
diff --git a/apps/dokploy/components/dashboard/settings/web-server/caddy-migration-panel.tsx b/apps/dokploy/components/dashboard/settings/web-server/caddy-migration-panel.tsx
new file mode 100644
index 0000000000..a788b01d9e
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/web-server/caddy-migration-panel.tsx
@@ -0,0 +1,319 @@
+import type { CaddyMigrationReport } from "@dokploy/server";
+import { AlertTriangle, CheckCircle2, Loader2, RotateCcw } from "lucide-react";
+import { useState } from "react";
+import { toast } from "sonner";
+import { AlertBlock } from "@/components/shared/alert-block";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Label } from "@/components/ui/label";
+import { api } from "@/utils/api";
+
+interface Props {
+ serverId?: string;
+}
+
+const statusLabel = (report: CaddyMigrationReport) => {
+ if (report.summary.blockingWarnings > 0) {
+ return "Blocked";
+ }
+ if (report.validation.status !== "passed") {
+ return "Validation required";
+ }
+ return "Ready";
+};
+
+export const CaddyMigrationPanel = ({ serverId }: Props) => {
+ const [migrationId, setMigrationId] = useState
(null);
+ const [maintenanceConfirmed, setMaintenanceConfirmed] = useState(false);
+
+ const { data: activeProvider } =
+ api.settings.getActiveWebServerProvider.useQuery({ serverId });
+ const utils = api.useUtils();
+ const { mutateAsync: prepareMigration, isPending: isPreparing } =
+ api.settings.prepareCaddyMigration.useMutation();
+ const { mutateAsync: applyMigration, isPending: isApplying } =
+ api.settings.applyCaddyMigration.useMutation();
+ const { mutateAsync: rollbackMigration, isPending: isRollingBack } =
+ api.settings.rollbackCaddyMigration.useMutation();
+ const { data: fetchedReport, refetch: refetchReport } =
+ api.settings.getCaddyMigrationReport.useQuery(
+ { migrationId: migrationId ?? "", serverId },
+ { enabled: !!migrationId },
+ );
+ const [preparedReport, setPreparedReport] =
+ useState(null);
+ const report = fetchedReport ?? preparedReport;
+ const blockingWarnings = report?.warnings.filter(
+ (warning) => warning.blocking,
+ );
+ const nonBlockingWarnings = report?.warnings.filter(
+ (warning) => !warning.blocking,
+ );
+ const isMutating = isPreparing || isApplying || isRollingBack;
+ const canApply =
+ !!report &&
+ report.summary.blockingWarnings === 0 &&
+ report.validation.status === "passed" &&
+ maintenanceConfirmed &&
+ !isMutating;
+ const canRollback = !!report && report.status !== "prepared" && !isMutating;
+
+ const handleDryRun = async () => {
+ try {
+ const nextReport = await prepareMigration({ serverId });
+ setPreparedReport(nextReport);
+ setMigrationId(nextReport.migrationId);
+ setMaintenanceConfirmed(false);
+ toast.success("Caddy migration dry run prepared");
+ } catch (error) {
+ toast.error(
+ (error as Error).message || "Error preparing Caddy migration",
+ );
+ }
+ };
+
+ const handleApply = async () => {
+ if (!report) return;
+ try {
+ await applyMigration({
+ migrationId: report.migrationId,
+ serverId,
+ confirmMaintenanceWindow: true,
+ });
+ toast.success("Caddy migration apply started");
+ await utils.settings.getActiveWebServerProvider.invalidate({ serverId });
+ await refetchReport();
+ } catch (error) {
+ toast.error((error as Error).message || "Error applying Caddy migration");
+ }
+ };
+
+ const handleRollback = async () => {
+ if (!report) return;
+ try {
+ await rollbackMigration({ migrationId: report.migrationId, serverId });
+ toast.success("Caddy rollback started");
+ await utils.settings.getActiveWebServerProvider.invalidate({ serverId });
+ await refetchReport();
+ } catch (error) {
+ toast.error(
+ (error as Error).message || "Error rolling back Caddy migration",
+ );
+ }
+ };
+
+ return (
+
+
+
+
+ Caddy migration
+
+ Prepare a reviewable Traefik → Caddy migration before any
+ maintenance-window cutover.
+
+
+
void handleDryRun()}
+ isLoading={isPreparing}
+ disabled={isMutating}
+ >
+ Dry-run migration
+
+
+
+
+ {activeProvider === "caddy" && (
+
+ Caddy is already the active provider. Dry runs are still useful for
+ reviewing translated Traefik artifacts before rollback or follow-up
+ changes.
+
+ )}
+
+ {isPreparing && (
+
+ Preparing migration
+ artifacts...
+
+ )}
+
+ {report && (
+
+
+
+
+
+ Dry run {report.migrationId}
+
+ 0
+ ? "destructive"
+ : "secondary"
+ }
+ >
+ {statusLabel(report)}
+
+
+
+ Created {new Date(report.createdAt).toLocaleString()} ·
+ Status: {report.status} · Validation:{" "}
+ {report.validation.status}
+
+
+
+
+
{report.summary.fragments}
+
Fragments
+
+
+
{report.summary.routes}
+
Routes
+
+
+
+ {report.summary.blockingWarnings}/{report.summary.warnings}
+
+
Blocking
+
+
+
+
+ {report.validation.message && (
+
+ {report.validation.message}
+
+ )}
+
+
+
+
Inputs
+
+
+ Traefik static config:{" "}
+ {report.inputs.traefikStaticConfigFound
+ ? "found"
+ : "not found"}
+
+ Dynamic files: {report.inputs.dynamicFiles.length}
+
+ Application domains: {report.inputs.dbApplicationDomains}
+
+ Compose domains: {report.inputs.dbComposeDomains}
+
+
+
+
Artifacts
+
+ Report: {report.artifactPaths.reportMd}
+ Draft Caddy JSON: {report.artifactPaths.caddyJson}
+ Fragments: {report.artifactPaths.fragmentsDir}
+
+
+
+
+ {blockingWarnings && blockingWarnings.length > 0 ? (
+
+
+
Blocking items
+
+ {blockingWarnings.map((warning, index) => (
+
+ {warning.source ? `${warning.source}: ` : ""}
+ {warning.message}
+
+ ))}
+
+
+
+ ) : (
+
}
+ >
+ No blocking migration items were reported.
+
+ )}
+
+ {nonBlockingWarnings && nonBlockingWarnings.length > 0 && (
+
+
+ Review non-blocking warnings ({nonBlockingWarnings.length})
+
+
+ {nonBlockingWarnings.map((warning, index) => (
+
+ {warning.source ? `${warning.source}: ` : ""}
+ {warning.message}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+ Apply stops Traefik, starts Caddy on ports 80/443/443 UDP,
+ and changes the active provider only after cutover checks
+ pass. Run this during a maintenance window. Changing Caddy
+ settings after a dry run requires preparing a fresh dry run.
+
+
+
+ setMaintenanceConfirmed(checked === true)
+ }
+ />
+
+ I am in a maintenance window and approve the cutover.
+
+
+
+
+
+
+
+ void handleRollback()}
+ isLoading={isRollingBack}
+ disabled={!canRollback}
+ >
+ Roll back to Traefik
+
+ void handleApply()}
+ isLoading={isApplying}
+ disabled={!canApply}
+ >
+ Apply Caddy cutover
+
+
+
+ )}
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/web-server/caddy-trusted-proxy-settings.tsx b/apps/dokploy/components/dashboard/settings/web-server/caddy-trusted-proxy-settings.tsx
new file mode 100644
index 0000000000..e3acb8962a
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/web-server/caddy-trusted-proxy-settings.tsx
@@ -0,0 +1,215 @@
+import { Cloud, ShieldCheck } from "lucide-react";
+import { useEffect, useState } from "react";
+import { toast } from "sonner";
+import { AlertBlock } from "@/components/shared/alert-block";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { Label } from "@/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Switch } from "@/components/ui/switch";
+import { Textarea } from "@/components/ui/textarea";
+import { api } from "@/utils/api";
+
+type TrustedProxyMode = "disabled" | "cloudflare" | "static";
+
+interface Props {
+ serverId?: string;
+ children?: React.ReactNode;
+}
+
+const splitList = (value: string) =>
+ Array.from(
+ new Set(
+ value
+ .split(/[\n,]+/)
+ .map((item) => item.trim())
+ .filter((item) => item.length > 0),
+ ),
+ );
+
+const joinList = (values?: string[] | null) => (values ?? []).join("\n");
+
+const modeLabel = (mode?: TrustedProxyMode) => {
+ switch (mode) {
+ case "cloudflare":
+ return "Cloudflare";
+ case "static":
+ return "Static CIDRs";
+ default:
+ return "Disabled";
+ }
+};
+
+export const CaddyTrustedProxySettings = ({ serverId, children }: Props) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [mode, setMode] = useState("disabled");
+ const [ranges, setRanges] = useState("");
+ const [headers, setHeaders] = useState("");
+ const [strict, setStrict] = useState(true);
+
+ const { data: settings, refetch } =
+ api.settings.getCaddyTrustedProxySettings.useQuery({ serverId });
+ const { mutateAsync: updateSettings, isPending } =
+ api.settings.updateCaddyTrustedProxySettings.useMutation();
+
+ useEffect(() => {
+ if (!settings) return;
+ setMode(settings.mode);
+ setRanges(joinList(settings.ranges));
+ setHeaders(joinList(settings.clientIpHeaders));
+ setStrict(settings.strict !== false);
+ }, [settings]);
+
+ const save = async () => {
+ try {
+ await updateSettings({
+ serverId,
+ mode,
+ ranges: mode === "static" ? splitList(ranges) : [],
+ clientIpHeaders: splitList(headers),
+ strict,
+ });
+ await refetch();
+ toast.success("Caddy trusted proxy settings updated");
+ setIsOpen(false);
+ } catch (error) {
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Error updating Caddy trusted proxy settings",
+ );
+ }
+ };
+
+ const trigger = children ?? (
+
+
+
+
+
Caddy trusted proxies
+
+ Mode: {modeLabel(settings?.mode)}
+
+
+
+
+ Configure
+
+
+ );
+
+ return (
+
+ {trigger}
+
+
+ Caddy trusted proxies
+
+ Configure which proxy IPs Caddy trusts for client IP headers.
+
+
+
+
+
+ Mode
+ setMode(value as TrustedProxyMode)}
+ >
+
+
+
+
+ Disabled
+ Cloudflare
+ Static CIDRs
+
+
+
+
+ {mode === "cloudflare" && (
+
+
+
+
+ Caddy will trust Cloudflare IP ranges and use CF-Connecting-IP
+ before X-Forwarded-For. Use DNS-only or Full (strict) SSL mode
+ for origin traffic; Flexible SSL is not recommended.
+
+
+
+ )}
+
+ {mode === "static" && (
+
+ Trusted CIDR ranges
+
+ )}
+
+ {mode !== "disabled" && (
+ <>
+
+ Client IP headers
+
+
+
+
Strict proxy order
+
+ Require the trusted proxy chain to be ordered.
+
+
+
+
+ >
+ )}
+
+
+
+ setIsOpen(false)}
+ >
+ Cancel
+
+
+ Save
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx
index 1312b96c59..8c5a0e8db2 100644
--- a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx
@@ -1,183 +1,3 @@
-import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import { z } from "zod";
-import { AlertBlock } from "@/components/shared/alert-block";
-import { CodeEditor } from "@/components/shared/code-editor";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation";
-import { api } from "@/utils/api";
+import { EditWebServerEnv } from "./edit-web-server-env";
-const schema = z.object({
- env: z.string(),
-});
-
-type Schema = z.infer;
-
-interface Props {
- children?: React.ReactNode;
- serverId?: string;
-}
-
-export const EditTraefikEnv = ({ children, serverId }: Props) => {
- const [canEdit, setCanEdit] = useState(true);
-
- const { data } = api.settings.readTraefikEnv.useQuery({
- serverId,
- });
-
- const { mutateAsync, isPending, error, isError } =
- api.settings.writeTraefikEnv.useMutation();
-
- const {
- execute: executeWithHealthCheck,
- isExecuting: isHealthCheckExecuting,
- } = useHealthCheckAfterMutation({
- initialDelay: 5000,
- successMessage: "Traefik Env Updated",
- });
-
- const form = useForm({
- defaultValues: {
- env: data || "",
- },
- disabled: canEdit,
- resolver: zodResolver(schema),
- });
-
- useEffect(() => {
- if (data) {
- form.reset({
- env: data || "",
- });
- }
- }, [form, form.reset, data]);
-
- const onSubmit = async (data: Schema) => {
- try {
- await executeWithHealthCheck(() =>
- mutateAsync({
- env: data.env,
- serverId,
- }),
- );
- } catch {
- toast.error("Error updating the Traefik env");
- }
- };
-
- // Add keyboard shortcut for Ctrl+S/Cmd+S
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (
- (e.ctrlKey || e.metaKey) &&
- e.code === "KeyS" &&
- !isPending &&
- !canEdit
- ) {
- e.preventDefault();
- form.handleSubmit(onSubmit)();
- }
- };
-
- document.addEventListener("keydown", handleKeyDown);
- return () => {
- document.removeEventListener("keydown", handleKeyDown);
- };
- }, [form, onSubmit, isPending, canEdit]);
-
- return (
-
- {children}
-
-
- Update Traefik Environment
-
- Update the traefik environment variables
-
-
- {isError && {error?.message} }
-
-
-
-
-
- Update
-
-
-
-
-
- );
-};
+export const EditTraefikEnv = EditWebServerEnv;
diff --git a/apps/dokploy/components/dashboard/settings/web-server/edit-web-server-env.tsx b/apps/dokploy/components/dashboard/settings/web-server/edit-web-server-env.tsx
new file mode 100644
index 0000000000..69d9bfadf5
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/web-server/edit-web-server-env.tsx
@@ -0,0 +1,197 @@
+import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
+import { useEffect, useState } from "react";
+import { useForm } from "react-hook-form";
+import { toast } from "sonner";
+import { z } from "zod";
+import { AlertBlock } from "@/components/shared/alert-block";
+import { CodeEditor } from "@/components/shared/code-editor";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation";
+import { api } from "@/utils/api";
+
+const schema = z.object({
+ env: z.string(),
+});
+
+type Schema = z.infer;
+
+interface Props {
+ children?: React.ReactNode;
+ serverId?: string;
+}
+
+export const EditWebServerEnv = ({ children, serverId }: Props) => {
+ const [canEdit, setCanEdit] = useState(true);
+
+ const { data: activeProvider } =
+ api.settings.getActiveWebServerProvider.useQuery({ serverId });
+ const providerLabel =
+ activeProvider === "caddy"
+ ? "Caddy"
+ : activeProvider === "traefik"
+ ? "Traefik"
+ : "Web Server";
+
+ const { data } = api.settings.readWebServerEnv.useQuery({
+ serverId,
+ });
+
+ const { mutateAsync, isPending, error, isError } =
+ api.settings.writeWebServerEnv.useMutation();
+
+ const {
+ execute: executeWithHealthCheck,
+ isExecuting: isHealthCheckExecuting,
+ } = useHealthCheckAfterMutation({
+ initialDelay: 5000,
+ successMessage: `${providerLabel} env updated`,
+ });
+
+ const form = useForm({
+ defaultValues: {
+ env: data || "",
+ },
+ disabled: canEdit,
+ resolver: zodResolver(schema),
+ });
+
+ useEffect(() => {
+ if (data) {
+ form.reset({
+ env: data || "",
+ });
+ }
+ }, [form, form.reset, data]);
+
+ const onSubmit = async (data: Schema) => {
+ try {
+ await executeWithHealthCheck(() =>
+ mutateAsync({
+ env: data.env,
+ serverId,
+ }),
+ );
+ } catch {
+ toast.error(`Error updating the ${providerLabel} env`);
+ }
+ };
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (
+ (e.ctrlKey || e.metaKey) &&
+ e.code === "KeyS" &&
+ !isPending &&
+ !canEdit
+ ) {
+ e.preventDefault();
+ form.handleSubmit(onSubmit)();
+ }
+ };
+
+ document.addEventListener("keydown", handleKeyDown);
+ return () => {
+ document.removeEventListener("keydown", handleKeyDown);
+ };
+ }, [form, onSubmit, isPending, canEdit]);
+
+ return (
+
+ {children}
+
+
+ Update {providerLabel} Environment
+
+ Update the active web server environment variables.
+
+
+ {isError && {error?.message} }
+
+
+
+
+
+ Update
+
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx
index 6f42c804b5..e7a703908a 100644
--- a/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx
@@ -48,17 +48,21 @@ const PortSchema = z.object({
protocol: z.enum(["tcp", "udp", "sctp"]),
});
-const TraefikPortsSchema = z.object({
+const WebServerPortsSchema = z.object({
ports: z.array(PortSchema),
});
-type TraefikPortsForm = z.infer;
+type WebServerPortsForm = z.infer;
export const ManageTraefikPorts = ({ children, serverId }: Props) => {
const [open, setOpen] = useState(false);
- const form = useForm({
- resolver: zodResolver(TraefikPortsSchema),
+ const { data: activeProvider } =
+ api.settings.getActiveWebServerProvider.useQuery({ serverId });
+ const providerLabel = activeProvider === "caddy" ? "Caddy" : "Traefik";
+
+ const form = useForm({
+ resolver: zodResolver(WebServerPortsSchema),
defaultValues: {
ports: [],
},
@@ -70,12 +74,12 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
});
const { data: currentPorts, refetch: refetchPorts } =
- api.settings.getTraefikPorts.useQuery({
+ api.settings.getWebServerPorts.useQuery({
serverId,
});
const { mutateAsync: updatePorts, isPending } =
- api.settings.updateTraefikPorts.useMutation();
+ api.settings.updateWebServerPorts.useMutation();
const {
execute: executeWithHealthCheck,
@@ -104,7 +108,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
append({ targetPort: 0, publishedPort: 0, protocol: "tcp" });
};
- const onSubmit = async (data: TraefikPortsForm) => {
+ const onSubmit = async (data: WebServerPortsForm) => {
try {
await executeWithHealthCheck(() =>
updatePorts({
@@ -114,7 +118,9 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
);
setOpen(false);
} catch (error) {
- toast.error((error as Error).message || "Error updating Traefik ports");
+ toast.error(
+ (error as Error).message || `Error updating ${providerLabel} ports`,
+ );
}
};
@@ -132,7 +138,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
- Add or remove additional ports for Traefik
+ Add or remove additional ports for {providerLabel}
{fields.length} port mapping{fields.length !== 1 ? "s" : ""}{" "}
configured
@@ -286,7 +292,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
Each port mapping defines how external traffic reaches
- your containers through Traefik.
+ your containers through {providerLabel}.
@@ -300,8 +306,8 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
All ports are bound directly to the host machine,
- allowing Traefik to handle incoming traffic and route
- it appropriately to your services.
+ allowing {providerLabel} to handle incoming traffic
+ and route it appropriately to your services.
@@ -309,9 +315,9 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
)}
- The Traefik container will be recreated from scratch. This
- means the container will be deleted and created again, which
- may cause downtime in your applications.
+ The {providerLabel} resource will be recreated from scratch.
+ This means the container or service will be deleted and
+ created again, which may cause downtime in your applications.
diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-webserver.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-webserver.tsx
index abeba47c4b..2fd2c45630 100644
--- a/apps/dokploy/components/dashboard/settings/web-server/update-webserver.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server/update-webserver.tsx
@@ -30,7 +30,10 @@ type ServiceStatus = {
type HealthResult = {
postgres: ServiceStatus;
redis: ServiceStatus;
- traefik: ServiceStatus;
+ webServer: ServiceStatus & {
+ provider: "traefik" | "caddy";
+ };
+ traefik?: ServiceStatus;
};
type ModalState = "idle" | "checking" | "results" | "updating";
@@ -55,6 +58,11 @@ const ServiceStatusItem = ({
);
+const webServerLabels: Record = {
+ traefik: "Traefik",
+ caddy: "Caddy",
+};
+
export const UpdateWebServer = () => {
const [modalState, setModalState] = useState("idle");
const [open, setOpen] = useState(false);
@@ -85,7 +93,7 @@ export const UpdateWebServer = () => {
healthResult &&
healthResult.postgres.status === "healthy" &&
healthResult.redis.status === "healthy" &&
- healthResult.traefik.status === "healthy";
+ healthResult.webServer.status === "healthy";
const checkIsUpdateFinished = async () => {
try {
@@ -174,7 +182,7 @@ export const UpdateWebServer = () => {
{modalState === "checking" && (
- Checking PostgreSQL, Redis and Traefik...
+ Checking PostgreSQL, Redis and the active web server...
)}
@@ -190,8 +198,8 @@ export const UpdateWebServer = () => {
service={healthResult.redis}
/>
diff --git a/apps/dokploy/components/dashboard/settings/web-server/web-server-provider-selector.tsx b/apps/dokploy/components/dashboard/settings/web-server/web-server-provider-selector.tsx
new file mode 100644
index 0000000000..539f69cbc5
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/web-server/web-server-provider-selector.tsx
@@ -0,0 +1,101 @@
+import type { WebServerProvider } from "@dokploy/server";
+import { toast } from "sonner";
+import { AlertBlock } from "@/components/shared/alert-block";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { api } from "@/utils/api";
+
+interface Props {
+ serverId?: string;
+}
+
+const providerLabels: Record = {
+ traefik: "Traefik",
+ caddy: "Caddy",
+};
+
+export const WebServerProviderSelector = ({ serverId }: Props) => {
+ const utils = api.useUtils();
+ const { data: activeProvider, isLoading } =
+ api.settings.getActiveWebServerProvider.useQuery({ serverId });
+ const { mutateAsync: updateProvider, isPending } =
+ api.settings.updateActiveWebServerProvider.useMutation();
+
+ const handleProviderChange = async (provider: WebServerProvider) => {
+ if (!activeProvider || provider === activeProvider) return;
+ if (provider === "caddy") {
+ toast.error(
+ "Run and apply a Caddy migration dry run to activate Caddy safely.",
+ );
+ return;
+ }
+ if (activeProvider === "caddy" && provider === "traefik") {
+ toast.error("Use the migration rollback action to return to Traefik.");
+ return;
+ }
+
+ try {
+ await updateProvider({ provider, serverId });
+ await utils.settings.getActiveWebServerProvider.invalidate({ serverId });
+ await utils.settings.getWebServerDashboardState.invalidate({ serverId });
+ await utils.settings.readWebServerConfig.invalidate({ serverId });
+ toast.success(`Active web server set to ${providerLabels[provider]}`);
+ } catch (error) {
+ toast.error(
+ (error as Error).message || "Error updating active web server provider",
+ );
+ }
+ };
+
+ return (
+
+
+ Active provider
+
+ Review the active web server Dokploy uses for provider-aware reload,
+ configuration, and new domain changes.
+
+
+
+
+ void handleProviderChange(value as WebServerProvider)
+ }
+ >
+
+
+
+
+ Traefik
+ Caddy
+
+
+ {activeProvider === "traefik" ? (
+
+ Traefik workflows remain available. Caddy activation is handled by
+ the migration apply flow below so cutover checks are not bypassed.
+
+ ) : (
+
+ Caddy is the active provider. Traefik-specific editors remain
+ available only for existing Traefik configuration review.
+
+ )}
+
+
+ );
+};
diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx
index c9a4d5dfba..f7646ef0d0 100644
--- a/apps/dokploy/components/layouts/side.tsx
+++ b/apps/dokploy/components/layouts/side.tsx
@@ -188,10 +188,10 @@ const MENU: Menu = {
},
{
isSingle: true,
- title: "Traefik File System",
+ title: "Web Server Files",
url: "/dashboard/traefik",
icon: GalleryVerticalEnd,
- // Only enabled for users with access to Traefik files in non-cloud environments
+ // `traefikFiles` is the legacy permission key for active web-server files.
isEnabled: ({ permissions, isCloud }) =>
!!(permissions?.traefikFiles.read && !isCloud),
},
diff --git a/apps/dokploy/components/layouts/user-nav.tsx b/apps/dokploy/components/layouts/user-nav.tsx
index 4df84bb14d..a9f3b76cb4 100644
--- a/apps/dokploy/components/layouts/user-nav.tsx
+++ b/apps/dokploy/components/layouts/user-nav.tsx
@@ -102,7 +102,7 @@ export const UserNav = () => {
router.push("/dashboard/traefik");
}}
>
- Traefik
+ Web Server Files
)}
{permissions?.docker.read && (
diff --git a/apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx b/apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx
index 51b31b84c7..07020d6695 100644
--- a/apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx
+++ b/apps/dokploy/components/proprietary/roles/manage-custom-roles.tsx
@@ -76,8 +76,8 @@ const RESOURCE_META: Record = {
description: "Access to Git providers (GitHub, GitLab, Bitbucket, Gitea)",
},
traefikFiles: {
- label: "Traefik Files",
- description: "Access to the Traefik file system configuration",
+ label: "Web Server Files",
+ description: "Access to the active web server file browser",
},
api: {
label: "API / CLI",
@@ -242,11 +242,11 @@ const ACTION_META: Record<
traefikFiles: {
read: {
label: "Read",
- description: "View Traefik configuration files",
+ description: "View active web server configuration files",
},
write: {
label: "Write",
- description: "Edit and save Traefik configuration files",
+ description: "Edit and save active web server configuration files",
},
},
api: {
diff --git a/apps/dokploy/components/shared/code-editor.tsx b/apps/dokploy/components/shared/code-editor.tsx
index 3f07fa8b39..bfc9f2a31b 100644
--- a/apps/dokploy/components/shared/code-editor.tsx
+++ b/apps/dokploy/components/shared/code-editor.tsx
@@ -190,7 +190,7 @@ export const CodeEditor = ({
)}
>
{props.disabled && (
-
+
)}
diff --git a/apps/dokploy/drizzle/0170_web_server_provider.sql b/apps/dokploy/drizzle/0170_web_server_provider.sql
new file mode 100644
index 0000000000..24b513d90c
--- /dev/null
+++ b/apps/dokploy/drizzle/0170_web_server_provider.sql
@@ -0,0 +1,3 @@
+CREATE TYPE "public"."webServerProvider" AS ENUM('traefik', 'caddy');--> statement-breakpoint
+ALTER TABLE "server" ADD COLUMN "webServerProvider" "webServerProvider" DEFAULT 'traefik' NOT NULL;--> statement-breakpoint
+ALTER TABLE "webServerSettings" ADD COLUMN "webServerProvider" "webServerProvider" DEFAULT 'traefik' NOT NULL;
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/0171_caddy_trusted_proxy_config.sql b/apps/dokploy/drizzle/0171_caddy_trusted_proxy_config.sql
new file mode 100644
index 0000000000..3e86ae140e
--- /dev/null
+++ b/apps/dokploy/drizzle/0171_caddy_trusted_proxy_config.sql
@@ -0,0 +1,2 @@
+ALTER TABLE "server" ADD COLUMN "caddyTrustedProxyConfig" jsonb DEFAULT 'null'::jsonb;--> statement-breakpoint
+ALTER TABLE "webServerSettings" ADD COLUMN "caddyTrustedProxyConfig" jsonb DEFAULT 'null'::jsonb;
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/0172_normal_gauntlet.sql b/apps/dokploy/drizzle/0172_normal_gauntlet.sql
new file mode 100644
index 0000000000..aebd384685
--- /dev/null
+++ b/apps/dokploy/drizzle/0172_normal_gauntlet.sql
@@ -0,0 +1 @@
+ALTER TABLE "webServerSettings" ADD COLUMN "requestLogsEnabled" boolean DEFAULT false NOT NULL;
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/0170_snapshot.json b/apps/dokploy/drizzle/meta/0170_snapshot.json
new file mode 100644
index 0000000000..5f624ff410
--- /dev/null
+++ b/apps/dokploy/drizzle/meta/0170_snapshot.json
@@ -0,0 +1,8356 @@
+{
+ "id": "93bfd2e4-aeef-4158-9b26-97da9f79b99b",
+ "prevId": "f3f22c15-d2d1-4ed1-9561-24aa98e30231",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is2FAEnabled": {
+ "name": "is2FAEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resetPasswordToken": {
+ "name": "resetPasswordToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resetPasswordExpiresAt": {
+ "name": "resetPasswordExpiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmationToken": {
+ "name": "confirmationToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmationExpiresAt": {
+ "name": "confirmationExpiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.apikey": {
+ "name": "apikey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start": {
+ "name": "start",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config_id": {
+ "name": "config_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'default'"
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refill_interval": {
+ "name": "refill_interval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refill_amount": {
+ "name": "refill_amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_refill_at": {
+ "name": "last_refill_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_enabled": {
+ "name": "rate_limit_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_time_window": {
+ "name": "rate_limit_time_window",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_max": {
+ "name": "rate_limit_max",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "request_count": {
+ "name": "request_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "remaining": {
+ "name": "remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "apikey_reference_id_user_id_fk": {
+ "name": "apikey_reference_id_user_id_fk",
+ "tableFrom": "apikey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "reference_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation": {
+ "name": "invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invitation_organization_id_organization_id_fk": {
+ "name": "invitation_organization_id_organization_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_inviter_id_user_id_fk": {
+ "name": "invitation_inviter_id_user_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "user",
+ "columnsFrom": [
+ "inviter_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.member": {
+ "name": "member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateProjects": {
+ "name": "canCreateProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToSSHKeys": {
+ "name": "canAccessToSSHKeys",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateServices": {
+ "name": "canCreateServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteProjects": {
+ "name": "canDeleteProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteServices": {
+ "name": "canDeleteServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToDocker": {
+ "name": "canAccessToDocker",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToAPI": {
+ "name": "canAccessToAPI",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToGitProviders": {
+ "name": "canAccessToGitProviders",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToTraefikFiles": {
+ "name": "canAccessToTraefikFiles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteEnvironments": {
+ "name": "canDeleteEnvironments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateEnvironments": {
+ "name": "canCreateEnvironments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accesedProjects": {
+ "name": "accesedProjects",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedEnvironments": {
+ "name": "accessedEnvironments",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accesedServices": {
+ "name": "accesedServices",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedGitProviders": {
+ "name": "accessedGitProviders",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedServers": {
+ "name": "accessedServers",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "member_organization_id_organization_id_fk": {
+ "name": "member_organization_id_organization_id_fk",
+ "tableFrom": "member",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "member_user_id_user_id_fk": {
+ "name": "member_user_id_user_id_fk",
+ "tableFrom": "member",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "organization_owner_id_user_id_fk": {
+ "name": "organization_owner_id_user_id_fk",
+ "tableFrom": "organization",
+ "tableTo": "user",
+ "columnsFrom": [
+ "owner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "organization_slug_unique": {
+ "name": "organization_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization_role": {
+ "name": "organization_role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "organizationRole_organizationId_idx": {
+ "name": "organizationRole_organizationId_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organizationRole_role_idx": {
+ "name": "organizationRole_role_idx",
+ "columns": [
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "organization_role_organization_id_organization_id_fk": {
+ "name": "organization_role_organization_id_organization_id_fk",
+ "tableFrom": "organization_role",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.two_factor": {
+ "name": "two_factor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backup_codes": {
+ "name": "backup_codes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "two_factor_user_id_user_id_fk": {
+ "name": "two_factor_user_id_user_id_fk",
+ "tableFrom": "two_factor",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ai": {
+ "name": "ai",
+ "schema": "",
+ "columns": {
+ "aiId": {
+ "name": "aiId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiUrl": {
+ "name": "apiUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isEnabled": {
+ "name": "isEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ai_organizationId_organization_id_fk": {
+ "name": "ai_organizationId_organization_id_fk",
+ "tableFrom": "ai",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.application": {
+ "name": "application",
+ "schema": "",
+ "columns": {
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewEnv": {
+ "name": "previewEnv",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "watchPaths": {
+ "name": "watchPaths",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewBuildArgs": {
+ "name": "previewBuildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewBuildSecrets": {
+ "name": "previewBuildSecrets",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewLabels": {
+ "name": "previewLabels",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewWildcard": {
+ "name": "previewWildcard",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewPort": {
+ "name": "previewPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "previewHttps": {
+ "name": "previewHttps",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "previewPath": {
+ "name": "previewPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "previewCustomCertResolver": {
+ "name": "previewCustomCertResolver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewLimit": {
+ "name": "previewLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3
+ },
+ "isPreviewDeploymentsActive": {
+ "name": "isPreviewDeploymentsActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "previewRequireCollaboratorPermissions": {
+ "name": "previewRequireCollaboratorPermissions",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "rollbackActive": {
+ "name": "rollbackActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "buildArgs": {
+ "name": "buildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildSecrets": {
+ "name": "buildSecrets",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subtitle": {
+ "name": "subtitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "cleanCache": {
+ "name": "cleanCache",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildPath": {
+ "name": "buildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "triggerType": {
+ "name": "triggerType",
+ "type": "triggerType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'push'"
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBuildPath": {
+ "name": "gitlabBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaRepository": {
+ "name": "giteaRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaOwner": {
+ "name": "giteaOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBranch": {
+ "name": "giteaBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBuildPath": {
+ "name": "giteaBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepositorySlug": {
+ "name": "bitbucketRepositorySlug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBuildPath": {
+ "name": "bitbucketBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBuildPath": {
+ "name": "customGitBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableSubmodules": {
+ "name": "enableSubmodules",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerfile": {
+ "name": "dockerfile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'Dockerfile'"
+ },
+ "dockerContextPath": {
+ "name": "dockerContextPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerBuildStage": {
+ "name": "dockerBuildStage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dropBuildPath": {
+ "name": "dropBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "buildType": {
+ "name": "buildType",
+ "type": "buildType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'nixpacks'"
+ },
+ "railpackVersion": {
+ "name": "railpackVersion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0.15.4'"
+ },
+ "herokuVersion": {
+ "name": "herokuVersion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'24'"
+ },
+ "publishDirectory": {
+ "name": "publishDirectory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isStaticSpa": {
+ "name": "isStaticSpa",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createEnvFile": {
+ "name": "createEnvFile",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackRegistryId": {
+ "name": "rollbackRegistryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildServerId": {
+ "name": "buildServerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildRegistryId": {
+ "name": "buildRegistryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "application",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_registryId_registry_registryId_fk": {
+ "name": "application_registryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "registryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_rollbackRegistryId_registry_registryId_fk": {
+ "name": "application_rollbackRegistryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "rollbackRegistryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_environmentId_environment_environmentId_fk": {
+ "name": "application_environmentId_environment_environmentId_fk",
+ "tableFrom": "application",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_githubId_github_githubId_fk": {
+ "name": "application_githubId_github_githubId_fk",
+ "tableFrom": "application",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_gitlabId_gitlab_gitlabId_fk": {
+ "name": "application_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_giteaId_gitea_giteaId_fk": {
+ "name": "application_giteaId_gitea_giteaId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitea",
+ "columnsFrom": [
+ "giteaId"
+ ],
+ "columnsTo": [
+ "giteaId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "application_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "application",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_serverId_server_serverId_fk": {
+ "name": "application_serverId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_buildServerId_server_serverId_fk": {
+ "name": "application_buildServerId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "buildServerId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_buildRegistryId_registry_registryId_fk": {
+ "name": "application_buildRegistryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "buildRegistryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "application_appName_unique": {
+ "name": "application_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_log": {
+ "name": "audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_email": {
+ "name": "user_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_role": {
+ "name": "user_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resource_name": {
+ "name": "resource_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "auditLog_organizationId_idx": {
+ "name": "auditLog_organizationId_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "auditLog_userId_idx": {
+ "name": "auditLog_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "auditLog_createdAt_idx": {
+ "name": "auditLog_createdAt_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_log_organization_id_organization_id_fk": {
+ "name": "audit_log_organization_id_organization_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_log_user_id_user_id_fk": {
+ "name": "audit_log_user_id_user_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.backup": {
+ "name": "backup",
+ "schema": "",
+ "columns": {
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule": {
+ "name": "schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "database": {
+ "name": "database",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "keepLatestCount": {
+ "name": "keepLatestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backupType": {
+ "name": "backupType",
+ "type": "backupType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'database'"
+ },
+ "databaseType": {
+ "name": "databaseType",
+ "type": "databaseType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "backup_destinationId_destination_destinationId_fk": {
+ "name": "backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_composeId_compose_composeId_fk": {
+ "name": "backup_composeId_compose_composeId_fk",
+ "tableFrom": "backup",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_postgresId_postgres_postgresId_fk": {
+ "name": "backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mongoId_mongo_mongoId_fk": {
+ "name": "backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_libsqlId_libsql_libsqlId_fk": {
+ "name": "backup_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_userId_user_id_fk": {
+ "name": "backup_userId_user_id_fk",
+ "tableFrom": "backup",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "backup_appName_unique": {
+ "name": "backup_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bitbucket": {
+ "name": "bitbucket",
+ "schema": "",
+ "columns": {
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bitbucketUsername": {
+ "name": "bitbucketUsername",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketEmail": {
+ "name": "bitbucketEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "appPassword": {
+ "name": "appPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketWorkspaceName": {
+ "name": "bitbucketWorkspaceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bitbucket_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "bitbucket",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.certificate": {
+ "name": "certificate",
+ "schema": "",
+ "columns": {
+ "certificateId": {
+ "name": "certificateId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificateData": {
+ "name": "certificateData",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificatePath": {
+ "name": "certificatePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "autoRenew": {
+ "name": "autoRenew",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "certificate_organizationId_organization_id_fk": {
+ "name": "certificate_organizationId_organization_id_fk",
+ "tableFrom": "certificate",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "certificate_serverId_server_serverId_fk": {
+ "name": "certificate_serverId_server_serverId_fk",
+ "tableFrom": "certificate",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "certificate_certificatePath_unique": {
+ "name": "certificate_certificatePath_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "certificatePath"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.compose": {
+ "name": "compose",
+ "schema": "",
+ "columns": {
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeFile": {
+ "name": "composeFile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceTypeCompose",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "composeType": {
+ "name": "composeType",
+ "type": "composeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'docker-compose'"
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepositorySlug": {
+ "name": "bitbucketRepositorySlug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaRepository": {
+ "name": "giteaRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaOwner": {
+ "name": "giteaOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBranch": {
+ "name": "giteaBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enableSubmodules": {
+ "name": "enableSubmodules",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "composePath": {
+ "name": "composePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'./docker-compose.yml'"
+ },
+ "suffix": {
+ "name": "suffix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "randomize": {
+ "name": "randomize",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isolatedDeployment": {
+ "name": "isolatedDeployment",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isolatedDeploymentsVolume": {
+ "name": "isolatedDeploymentsVolume",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "triggerType": {
+ "name": "triggerType",
+ "type": "triggerType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'push'"
+ },
+ "composeStatus": {
+ "name": "composeStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "watchPaths": {
+ "name": "watchPaths",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "compose",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_environmentId_environment_environmentId_fk": {
+ "name": "compose_environmentId_environment_environmentId_fk",
+ "tableFrom": "compose",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "compose_githubId_github_githubId_fk": {
+ "name": "compose_githubId_github_githubId_fk",
+ "tableFrom": "compose",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_gitlabId_gitlab_gitlabId_fk": {
+ "name": "compose_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "compose_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "compose",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_giteaId_gitea_giteaId_fk": {
+ "name": "compose_giteaId_gitea_giteaId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitea",
+ "columnsFrom": [
+ "giteaId"
+ ],
+ "columnsTo": [
+ "giteaId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_serverId_server_serverId_fk": {
+ "name": "compose_serverId_server_serverId_fk",
+ "tableFrom": "compose",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment": {
+ "name": "deployment",
+ "schema": "",
+ "columns": {
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "deploymentStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'running'"
+ },
+ "logPath": {
+ "name": "logPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pid": {
+ "name": "pid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isPreviewDeployment": {
+ "name": "isPreviewDeployment",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "startedAt": {
+ "name": "startedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finishedAt": {
+ "name": "finishedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scheduleId": {
+ "name": "scheduleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeBackupId": {
+ "name": "volumeBackupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildServerId": {
+ "name": "buildServerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "deployment_applicationId_application_applicationId_fk": {
+ "name": "deployment_applicationId_application_applicationId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_composeId_compose_composeId_fk": {
+ "name": "deployment_composeId_compose_composeId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_serverId_server_serverId_fk": {
+ "name": "deployment_serverId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": {
+ "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "preview_deployments",
+ "columnsFrom": [
+ "previewDeploymentId"
+ ],
+ "columnsTo": [
+ "previewDeploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_scheduleId_schedule_scheduleId_fk": {
+ "name": "deployment_scheduleId_schedule_scheduleId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "schedule",
+ "columnsFrom": [
+ "scheduleId"
+ ],
+ "columnsTo": [
+ "scheduleId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_backupId_backup_backupId_fk": {
+ "name": "deployment_backupId_backup_backupId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "backup",
+ "columnsFrom": [
+ "backupId"
+ ],
+ "columnsTo": [
+ "backupId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_rollbackId_rollback_rollbackId_fk": {
+ "name": "deployment_rollbackId_rollback_rollbackId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "rollback",
+ "columnsFrom": [
+ "rollbackId"
+ ],
+ "columnsTo": [
+ "rollbackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_volumeBackupId_volume_backup_volumeBackupId_fk": {
+ "name": "deployment_volumeBackupId_volume_backup_volumeBackupId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "volume_backup",
+ "columnsFrom": [
+ "volumeBackupId"
+ ],
+ "columnsTo": [
+ "volumeBackupId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_buildServerId_server_serverId_fk": {
+ "name": "deployment_buildServerId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "buildServerId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.destination": {
+ "name": "destination",
+ "schema": "",
+ "columns": {
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accessKey": {
+ "name": "accessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secretAccessKey": {
+ "name": "secretAccessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bucket": {
+ "name": "bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "region": {
+ "name": "region",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "additionalFlags": {
+ "name": "additionalFlags",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "destination_organizationId_organization_id_fk": {
+ "name": "destination_organizationId_organization_id_fk",
+ "tableFrom": "destination",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.domain": {
+ "name": "domain",
+ "schema": "",
+ "columns": {
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "customEntrypoint": {
+ "name": "customEntrypoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domainType": {
+ "name": "domainType",
+ "type": "domainType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'application'"
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customCertResolver": {
+ "name": "customCertResolver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "internalPath": {
+ "name": "internalPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "stripPath": {
+ "name": "stripPath",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "middlewares": {
+ "name": "middlewares",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "domain_composeId_compose_composeId_fk": {
+ "name": "domain_composeId_compose_composeId_fk",
+ "tableFrom": "domain",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_applicationId_application_applicationId_fk": {
+ "name": "domain_applicationId_application_applicationId_fk",
+ "tableFrom": "domain",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": {
+ "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk",
+ "tableFrom": "domain",
+ "tableTo": "preview_deployments",
+ "columnsFrom": [
+ "previewDeploymentId"
+ ],
+ "columnsTo": [
+ "previewDeploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment": {
+ "name": "environment",
+ "schema": "",
+ "columns": {
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isDefault": {
+ "name": "isDefault",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_projectId_project_projectId_fk": {
+ "name": "environment_projectId_project_projectId_fk",
+ "tableFrom": "environment",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_provider": {
+ "name": "git_provider",
+ "schema": "",
+ "columns": {
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerType": {
+ "name": "providerType",
+ "type": "gitProviderType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sharedWithOrganization": {
+ "name": "sharedWithOrganization",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_provider_organizationId_organization_id_fk": {
+ "name": "git_provider_organizationId_organization_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_provider_userId_user_id_fk": {
+ "name": "git_provider_userId_user_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gitea": {
+ "name": "gitea",
+ "schema": "",
+ "columns": {
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "giteaUrl": {
+ "name": "giteaUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'https://gitea.com'"
+ },
+ "giteaInternalUrl": {
+ "name": "giteaInternalUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'repo,repo:status,read:user,read:org'"
+ },
+ "last_authenticated_at": {
+ "name": "last_authenticated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitea_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitea",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.github": {
+ "name": "github",
+ "schema": "",
+ "columns": {
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "githubAppName": {
+ "name": "githubAppName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubAppId": {
+ "name": "githubAppId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientId": {
+ "name": "githubClientId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientSecret": {
+ "name": "githubClientSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubInstallationId": {
+ "name": "githubInstallationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubPrivateKey": {
+ "name": "githubPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubWebhookSecret": {
+ "name": "githubWebhookSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "github_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "github_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "github",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gitlab": {
+ "name": "gitlab",
+ "schema": "",
+ "columns": {
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "gitlabUrl": {
+ "name": "gitlabUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'https://gitlab.com'"
+ },
+ "gitlabInternalUrl": {
+ "name": "gitlabInternalUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitlab_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitlab",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.libsql": {
+ "name": "libsql",
+ "schema": "",
+ "columns": {
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sqldNode": {
+ "name": "sqldNode",
+ "type": "sqldNode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'primary'"
+ },
+ "sqldPrimaryUrl": {
+ "name": "sqldPrimaryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableNamespaces": {
+ "name": "enableNamespaces",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalGRPCPort": {
+ "name": "externalGRPCPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalAdminPort": {
+ "name": "externalAdminPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "libsql_environmentId_environment_environmentId_fk": {
+ "name": "libsql_environmentId_environment_environmentId_fk",
+ "tableFrom": "libsql",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "libsql_serverId_server_serverId_fk": {
+ "name": "libsql_serverId_server_serverId_fk",
+ "tableFrom": "libsql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "libsql_appName_unique": {
+ "name": "libsql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mariadb": {
+ "name": "mariadb",
+ "schema": "",
+ "columns": {
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mariadb_environmentId_environment_environmentId_fk": {
+ "name": "mariadb_environmentId_environment_environmentId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mariadb_serverId_server_serverId_fk": {
+ "name": "mariadb_serverId_server_serverId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mariadb_appName_unique": {
+ "name": "mariadb_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mongo": {
+ "name": "mongo",
+ "schema": "",
+ "columns": {
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'mongo:8'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicaSets": {
+ "name": "replicaSets",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mongo_environmentId_environment_environmentId_fk": {
+ "name": "mongo_environmentId_environment_environmentId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mongo_serverId_server_serverId_fk": {
+ "name": "mongo_serverId_server_serverId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mongo_appName_unique": {
+ "name": "mongo_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mount": {
+ "name": "mount",
+ "schema": "",
+ "columns": {
+ "mountId": {
+ "name": "mountId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "mountType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hostPath": {
+ "name": "hostPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "mountPath": {
+ "name": "mountPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mount_applicationId_application_applicationId_fk": {
+ "name": "mount_applicationId_application_applicationId_fk",
+ "tableFrom": "mount",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_composeId_compose_composeId_fk": {
+ "name": "mount_composeId_compose_composeId_fk",
+ "tableFrom": "mount",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_libsqlId_libsql_libsqlId_fk": {
+ "name": "mount_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mariadbId_mariadb_mariadbId_fk": {
+ "name": "mount_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mongoId_mongo_mongoId_fk": {
+ "name": "mount_mongoId_mongo_mongoId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mysqlId_mysql_mysqlId_fk": {
+ "name": "mount_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_postgresId_postgres_postgresId_fk": {
+ "name": "mount_postgresId_postgres_postgresId_fk",
+ "tableFrom": "mount",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_redisId_redis_redisId_fk": {
+ "name": "mount_redisId_redis_redisId_fk",
+ "tableFrom": "mount",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mysql": {
+ "name": "mysql",
+ "schema": "",
+ "columns": {
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mysql_environmentId_environment_environmentId_fk": {
+ "name": "mysql_environmentId_environment_environmentId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mysql_serverId_server_serverId_fk": {
+ "name": "mysql_serverId_server_serverId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mysql_appName_unique": {
+ "name": "mysql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.custom": {
+ "name": "custom",
+ "schema": "",
+ "columns": {
+ "customId": {
+ "name": "customId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "headers": {
+ "name": "headers",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.discord": {
+ "name": "discord",
+ "schema": "",
+ "columns": {
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decoration": {
+ "name": "decoration",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.email": {
+ "name": "email",
+ "schema": "",
+ "columns": {
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "smtpServer": {
+ "name": "smtpServer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "smtpPort": {
+ "name": "smtpPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gotify": {
+ "name": "gotify",
+ "schema": "",
+ "columns": {
+ "gotifyId": {
+ "name": "gotifyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverUrl": {
+ "name": "serverUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appToken": {
+ "name": "appToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 5
+ },
+ "decoration": {
+ "name": "decoration",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lark": {
+ "name": "lark",
+ "schema": "",
+ "columns": {
+ "larkId": {
+ "name": "larkId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mattermost": {
+ "name": "mattermost",
+ "schema": "",
+ "columns": {
+ "mattermostId": {
+ "name": "mattermostId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification": {
+ "name": "notification",
+ "schema": "",
+ "columns": {
+ "notificationId": {
+ "name": "notificationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appDeploy": {
+ "name": "appDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "appBuildError": {
+ "name": "appBuildError",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "databaseBackup": {
+ "name": "databaseBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "volumeBackup": {
+ "name": "volumeBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployRestart": {
+ "name": "dokployRestart",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployBackup": {
+ "name": "dokployBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerCleanup": {
+ "name": "dockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "serverThreshold": {
+ "name": "serverThreshold",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "notificationType": {
+ "name": "notificationType",
+ "type": "notificationType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resendId": {
+ "name": "resendId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gotifyId": {
+ "name": "gotifyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ntfyId": {
+ "name": "ntfyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mattermostId": {
+ "name": "mattermostId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customId": {
+ "name": "customId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "larkId": {
+ "name": "larkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pushoverId": {
+ "name": "pushoverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "teamsId": {
+ "name": "teamsId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_slackId_slack_slackId_fk": {
+ "name": "notification_slackId_slack_slackId_fk",
+ "tableFrom": "notification",
+ "tableTo": "slack",
+ "columnsFrom": [
+ "slackId"
+ ],
+ "columnsTo": [
+ "slackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_telegramId_telegram_telegramId_fk": {
+ "name": "notification_telegramId_telegram_telegramId_fk",
+ "tableFrom": "notification",
+ "tableTo": "telegram",
+ "columnsFrom": [
+ "telegramId"
+ ],
+ "columnsTo": [
+ "telegramId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_discordId_discord_discordId_fk": {
+ "name": "notification_discordId_discord_discordId_fk",
+ "tableFrom": "notification",
+ "tableTo": "discord",
+ "columnsFrom": [
+ "discordId"
+ ],
+ "columnsTo": [
+ "discordId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_emailId_email_emailId_fk": {
+ "name": "notification_emailId_email_emailId_fk",
+ "tableFrom": "notification",
+ "tableTo": "email",
+ "columnsFrom": [
+ "emailId"
+ ],
+ "columnsTo": [
+ "emailId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_resendId_resend_resendId_fk": {
+ "name": "notification_resendId_resend_resendId_fk",
+ "tableFrom": "notification",
+ "tableTo": "resend",
+ "columnsFrom": [
+ "resendId"
+ ],
+ "columnsTo": [
+ "resendId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_gotifyId_gotify_gotifyId_fk": {
+ "name": "notification_gotifyId_gotify_gotifyId_fk",
+ "tableFrom": "notification",
+ "tableTo": "gotify",
+ "columnsFrom": [
+ "gotifyId"
+ ],
+ "columnsTo": [
+ "gotifyId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_ntfyId_ntfy_ntfyId_fk": {
+ "name": "notification_ntfyId_ntfy_ntfyId_fk",
+ "tableFrom": "notification",
+ "tableTo": "ntfy",
+ "columnsFrom": [
+ "ntfyId"
+ ],
+ "columnsTo": [
+ "ntfyId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_mattermostId_mattermost_mattermostId_fk": {
+ "name": "notification_mattermostId_mattermost_mattermostId_fk",
+ "tableFrom": "notification",
+ "tableTo": "mattermost",
+ "columnsFrom": [
+ "mattermostId"
+ ],
+ "columnsTo": [
+ "mattermostId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_customId_custom_customId_fk": {
+ "name": "notification_customId_custom_customId_fk",
+ "tableFrom": "notification",
+ "tableTo": "custom",
+ "columnsFrom": [
+ "customId"
+ ],
+ "columnsTo": [
+ "customId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_larkId_lark_larkId_fk": {
+ "name": "notification_larkId_lark_larkId_fk",
+ "tableFrom": "notification",
+ "tableTo": "lark",
+ "columnsFrom": [
+ "larkId"
+ ],
+ "columnsTo": [
+ "larkId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_pushoverId_pushover_pushoverId_fk": {
+ "name": "notification_pushoverId_pushover_pushoverId_fk",
+ "tableFrom": "notification",
+ "tableTo": "pushover",
+ "columnsFrom": [
+ "pushoverId"
+ ],
+ "columnsTo": [
+ "pushoverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_teamsId_teams_teamsId_fk": {
+ "name": "notification_teamsId_teams_teamsId_fk",
+ "tableFrom": "notification",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "teamsId"
+ ],
+ "columnsTo": [
+ "teamsId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_organizationId_organization_id_fk": {
+ "name": "notification_organizationId_organization_id_fk",
+ "tableFrom": "notification",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ntfy": {
+ "name": "ntfy",
+ "schema": "",
+ "columns": {
+ "ntfyId": {
+ "name": "ntfyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverUrl": {
+ "name": "serverUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "topic": {
+ "name": "topic",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "accessToken": {
+ "name": "accessToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 3
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pushover": {
+ "name": "pushover",
+ "schema": "",
+ "columns": {
+ "pushoverId": {
+ "name": "pushoverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userKey": {
+ "name": "userKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "retry": {
+ "name": "retry",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expire": {
+ "name": "expire",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resend": {
+ "name": "resend",
+ "schema": "",
+ "columns": {
+ "resendId": {
+ "name": "resendId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.slack": {
+ "name": "slack",
+ "schema": "",
+ "columns": {
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.teams": {
+ "name": "teams",
+ "schema": "",
+ "columns": {
+ "teamsId": {
+ "name": "teamsId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.telegram": {
+ "name": "telegram",
+ "schema": "",
+ "columns": {
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "botToken": {
+ "name": "botToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "messageThreadId": {
+ "name": "messageThreadId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.patch": {
+ "name": "patch",
+ "schema": "",
+ "columns": {
+ "patchId": {
+ "name": "patchId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "patchType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'update'"
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "patch_applicationId_application_applicationId_fk": {
+ "name": "patch_applicationId_application_applicationId_fk",
+ "tableFrom": "patch",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "patch_composeId_compose_composeId_fk": {
+ "name": "patch_composeId_compose_composeId_fk",
+ "tableFrom": "patch",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "patch_filepath_application_unique": {
+ "name": "patch_filepath_application_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "filePath",
+ "applicationId"
+ ]
+ },
+ "patch_filepath_compose_unique": {
+ "name": "patch_filepath_compose_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "filePath",
+ "composeId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.port": {
+ "name": "port",
+ "schema": "",
+ "columns": {
+ "portId": {
+ "name": "portId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publishedPort": {
+ "name": "publishedPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "publishMode": {
+ "name": "publishMode",
+ "type": "publishModeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'host'"
+ },
+ "targetPort": {
+ "name": "targetPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "protocolType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "port_applicationId_application_applicationId_fk": {
+ "name": "port_applicationId_application_applicationId_fk",
+ "tableFrom": "port",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.postgres": {
+ "name": "postgres",
+ "schema": "",
+ "columns": {
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "postgres_environmentId_environment_environmentId_fk": {
+ "name": "postgres_environmentId_environment_environmentId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "postgres_serverId_server_serverId_fk": {
+ "name": "postgres_serverId_server_serverId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postgres_appName_unique": {
+ "name": "postgres_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.preview_deployments": {
+ "name": "preview_deployments",
+ "schema": "",
+ "columns": {
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestId": {
+ "name": "pullRequestId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestNumber": {
+ "name": "pullRequestNumber",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestURL": {
+ "name": "pullRequestURL",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestTitle": {
+ "name": "pullRequestTitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestCommentId": {
+ "name": "pullRequestCommentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "previewStatus": {
+ "name": "previewStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "preview_deployments_applicationId_application_applicationId_fk": {
+ "name": "preview_deployments_applicationId_application_applicationId_fk",
+ "tableFrom": "preview_deployments",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "preview_deployments_domainId_domain_domainId_fk": {
+ "name": "preview_deployments_domainId_domain_domainId_fk",
+ "tableFrom": "preview_deployments",
+ "tableTo": "domain",
+ "columnsFrom": [
+ "domainId"
+ ],
+ "columnsTo": [
+ "domainId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "preview_deployments_appName_unique": {
+ "name": "preview_deployments_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.project": {
+ "name": "project",
+ "schema": "",
+ "columns": {
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_organizationId_organization_id_fk": {
+ "name": "project_organizationId_organization_id_fk",
+ "tableFrom": "project",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.redirect": {
+ "name": "redirect",
+ "schema": "",
+ "columns": {
+ "redirectId": {
+ "name": "redirectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "regex": {
+ "name": "regex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "replacement": {
+ "name": "replacement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permanent": {
+ "name": "permanent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redirect_applicationId_application_applicationId_fk": {
+ "name": "redirect_applicationId_application_applicationId_fk",
+ "tableFrom": "redirect",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.redis": {
+ "name": "redis",
+ "schema": "",
+ "columns": {
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redis_environmentId_environment_environmentId_fk": {
+ "name": "redis_environmentId_environment_environmentId_fk",
+ "tableFrom": "redis",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "redis_serverId_server_serverId_fk": {
+ "name": "redis_serverId_server_serverId_fk",
+ "tableFrom": "redis",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "redis_appName_unique": {
+ "name": "redis_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.registry": {
+ "name": "registry",
+ "schema": "",
+ "columns": {
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "registryName": {
+ "name": "registryName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "imagePrefix": {
+ "name": "imagePrefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "selfHosted": {
+ "name": "selfHosted",
+ "type": "RegistryType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'cloud'"
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "registry_organizationId_organization_id_fk": {
+ "name": "registry_organizationId_organization_id_fk",
+ "tableFrom": "registry",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rollback": {
+ "name": "rollback",
+ "schema": "",
+ "columns": {
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fullContext": {
+ "name": "fullContext",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "rollback_deploymentId_deployment_deploymentId_fk": {
+ "name": "rollback_deploymentId_deployment_deploymentId_fk",
+ "tableFrom": "rollback",
+ "tableTo": "deployment",
+ "columnsFrom": [
+ "deploymentId"
+ ],
+ "columnsTo": [
+ "deploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.schedule": {
+ "name": "schedule",
+ "schema": "",
+ "columns": {
+ "scheduleId": {
+ "name": "scheduleId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cronExpression": {
+ "name": "cronExpression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shellType": {
+ "name": "shellType",
+ "type": "shellType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'bash'"
+ },
+ "scheduleType": {
+ "name": "scheduleType",
+ "type": "scheduleType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "script": {
+ "name": "script",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "schedule_applicationId_application_applicationId_fk": {
+ "name": "schedule_applicationId_application_applicationId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_composeId_compose_composeId_fk": {
+ "name": "schedule_composeId_compose_composeId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_serverId_server_serverId_fk": {
+ "name": "schedule_serverId_server_serverId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_organizationId_organization_id_fk": {
+ "name": "schedule_organizationId_organization_id_fk",
+ "tableFrom": "schedule",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.security": {
+ "name": "security",
+ "schema": "",
+ "columns": {
+ "securityId": {
+ "name": "securityId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "security_applicationId_application_applicationId_fk": {
+ "name": "security_applicationId_application_applicationId_fk",
+ "tableFrom": "security",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "security_username_applicationId_unique": {
+ "name": "security_username_applicationId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username",
+ "applicationId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.server": {
+ "name": "server",
+ "schema": "",
+ "columns": {
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ipAddress": {
+ "name": "ipAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'root'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "webServerProvider": {
+ "name": "webServerProvider",
+ "type": "webServerProvider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'traefik'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverStatus": {
+ "name": "serverStatus",
+ "type": "serverStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "serverType": {
+ "name": "serverType",
+ "type": "serverType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'deploy'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metricsConfig": {
+ "name": "metricsConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "server_organizationId_organization_id_fk": {
+ "name": "server_organizationId_organization_id_fk",
+ "tableFrom": "server",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "server_sshKeyId_ssh-key_sshKeyId_fk": {
+ "name": "server_sshKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "server",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "sshKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active_organization_id": {
+ "name": "active_organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ssh-key": {
+ "name": "ssh-key",
+ "schema": "",
+ "columns": {
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "publicKey": {
+ "name": "publicKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lastUsedAt": {
+ "name": "lastUsedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh-key_organizationId_organization_id_fk": {
+ "name": "ssh-key_organizationId_organization_id_fk",
+ "tableFrom": "ssh-key",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_provider": {
+ "name": "sso_provider",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "saml_config": {
+ "name": "saml_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_provider_user_id_user_id_fk": {
+ "name": "sso_provider_user_id_user_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "sso_provider_organization_id_organization_id_fk": {
+ "name": "sso_provider_organization_id_organization_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_provider_provider_id_unique": {
+ "name": "sso_provider_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.project_tag": {
+ "name": "project_tag",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_tag_projectId_project_projectId_fk": {
+ "name": "project_tag_projectId_project_projectId_fk",
+ "tableFrom": "project_tag",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "project_tag_tagId_tag_tagId_fk": {
+ "name": "project_tag_tagId_tag_tagId_fk",
+ "tableFrom": "project_tag",
+ "tableTo": "tag",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "tagId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "unique_project_tag": {
+ "name": "unique_project_tag",
+ "nullsNotDistinct": false,
+ "columns": [
+ "projectId",
+ "tagId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tag": {
+ "name": "tag",
+ "schema": "",
+ "columns": {
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "tag_organizationId_organization_id_fk": {
+ "name": "tag_organizationId_organization_id_fk",
+ "tableFrom": "tag",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "unique_org_tag_name": {
+ "name": "unique_org_tag_name",
+ "nullsNotDistinct": false,
+ "columns": [
+ "organizationId",
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "firstName": {
+ "name": "firstName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "lastName": {
+ "name": "lastName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "isRegistered": {
+ "name": "isRegistered",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "expirationDate": {
+ "name": "expirationDate",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "two_factor_enabled": {
+ "name": "two_factor_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'user'"
+ },
+ "enablePaidFeatures": {
+ "name": "enablePaidFeatures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "allowImpersonation": {
+ "name": "allowImpersonation",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enableEnterpriseFeatures": {
+ "name": "enableEnterpriseFeatures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "licenseKey": {
+ "name": "licenseKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isValidEnterpriseLicense": {
+ "name": "isValidEnterpriseLicense",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "stripeCustomerId": {
+ "name": "stripeCustomerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripeSubscriptionId": {
+ "name": "stripeSubscriptionId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serversQuantity": {
+ "name": "serversQuantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "sendInvoiceNotifications": {
+ "name": "sendInvoiceNotifications",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isEnterpriseCloud": {
+ "name": "isEnterpriseCloud",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "trustedOrigins": {
+ "name": "trustedOrigins",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bookmarkedTemplates": {
+ "name": "bookmarkedTemplates",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.volume_backup": {
+ "name": "volume_backup",
+ "schema": "",
+ "columns": {
+ "volumeBackupId": {
+ "name": "volumeBackupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "turnOff": {
+ "name": "turnOff",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cronExpression": {
+ "name": "cronExpression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "keepLatestCount": {
+ "name": "keepLatestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "volume_backup_applicationId_application_applicationId_fk": {
+ "name": "volume_backup_applicationId_application_applicationId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_postgresId_postgres_postgresId_fk": {
+ "name": "volume_backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "volume_backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mongoId_mongo_mongoId_fk": {
+ "name": "volume_backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "volume_backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_redisId_redis_redisId_fk": {
+ "name": "volume_backup_redisId_redis_redisId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_libsqlId_libsql_libsqlId_fk": {
+ "name": "volume_backup_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_composeId_compose_composeId_fk": {
+ "name": "volume_backup_composeId_compose_composeId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_destinationId_destination_destinationId_fk": {
+ "name": "volume_backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webServerSettings": {
+ "name": "webServerSettings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webServerProvider": {
+ "name": "webServerProvider",
+ "type": "webServerProvider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'traefik'"
+ },
+ "serverIp": {
+ "name": "serverIp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "letsEncryptEmail": {
+ "name": "letsEncryptEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sshPrivateKey": {
+ "name": "sshPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "logCleanupCron": {
+ "name": "logCleanupCron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 0 * * *'"
+ },
+ "metricsConfig": {
+ "name": "metricsConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb"
+ },
+ "whitelabelingConfig": {
+ "name": "whitelabelingConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{\"appName\":null,\"appDescription\":null,\"logoUrl\":null,\"faviconUrl\":null,\"customCss\":null,\"loginLogoUrl\":null,\"supportUrl\":null,\"docsUrl\":null,\"errorPageTitle\":null,\"errorPageDescription\":null,\"metaTitle\":null,\"footerText\":null}'::jsonb"
+ },
+ "remoteServersOnly": {
+ "name": "remoteServersOnly",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enforceSSO": {
+ "name": "enforceSSO",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheApplications": {
+ "name": "cleanupCacheApplications",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheOnPreviews": {
+ "name": "cleanupCacheOnPreviews",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheOnCompose": {
+ "name": "cleanupCacheOnCompose",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.buildType": {
+ "name": "buildType",
+ "schema": "public",
+ "values": [
+ "dockerfile",
+ "heroku_buildpacks",
+ "paketo_buildpacks",
+ "nixpacks",
+ "static",
+ "railpack"
+ ]
+ },
+ "public.sourceType": {
+ "name": "sourceType",
+ "schema": "public",
+ "values": [
+ "docker",
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea",
+ "drop"
+ ]
+ },
+ "public.backupType": {
+ "name": "backupType",
+ "schema": "public",
+ "values": [
+ "database",
+ "compose"
+ ]
+ },
+ "public.databaseType": {
+ "name": "databaseType",
+ "schema": "public",
+ "values": [
+ "postgres",
+ "mariadb",
+ "mysql",
+ "mongo",
+ "web-server",
+ "libsql"
+ ]
+ },
+ "public.composeType": {
+ "name": "composeType",
+ "schema": "public",
+ "values": [
+ "docker-compose",
+ "stack"
+ ]
+ },
+ "public.sourceTypeCompose": {
+ "name": "sourceTypeCompose",
+ "schema": "public",
+ "values": [
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea",
+ "raw"
+ ]
+ },
+ "public.deploymentStatus": {
+ "name": "deploymentStatus",
+ "schema": "public",
+ "values": [
+ "running",
+ "done",
+ "error",
+ "cancelled"
+ ]
+ },
+ "public.domainType": {
+ "name": "domainType",
+ "schema": "public",
+ "values": [
+ "compose",
+ "application",
+ "preview"
+ ]
+ },
+ "public.gitProviderType": {
+ "name": "gitProviderType",
+ "schema": "public",
+ "values": [
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea"
+ ]
+ },
+ "public.mountType": {
+ "name": "mountType",
+ "schema": "public",
+ "values": [
+ "bind",
+ "volume",
+ "file"
+ ]
+ },
+ "public.serviceType": {
+ "name": "serviceType",
+ "schema": "public",
+ "values": [
+ "application",
+ "postgres",
+ "mysql",
+ "mariadb",
+ "mongo",
+ "redis",
+ "compose",
+ "libsql"
+ ]
+ },
+ "public.notificationType": {
+ "name": "notificationType",
+ "schema": "public",
+ "values": [
+ "slack",
+ "telegram",
+ "discord",
+ "email",
+ "resend",
+ "gotify",
+ "ntfy",
+ "mattermost",
+ "pushover",
+ "custom",
+ "lark",
+ "teams"
+ ]
+ },
+ "public.patchType": {
+ "name": "patchType",
+ "schema": "public",
+ "values": [
+ "create",
+ "update",
+ "delete"
+ ]
+ },
+ "public.protocolType": {
+ "name": "protocolType",
+ "schema": "public",
+ "values": [
+ "tcp",
+ "udp"
+ ]
+ },
+ "public.publishModeType": {
+ "name": "publishModeType",
+ "schema": "public",
+ "values": [
+ "ingress",
+ "host"
+ ]
+ },
+ "public.RegistryType": {
+ "name": "RegistryType",
+ "schema": "public",
+ "values": [
+ "selfHosted",
+ "cloud"
+ ]
+ },
+ "public.scheduleType": {
+ "name": "scheduleType",
+ "schema": "public",
+ "values": [
+ "application",
+ "compose",
+ "server",
+ "dokploy-server"
+ ]
+ },
+ "public.shellType": {
+ "name": "shellType",
+ "schema": "public",
+ "values": [
+ "bash",
+ "sh"
+ ]
+ },
+ "public.serverStatus": {
+ "name": "serverStatus",
+ "schema": "public",
+ "values": [
+ "active",
+ "inactive"
+ ]
+ },
+ "public.serverType": {
+ "name": "serverType",
+ "schema": "public",
+ "values": [
+ "deploy",
+ "build"
+ ]
+ },
+ "public.applicationStatus": {
+ "name": "applicationStatus",
+ "schema": "public",
+ "values": [
+ "idle",
+ "running",
+ "done",
+ "error"
+ ]
+ },
+ "public.certificateType": {
+ "name": "certificateType",
+ "schema": "public",
+ "values": [
+ "letsencrypt",
+ "none",
+ "custom"
+ ]
+ },
+ "public.sqldNode": {
+ "name": "sqldNode",
+ "schema": "public",
+ "values": [
+ "primary",
+ "replica"
+ ]
+ },
+ "public.triggerType": {
+ "name": "triggerType",
+ "schema": "public",
+ "values": [
+ "push",
+ "tag"
+ ]
+ },
+ "public.webServerProvider": {
+ "name": "webServerProvider",
+ "schema": "public",
+ "values": [
+ "traefik",
+ "caddy"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/0171_snapshot.json b/apps/dokploy/drizzle/meta/0171_snapshot.json
new file mode 100644
index 0000000000..788382429e
--- /dev/null
+++ b/apps/dokploy/drizzle/meta/0171_snapshot.json
@@ -0,0 +1,8370 @@
+{
+ "id": "99b3eb38-5993-4945-a6ae-46e791e23c57",
+ "prevId": "93bfd2e4-aeef-4158-9b26-97da9f79b99b",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is2FAEnabled": {
+ "name": "is2FAEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resetPasswordToken": {
+ "name": "resetPasswordToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resetPasswordExpiresAt": {
+ "name": "resetPasswordExpiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmationToken": {
+ "name": "confirmationToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmationExpiresAt": {
+ "name": "confirmationExpiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.apikey": {
+ "name": "apikey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start": {
+ "name": "start",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config_id": {
+ "name": "config_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'default'"
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refill_interval": {
+ "name": "refill_interval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refill_amount": {
+ "name": "refill_amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_refill_at": {
+ "name": "last_refill_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_enabled": {
+ "name": "rate_limit_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_time_window": {
+ "name": "rate_limit_time_window",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_max": {
+ "name": "rate_limit_max",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "request_count": {
+ "name": "request_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "remaining": {
+ "name": "remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "apikey_reference_id_user_id_fk": {
+ "name": "apikey_reference_id_user_id_fk",
+ "tableFrom": "apikey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "reference_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation": {
+ "name": "invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invitation_organization_id_organization_id_fk": {
+ "name": "invitation_organization_id_organization_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_inviter_id_user_id_fk": {
+ "name": "invitation_inviter_id_user_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "user",
+ "columnsFrom": [
+ "inviter_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.member": {
+ "name": "member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateProjects": {
+ "name": "canCreateProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToSSHKeys": {
+ "name": "canAccessToSSHKeys",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateServices": {
+ "name": "canCreateServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteProjects": {
+ "name": "canDeleteProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteServices": {
+ "name": "canDeleteServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToDocker": {
+ "name": "canAccessToDocker",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToAPI": {
+ "name": "canAccessToAPI",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToGitProviders": {
+ "name": "canAccessToGitProviders",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToTraefikFiles": {
+ "name": "canAccessToTraefikFiles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteEnvironments": {
+ "name": "canDeleteEnvironments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateEnvironments": {
+ "name": "canCreateEnvironments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accesedProjects": {
+ "name": "accesedProjects",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedEnvironments": {
+ "name": "accessedEnvironments",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accesedServices": {
+ "name": "accesedServices",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedGitProviders": {
+ "name": "accessedGitProviders",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedServers": {
+ "name": "accessedServers",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "member_organization_id_organization_id_fk": {
+ "name": "member_organization_id_organization_id_fk",
+ "tableFrom": "member",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "member_user_id_user_id_fk": {
+ "name": "member_user_id_user_id_fk",
+ "tableFrom": "member",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "organization_owner_id_user_id_fk": {
+ "name": "organization_owner_id_user_id_fk",
+ "tableFrom": "organization",
+ "tableTo": "user",
+ "columnsFrom": [
+ "owner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "organization_slug_unique": {
+ "name": "organization_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization_role": {
+ "name": "organization_role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "organizationRole_organizationId_idx": {
+ "name": "organizationRole_organizationId_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organizationRole_role_idx": {
+ "name": "organizationRole_role_idx",
+ "columns": [
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "organization_role_organization_id_organization_id_fk": {
+ "name": "organization_role_organization_id_organization_id_fk",
+ "tableFrom": "organization_role",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.two_factor": {
+ "name": "two_factor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backup_codes": {
+ "name": "backup_codes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "two_factor_user_id_user_id_fk": {
+ "name": "two_factor_user_id_user_id_fk",
+ "tableFrom": "two_factor",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ai": {
+ "name": "ai",
+ "schema": "",
+ "columns": {
+ "aiId": {
+ "name": "aiId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiUrl": {
+ "name": "apiUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isEnabled": {
+ "name": "isEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ai_organizationId_organization_id_fk": {
+ "name": "ai_organizationId_organization_id_fk",
+ "tableFrom": "ai",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.application": {
+ "name": "application",
+ "schema": "",
+ "columns": {
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewEnv": {
+ "name": "previewEnv",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "watchPaths": {
+ "name": "watchPaths",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewBuildArgs": {
+ "name": "previewBuildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewBuildSecrets": {
+ "name": "previewBuildSecrets",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewLabels": {
+ "name": "previewLabels",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewWildcard": {
+ "name": "previewWildcard",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewPort": {
+ "name": "previewPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "previewHttps": {
+ "name": "previewHttps",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "previewPath": {
+ "name": "previewPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "previewCustomCertResolver": {
+ "name": "previewCustomCertResolver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewLimit": {
+ "name": "previewLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3
+ },
+ "isPreviewDeploymentsActive": {
+ "name": "isPreviewDeploymentsActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "previewRequireCollaboratorPermissions": {
+ "name": "previewRequireCollaboratorPermissions",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "rollbackActive": {
+ "name": "rollbackActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "buildArgs": {
+ "name": "buildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildSecrets": {
+ "name": "buildSecrets",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subtitle": {
+ "name": "subtitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "cleanCache": {
+ "name": "cleanCache",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildPath": {
+ "name": "buildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "triggerType": {
+ "name": "triggerType",
+ "type": "triggerType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'push'"
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBuildPath": {
+ "name": "gitlabBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaRepository": {
+ "name": "giteaRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaOwner": {
+ "name": "giteaOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBranch": {
+ "name": "giteaBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBuildPath": {
+ "name": "giteaBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepositorySlug": {
+ "name": "bitbucketRepositorySlug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBuildPath": {
+ "name": "bitbucketBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBuildPath": {
+ "name": "customGitBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableSubmodules": {
+ "name": "enableSubmodules",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerfile": {
+ "name": "dockerfile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'Dockerfile'"
+ },
+ "dockerContextPath": {
+ "name": "dockerContextPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerBuildStage": {
+ "name": "dockerBuildStage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dropBuildPath": {
+ "name": "dropBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "buildType": {
+ "name": "buildType",
+ "type": "buildType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'nixpacks'"
+ },
+ "railpackVersion": {
+ "name": "railpackVersion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0.15.4'"
+ },
+ "herokuVersion": {
+ "name": "herokuVersion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'24'"
+ },
+ "publishDirectory": {
+ "name": "publishDirectory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isStaticSpa": {
+ "name": "isStaticSpa",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createEnvFile": {
+ "name": "createEnvFile",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackRegistryId": {
+ "name": "rollbackRegistryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildServerId": {
+ "name": "buildServerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildRegistryId": {
+ "name": "buildRegistryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "application",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_registryId_registry_registryId_fk": {
+ "name": "application_registryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "registryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_rollbackRegistryId_registry_registryId_fk": {
+ "name": "application_rollbackRegistryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "rollbackRegistryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_environmentId_environment_environmentId_fk": {
+ "name": "application_environmentId_environment_environmentId_fk",
+ "tableFrom": "application",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_githubId_github_githubId_fk": {
+ "name": "application_githubId_github_githubId_fk",
+ "tableFrom": "application",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_gitlabId_gitlab_gitlabId_fk": {
+ "name": "application_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_giteaId_gitea_giteaId_fk": {
+ "name": "application_giteaId_gitea_giteaId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitea",
+ "columnsFrom": [
+ "giteaId"
+ ],
+ "columnsTo": [
+ "giteaId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "application_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "application",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_serverId_server_serverId_fk": {
+ "name": "application_serverId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_buildServerId_server_serverId_fk": {
+ "name": "application_buildServerId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "buildServerId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_buildRegistryId_registry_registryId_fk": {
+ "name": "application_buildRegistryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "buildRegistryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "application_appName_unique": {
+ "name": "application_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_log": {
+ "name": "audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_email": {
+ "name": "user_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_role": {
+ "name": "user_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resource_name": {
+ "name": "resource_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "auditLog_organizationId_idx": {
+ "name": "auditLog_organizationId_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "auditLog_userId_idx": {
+ "name": "auditLog_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "auditLog_createdAt_idx": {
+ "name": "auditLog_createdAt_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_log_organization_id_organization_id_fk": {
+ "name": "audit_log_organization_id_organization_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_log_user_id_user_id_fk": {
+ "name": "audit_log_user_id_user_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.backup": {
+ "name": "backup",
+ "schema": "",
+ "columns": {
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule": {
+ "name": "schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "database": {
+ "name": "database",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "keepLatestCount": {
+ "name": "keepLatestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backupType": {
+ "name": "backupType",
+ "type": "backupType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'database'"
+ },
+ "databaseType": {
+ "name": "databaseType",
+ "type": "databaseType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "backup_destinationId_destination_destinationId_fk": {
+ "name": "backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_composeId_compose_composeId_fk": {
+ "name": "backup_composeId_compose_composeId_fk",
+ "tableFrom": "backup",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_postgresId_postgres_postgresId_fk": {
+ "name": "backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mongoId_mongo_mongoId_fk": {
+ "name": "backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_libsqlId_libsql_libsqlId_fk": {
+ "name": "backup_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_userId_user_id_fk": {
+ "name": "backup_userId_user_id_fk",
+ "tableFrom": "backup",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "backup_appName_unique": {
+ "name": "backup_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bitbucket": {
+ "name": "bitbucket",
+ "schema": "",
+ "columns": {
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bitbucketUsername": {
+ "name": "bitbucketUsername",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketEmail": {
+ "name": "bitbucketEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "appPassword": {
+ "name": "appPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketWorkspaceName": {
+ "name": "bitbucketWorkspaceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bitbucket_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "bitbucket",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.certificate": {
+ "name": "certificate",
+ "schema": "",
+ "columns": {
+ "certificateId": {
+ "name": "certificateId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificateData": {
+ "name": "certificateData",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificatePath": {
+ "name": "certificatePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "autoRenew": {
+ "name": "autoRenew",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "certificate_organizationId_organization_id_fk": {
+ "name": "certificate_organizationId_organization_id_fk",
+ "tableFrom": "certificate",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "certificate_serverId_server_serverId_fk": {
+ "name": "certificate_serverId_server_serverId_fk",
+ "tableFrom": "certificate",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "certificate_certificatePath_unique": {
+ "name": "certificate_certificatePath_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "certificatePath"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.compose": {
+ "name": "compose",
+ "schema": "",
+ "columns": {
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeFile": {
+ "name": "composeFile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceTypeCompose",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "composeType": {
+ "name": "composeType",
+ "type": "composeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'docker-compose'"
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepositorySlug": {
+ "name": "bitbucketRepositorySlug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaRepository": {
+ "name": "giteaRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaOwner": {
+ "name": "giteaOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBranch": {
+ "name": "giteaBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enableSubmodules": {
+ "name": "enableSubmodules",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "composePath": {
+ "name": "composePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'./docker-compose.yml'"
+ },
+ "suffix": {
+ "name": "suffix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "randomize": {
+ "name": "randomize",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isolatedDeployment": {
+ "name": "isolatedDeployment",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isolatedDeploymentsVolume": {
+ "name": "isolatedDeploymentsVolume",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "triggerType": {
+ "name": "triggerType",
+ "type": "triggerType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'push'"
+ },
+ "composeStatus": {
+ "name": "composeStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "watchPaths": {
+ "name": "watchPaths",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "compose",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_environmentId_environment_environmentId_fk": {
+ "name": "compose_environmentId_environment_environmentId_fk",
+ "tableFrom": "compose",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "compose_githubId_github_githubId_fk": {
+ "name": "compose_githubId_github_githubId_fk",
+ "tableFrom": "compose",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_gitlabId_gitlab_gitlabId_fk": {
+ "name": "compose_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "compose_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "compose",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_giteaId_gitea_giteaId_fk": {
+ "name": "compose_giteaId_gitea_giteaId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitea",
+ "columnsFrom": [
+ "giteaId"
+ ],
+ "columnsTo": [
+ "giteaId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_serverId_server_serverId_fk": {
+ "name": "compose_serverId_server_serverId_fk",
+ "tableFrom": "compose",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment": {
+ "name": "deployment",
+ "schema": "",
+ "columns": {
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "deploymentStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'running'"
+ },
+ "logPath": {
+ "name": "logPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pid": {
+ "name": "pid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isPreviewDeployment": {
+ "name": "isPreviewDeployment",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "startedAt": {
+ "name": "startedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finishedAt": {
+ "name": "finishedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scheduleId": {
+ "name": "scheduleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeBackupId": {
+ "name": "volumeBackupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildServerId": {
+ "name": "buildServerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "deployment_applicationId_application_applicationId_fk": {
+ "name": "deployment_applicationId_application_applicationId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_composeId_compose_composeId_fk": {
+ "name": "deployment_composeId_compose_composeId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_serverId_server_serverId_fk": {
+ "name": "deployment_serverId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": {
+ "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "preview_deployments",
+ "columnsFrom": [
+ "previewDeploymentId"
+ ],
+ "columnsTo": [
+ "previewDeploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_scheduleId_schedule_scheduleId_fk": {
+ "name": "deployment_scheduleId_schedule_scheduleId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "schedule",
+ "columnsFrom": [
+ "scheduleId"
+ ],
+ "columnsTo": [
+ "scheduleId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_backupId_backup_backupId_fk": {
+ "name": "deployment_backupId_backup_backupId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "backup",
+ "columnsFrom": [
+ "backupId"
+ ],
+ "columnsTo": [
+ "backupId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_rollbackId_rollback_rollbackId_fk": {
+ "name": "deployment_rollbackId_rollback_rollbackId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "rollback",
+ "columnsFrom": [
+ "rollbackId"
+ ],
+ "columnsTo": [
+ "rollbackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_volumeBackupId_volume_backup_volumeBackupId_fk": {
+ "name": "deployment_volumeBackupId_volume_backup_volumeBackupId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "volume_backup",
+ "columnsFrom": [
+ "volumeBackupId"
+ ],
+ "columnsTo": [
+ "volumeBackupId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_buildServerId_server_serverId_fk": {
+ "name": "deployment_buildServerId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "buildServerId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.destination": {
+ "name": "destination",
+ "schema": "",
+ "columns": {
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accessKey": {
+ "name": "accessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secretAccessKey": {
+ "name": "secretAccessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bucket": {
+ "name": "bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "region": {
+ "name": "region",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "additionalFlags": {
+ "name": "additionalFlags",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "destination_organizationId_organization_id_fk": {
+ "name": "destination_organizationId_organization_id_fk",
+ "tableFrom": "destination",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.domain": {
+ "name": "domain",
+ "schema": "",
+ "columns": {
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "customEntrypoint": {
+ "name": "customEntrypoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domainType": {
+ "name": "domainType",
+ "type": "domainType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'application'"
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customCertResolver": {
+ "name": "customCertResolver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "internalPath": {
+ "name": "internalPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "stripPath": {
+ "name": "stripPath",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "middlewares": {
+ "name": "middlewares",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "domain_composeId_compose_composeId_fk": {
+ "name": "domain_composeId_compose_composeId_fk",
+ "tableFrom": "domain",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_applicationId_application_applicationId_fk": {
+ "name": "domain_applicationId_application_applicationId_fk",
+ "tableFrom": "domain",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": {
+ "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk",
+ "tableFrom": "domain",
+ "tableTo": "preview_deployments",
+ "columnsFrom": [
+ "previewDeploymentId"
+ ],
+ "columnsTo": [
+ "previewDeploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment": {
+ "name": "environment",
+ "schema": "",
+ "columns": {
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isDefault": {
+ "name": "isDefault",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_projectId_project_projectId_fk": {
+ "name": "environment_projectId_project_projectId_fk",
+ "tableFrom": "environment",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_provider": {
+ "name": "git_provider",
+ "schema": "",
+ "columns": {
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerType": {
+ "name": "providerType",
+ "type": "gitProviderType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sharedWithOrganization": {
+ "name": "sharedWithOrganization",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_provider_organizationId_organization_id_fk": {
+ "name": "git_provider_organizationId_organization_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_provider_userId_user_id_fk": {
+ "name": "git_provider_userId_user_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gitea": {
+ "name": "gitea",
+ "schema": "",
+ "columns": {
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "giteaUrl": {
+ "name": "giteaUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'https://gitea.com'"
+ },
+ "giteaInternalUrl": {
+ "name": "giteaInternalUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'repo,repo:status,read:user,read:org'"
+ },
+ "last_authenticated_at": {
+ "name": "last_authenticated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitea_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitea",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.github": {
+ "name": "github",
+ "schema": "",
+ "columns": {
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "githubAppName": {
+ "name": "githubAppName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubAppId": {
+ "name": "githubAppId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientId": {
+ "name": "githubClientId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientSecret": {
+ "name": "githubClientSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubInstallationId": {
+ "name": "githubInstallationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubPrivateKey": {
+ "name": "githubPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubWebhookSecret": {
+ "name": "githubWebhookSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "github_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "github_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "github",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gitlab": {
+ "name": "gitlab",
+ "schema": "",
+ "columns": {
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "gitlabUrl": {
+ "name": "gitlabUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'https://gitlab.com'"
+ },
+ "gitlabInternalUrl": {
+ "name": "gitlabInternalUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitlab_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitlab",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.libsql": {
+ "name": "libsql",
+ "schema": "",
+ "columns": {
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sqldNode": {
+ "name": "sqldNode",
+ "type": "sqldNode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'primary'"
+ },
+ "sqldPrimaryUrl": {
+ "name": "sqldPrimaryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableNamespaces": {
+ "name": "enableNamespaces",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalGRPCPort": {
+ "name": "externalGRPCPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalAdminPort": {
+ "name": "externalAdminPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "libsql_environmentId_environment_environmentId_fk": {
+ "name": "libsql_environmentId_environment_environmentId_fk",
+ "tableFrom": "libsql",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "libsql_serverId_server_serverId_fk": {
+ "name": "libsql_serverId_server_serverId_fk",
+ "tableFrom": "libsql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "libsql_appName_unique": {
+ "name": "libsql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mariadb": {
+ "name": "mariadb",
+ "schema": "",
+ "columns": {
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mariadb_environmentId_environment_environmentId_fk": {
+ "name": "mariadb_environmentId_environment_environmentId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mariadb_serverId_server_serverId_fk": {
+ "name": "mariadb_serverId_server_serverId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mariadb_appName_unique": {
+ "name": "mariadb_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mongo": {
+ "name": "mongo",
+ "schema": "",
+ "columns": {
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'mongo:8'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicaSets": {
+ "name": "replicaSets",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mongo_environmentId_environment_environmentId_fk": {
+ "name": "mongo_environmentId_environment_environmentId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mongo_serverId_server_serverId_fk": {
+ "name": "mongo_serverId_server_serverId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mongo_appName_unique": {
+ "name": "mongo_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mount": {
+ "name": "mount",
+ "schema": "",
+ "columns": {
+ "mountId": {
+ "name": "mountId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "mountType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hostPath": {
+ "name": "hostPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "mountPath": {
+ "name": "mountPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mount_applicationId_application_applicationId_fk": {
+ "name": "mount_applicationId_application_applicationId_fk",
+ "tableFrom": "mount",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_composeId_compose_composeId_fk": {
+ "name": "mount_composeId_compose_composeId_fk",
+ "tableFrom": "mount",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_libsqlId_libsql_libsqlId_fk": {
+ "name": "mount_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mariadbId_mariadb_mariadbId_fk": {
+ "name": "mount_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mongoId_mongo_mongoId_fk": {
+ "name": "mount_mongoId_mongo_mongoId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mysqlId_mysql_mysqlId_fk": {
+ "name": "mount_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_postgresId_postgres_postgresId_fk": {
+ "name": "mount_postgresId_postgres_postgresId_fk",
+ "tableFrom": "mount",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_redisId_redis_redisId_fk": {
+ "name": "mount_redisId_redis_redisId_fk",
+ "tableFrom": "mount",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mysql": {
+ "name": "mysql",
+ "schema": "",
+ "columns": {
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mysql_environmentId_environment_environmentId_fk": {
+ "name": "mysql_environmentId_environment_environmentId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mysql_serverId_server_serverId_fk": {
+ "name": "mysql_serverId_server_serverId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mysql_appName_unique": {
+ "name": "mysql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.custom": {
+ "name": "custom",
+ "schema": "",
+ "columns": {
+ "customId": {
+ "name": "customId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "headers": {
+ "name": "headers",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.discord": {
+ "name": "discord",
+ "schema": "",
+ "columns": {
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decoration": {
+ "name": "decoration",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.email": {
+ "name": "email",
+ "schema": "",
+ "columns": {
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "smtpServer": {
+ "name": "smtpServer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "smtpPort": {
+ "name": "smtpPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gotify": {
+ "name": "gotify",
+ "schema": "",
+ "columns": {
+ "gotifyId": {
+ "name": "gotifyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverUrl": {
+ "name": "serverUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appToken": {
+ "name": "appToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 5
+ },
+ "decoration": {
+ "name": "decoration",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lark": {
+ "name": "lark",
+ "schema": "",
+ "columns": {
+ "larkId": {
+ "name": "larkId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mattermost": {
+ "name": "mattermost",
+ "schema": "",
+ "columns": {
+ "mattermostId": {
+ "name": "mattermostId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification": {
+ "name": "notification",
+ "schema": "",
+ "columns": {
+ "notificationId": {
+ "name": "notificationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appDeploy": {
+ "name": "appDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "appBuildError": {
+ "name": "appBuildError",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "databaseBackup": {
+ "name": "databaseBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "volumeBackup": {
+ "name": "volumeBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployRestart": {
+ "name": "dokployRestart",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployBackup": {
+ "name": "dokployBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerCleanup": {
+ "name": "dockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "serverThreshold": {
+ "name": "serverThreshold",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "notificationType": {
+ "name": "notificationType",
+ "type": "notificationType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resendId": {
+ "name": "resendId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gotifyId": {
+ "name": "gotifyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ntfyId": {
+ "name": "ntfyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mattermostId": {
+ "name": "mattermostId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customId": {
+ "name": "customId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "larkId": {
+ "name": "larkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pushoverId": {
+ "name": "pushoverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "teamsId": {
+ "name": "teamsId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_slackId_slack_slackId_fk": {
+ "name": "notification_slackId_slack_slackId_fk",
+ "tableFrom": "notification",
+ "tableTo": "slack",
+ "columnsFrom": [
+ "slackId"
+ ],
+ "columnsTo": [
+ "slackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_telegramId_telegram_telegramId_fk": {
+ "name": "notification_telegramId_telegram_telegramId_fk",
+ "tableFrom": "notification",
+ "tableTo": "telegram",
+ "columnsFrom": [
+ "telegramId"
+ ],
+ "columnsTo": [
+ "telegramId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_discordId_discord_discordId_fk": {
+ "name": "notification_discordId_discord_discordId_fk",
+ "tableFrom": "notification",
+ "tableTo": "discord",
+ "columnsFrom": [
+ "discordId"
+ ],
+ "columnsTo": [
+ "discordId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_emailId_email_emailId_fk": {
+ "name": "notification_emailId_email_emailId_fk",
+ "tableFrom": "notification",
+ "tableTo": "email",
+ "columnsFrom": [
+ "emailId"
+ ],
+ "columnsTo": [
+ "emailId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_resendId_resend_resendId_fk": {
+ "name": "notification_resendId_resend_resendId_fk",
+ "tableFrom": "notification",
+ "tableTo": "resend",
+ "columnsFrom": [
+ "resendId"
+ ],
+ "columnsTo": [
+ "resendId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_gotifyId_gotify_gotifyId_fk": {
+ "name": "notification_gotifyId_gotify_gotifyId_fk",
+ "tableFrom": "notification",
+ "tableTo": "gotify",
+ "columnsFrom": [
+ "gotifyId"
+ ],
+ "columnsTo": [
+ "gotifyId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_ntfyId_ntfy_ntfyId_fk": {
+ "name": "notification_ntfyId_ntfy_ntfyId_fk",
+ "tableFrom": "notification",
+ "tableTo": "ntfy",
+ "columnsFrom": [
+ "ntfyId"
+ ],
+ "columnsTo": [
+ "ntfyId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_mattermostId_mattermost_mattermostId_fk": {
+ "name": "notification_mattermostId_mattermost_mattermostId_fk",
+ "tableFrom": "notification",
+ "tableTo": "mattermost",
+ "columnsFrom": [
+ "mattermostId"
+ ],
+ "columnsTo": [
+ "mattermostId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_customId_custom_customId_fk": {
+ "name": "notification_customId_custom_customId_fk",
+ "tableFrom": "notification",
+ "tableTo": "custom",
+ "columnsFrom": [
+ "customId"
+ ],
+ "columnsTo": [
+ "customId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_larkId_lark_larkId_fk": {
+ "name": "notification_larkId_lark_larkId_fk",
+ "tableFrom": "notification",
+ "tableTo": "lark",
+ "columnsFrom": [
+ "larkId"
+ ],
+ "columnsTo": [
+ "larkId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_pushoverId_pushover_pushoverId_fk": {
+ "name": "notification_pushoverId_pushover_pushoverId_fk",
+ "tableFrom": "notification",
+ "tableTo": "pushover",
+ "columnsFrom": [
+ "pushoverId"
+ ],
+ "columnsTo": [
+ "pushoverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_teamsId_teams_teamsId_fk": {
+ "name": "notification_teamsId_teams_teamsId_fk",
+ "tableFrom": "notification",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "teamsId"
+ ],
+ "columnsTo": [
+ "teamsId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_organizationId_organization_id_fk": {
+ "name": "notification_organizationId_organization_id_fk",
+ "tableFrom": "notification",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ntfy": {
+ "name": "ntfy",
+ "schema": "",
+ "columns": {
+ "ntfyId": {
+ "name": "ntfyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverUrl": {
+ "name": "serverUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "topic": {
+ "name": "topic",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "accessToken": {
+ "name": "accessToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 3
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pushover": {
+ "name": "pushover",
+ "schema": "",
+ "columns": {
+ "pushoverId": {
+ "name": "pushoverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userKey": {
+ "name": "userKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "retry": {
+ "name": "retry",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expire": {
+ "name": "expire",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resend": {
+ "name": "resend",
+ "schema": "",
+ "columns": {
+ "resendId": {
+ "name": "resendId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.slack": {
+ "name": "slack",
+ "schema": "",
+ "columns": {
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.teams": {
+ "name": "teams",
+ "schema": "",
+ "columns": {
+ "teamsId": {
+ "name": "teamsId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.telegram": {
+ "name": "telegram",
+ "schema": "",
+ "columns": {
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "botToken": {
+ "name": "botToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "messageThreadId": {
+ "name": "messageThreadId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.patch": {
+ "name": "patch",
+ "schema": "",
+ "columns": {
+ "patchId": {
+ "name": "patchId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "patchType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'update'"
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "patch_applicationId_application_applicationId_fk": {
+ "name": "patch_applicationId_application_applicationId_fk",
+ "tableFrom": "patch",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "patch_composeId_compose_composeId_fk": {
+ "name": "patch_composeId_compose_composeId_fk",
+ "tableFrom": "patch",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "patch_filepath_application_unique": {
+ "name": "patch_filepath_application_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "filePath",
+ "applicationId"
+ ]
+ },
+ "patch_filepath_compose_unique": {
+ "name": "patch_filepath_compose_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "filePath",
+ "composeId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.port": {
+ "name": "port",
+ "schema": "",
+ "columns": {
+ "portId": {
+ "name": "portId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publishedPort": {
+ "name": "publishedPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "publishMode": {
+ "name": "publishMode",
+ "type": "publishModeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'host'"
+ },
+ "targetPort": {
+ "name": "targetPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "protocolType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "port_applicationId_application_applicationId_fk": {
+ "name": "port_applicationId_application_applicationId_fk",
+ "tableFrom": "port",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.postgres": {
+ "name": "postgres",
+ "schema": "",
+ "columns": {
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "postgres_environmentId_environment_environmentId_fk": {
+ "name": "postgres_environmentId_environment_environmentId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "postgres_serverId_server_serverId_fk": {
+ "name": "postgres_serverId_server_serverId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postgres_appName_unique": {
+ "name": "postgres_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.preview_deployments": {
+ "name": "preview_deployments",
+ "schema": "",
+ "columns": {
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestId": {
+ "name": "pullRequestId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestNumber": {
+ "name": "pullRequestNumber",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestURL": {
+ "name": "pullRequestURL",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestTitle": {
+ "name": "pullRequestTitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestCommentId": {
+ "name": "pullRequestCommentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "previewStatus": {
+ "name": "previewStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "preview_deployments_applicationId_application_applicationId_fk": {
+ "name": "preview_deployments_applicationId_application_applicationId_fk",
+ "tableFrom": "preview_deployments",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "preview_deployments_domainId_domain_domainId_fk": {
+ "name": "preview_deployments_domainId_domain_domainId_fk",
+ "tableFrom": "preview_deployments",
+ "tableTo": "domain",
+ "columnsFrom": [
+ "domainId"
+ ],
+ "columnsTo": [
+ "domainId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "preview_deployments_appName_unique": {
+ "name": "preview_deployments_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.project": {
+ "name": "project",
+ "schema": "",
+ "columns": {
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_organizationId_organization_id_fk": {
+ "name": "project_organizationId_organization_id_fk",
+ "tableFrom": "project",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.redirect": {
+ "name": "redirect",
+ "schema": "",
+ "columns": {
+ "redirectId": {
+ "name": "redirectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "regex": {
+ "name": "regex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "replacement": {
+ "name": "replacement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permanent": {
+ "name": "permanent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redirect_applicationId_application_applicationId_fk": {
+ "name": "redirect_applicationId_application_applicationId_fk",
+ "tableFrom": "redirect",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.redis": {
+ "name": "redis",
+ "schema": "",
+ "columns": {
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redis_environmentId_environment_environmentId_fk": {
+ "name": "redis_environmentId_environment_environmentId_fk",
+ "tableFrom": "redis",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "redis_serverId_server_serverId_fk": {
+ "name": "redis_serverId_server_serverId_fk",
+ "tableFrom": "redis",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "redis_appName_unique": {
+ "name": "redis_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.registry": {
+ "name": "registry",
+ "schema": "",
+ "columns": {
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "registryName": {
+ "name": "registryName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "imagePrefix": {
+ "name": "imagePrefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "selfHosted": {
+ "name": "selfHosted",
+ "type": "RegistryType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'cloud'"
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "registry_organizationId_organization_id_fk": {
+ "name": "registry_organizationId_organization_id_fk",
+ "tableFrom": "registry",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rollback": {
+ "name": "rollback",
+ "schema": "",
+ "columns": {
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fullContext": {
+ "name": "fullContext",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "rollback_deploymentId_deployment_deploymentId_fk": {
+ "name": "rollback_deploymentId_deployment_deploymentId_fk",
+ "tableFrom": "rollback",
+ "tableTo": "deployment",
+ "columnsFrom": [
+ "deploymentId"
+ ],
+ "columnsTo": [
+ "deploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.schedule": {
+ "name": "schedule",
+ "schema": "",
+ "columns": {
+ "scheduleId": {
+ "name": "scheduleId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cronExpression": {
+ "name": "cronExpression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shellType": {
+ "name": "shellType",
+ "type": "shellType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'bash'"
+ },
+ "scheduleType": {
+ "name": "scheduleType",
+ "type": "scheduleType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "script": {
+ "name": "script",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "schedule_applicationId_application_applicationId_fk": {
+ "name": "schedule_applicationId_application_applicationId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_composeId_compose_composeId_fk": {
+ "name": "schedule_composeId_compose_composeId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_serverId_server_serverId_fk": {
+ "name": "schedule_serverId_server_serverId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_organizationId_organization_id_fk": {
+ "name": "schedule_organizationId_organization_id_fk",
+ "tableFrom": "schedule",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.security": {
+ "name": "security",
+ "schema": "",
+ "columns": {
+ "securityId": {
+ "name": "securityId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "security_applicationId_application_applicationId_fk": {
+ "name": "security_applicationId_application_applicationId_fk",
+ "tableFrom": "security",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "security_username_applicationId_unique": {
+ "name": "security_username_applicationId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username",
+ "applicationId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.server": {
+ "name": "server",
+ "schema": "",
+ "columns": {
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ipAddress": {
+ "name": "ipAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'root'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "webServerProvider": {
+ "name": "webServerProvider",
+ "type": "webServerProvider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'traefik'"
+ },
+ "caddyTrustedProxyConfig": {
+ "name": "caddyTrustedProxyConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'null'::jsonb"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverStatus": {
+ "name": "serverStatus",
+ "type": "serverStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "serverType": {
+ "name": "serverType",
+ "type": "serverType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'deploy'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metricsConfig": {
+ "name": "metricsConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "server_organizationId_organization_id_fk": {
+ "name": "server_organizationId_organization_id_fk",
+ "tableFrom": "server",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "server_sshKeyId_ssh-key_sshKeyId_fk": {
+ "name": "server_sshKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "server",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "sshKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active_organization_id": {
+ "name": "active_organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ssh-key": {
+ "name": "ssh-key",
+ "schema": "",
+ "columns": {
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "publicKey": {
+ "name": "publicKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lastUsedAt": {
+ "name": "lastUsedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh-key_organizationId_organization_id_fk": {
+ "name": "ssh-key_organizationId_organization_id_fk",
+ "tableFrom": "ssh-key",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_provider": {
+ "name": "sso_provider",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "saml_config": {
+ "name": "saml_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_provider_user_id_user_id_fk": {
+ "name": "sso_provider_user_id_user_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "sso_provider_organization_id_organization_id_fk": {
+ "name": "sso_provider_organization_id_organization_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_provider_provider_id_unique": {
+ "name": "sso_provider_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.project_tag": {
+ "name": "project_tag",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_tag_projectId_project_projectId_fk": {
+ "name": "project_tag_projectId_project_projectId_fk",
+ "tableFrom": "project_tag",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "project_tag_tagId_tag_tagId_fk": {
+ "name": "project_tag_tagId_tag_tagId_fk",
+ "tableFrom": "project_tag",
+ "tableTo": "tag",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "tagId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "unique_project_tag": {
+ "name": "unique_project_tag",
+ "nullsNotDistinct": false,
+ "columns": [
+ "projectId",
+ "tagId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tag": {
+ "name": "tag",
+ "schema": "",
+ "columns": {
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "tag_organizationId_organization_id_fk": {
+ "name": "tag_organizationId_organization_id_fk",
+ "tableFrom": "tag",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "unique_org_tag_name": {
+ "name": "unique_org_tag_name",
+ "nullsNotDistinct": false,
+ "columns": [
+ "organizationId",
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "firstName": {
+ "name": "firstName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "lastName": {
+ "name": "lastName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "isRegistered": {
+ "name": "isRegistered",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "expirationDate": {
+ "name": "expirationDate",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "two_factor_enabled": {
+ "name": "two_factor_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'user'"
+ },
+ "enablePaidFeatures": {
+ "name": "enablePaidFeatures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "allowImpersonation": {
+ "name": "allowImpersonation",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enableEnterpriseFeatures": {
+ "name": "enableEnterpriseFeatures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "licenseKey": {
+ "name": "licenseKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isValidEnterpriseLicense": {
+ "name": "isValidEnterpriseLicense",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "stripeCustomerId": {
+ "name": "stripeCustomerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripeSubscriptionId": {
+ "name": "stripeSubscriptionId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serversQuantity": {
+ "name": "serversQuantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "sendInvoiceNotifications": {
+ "name": "sendInvoiceNotifications",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isEnterpriseCloud": {
+ "name": "isEnterpriseCloud",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "trustedOrigins": {
+ "name": "trustedOrigins",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bookmarkedTemplates": {
+ "name": "bookmarkedTemplates",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.volume_backup": {
+ "name": "volume_backup",
+ "schema": "",
+ "columns": {
+ "volumeBackupId": {
+ "name": "volumeBackupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "turnOff": {
+ "name": "turnOff",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cronExpression": {
+ "name": "cronExpression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "keepLatestCount": {
+ "name": "keepLatestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "volume_backup_applicationId_application_applicationId_fk": {
+ "name": "volume_backup_applicationId_application_applicationId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_postgresId_postgres_postgresId_fk": {
+ "name": "volume_backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "volume_backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mongoId_mongo_mongoId_fk": {
+ "name": "volume_backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "volume_backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_redisId_redis_redisId_fk": {
+ "name": "volume_backup_redisId_redis_redisId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_libsqlId_libsql_libsqlId_fk": {
+ "name": "volume_backup_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_composeId_compose_composeId_fk": {
+ "name": "volume_backup_composeId_compose_composeId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_destinationId_destination_destinationId_fk": {
+ "name": "volume_backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webServerSettings": {
+ "name": "webServerSettings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webServerProvider": {
+ "name": "webServerProvider",
+ "type": "webServerProvider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'traefik'"
+ },
+ "caddyTrustedProxyConfig": {
+ "name": "caddyTrustedProxyConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'null'::jsonb"
+ },
+ "serverIp": {
+ "name": "serverIp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "letsEncryptEmail": {
+ "name": "letsEncryptEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sshPrivateKey": {
+ "name": "sshPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "logCleanupCron": {
+ "name": "logCleanupCron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 0 * * *'"
+ },
+ "metricsConfig": {
+ "name": "metricsConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb"
+ },
+ "whitelabelingConfig": {
+ "name": "whitelabelingConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{\"appName\":null,\"appDescription\":null,\"logoUrl\":null,\"faviconUrl\":null,\"customCss\":null,\"loginLogoUrl\":null,\"supportUrl\":null,\"docsUrl\":null,\"errorPageTitle\":null,\"errorPageDescription\":null,\"metaTitle\":null,\"footerText\":null}'::jsonb"
+ },
+ "remoteServersOnly": {
+ "name": "remoteServersOnly",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enforceSSO": {
+ "name": "enforceSSO",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheApplications": {
+ "name": "cleanupCacheApplications",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheOnPreviews": {
+ "name": "cleanupCacheOnPreviews",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheOnCompose": {
+ "name": "cleanupCacheOnCompose",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.buildType": {
+ "name": "buildType",
+ "schema": "public",
+ "values": [
+ "dockerfile",
+ "heroku_buildpacks",
+ "paketo_buildpacks",
+ "nixpacks",
+ "static",
+ "railpack"
+ ]
+ },
+ "public.sourceType": {
+ "name": "sourceType",
+ "schema": "public",
+ "values": [
+ "docker",
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea",
+ "drop"
+ ]
+ },
+ "public.backupType": {
+ "name": "backupType",
+ "schema": "public",
+ "values": [
+ "database",
+ "compose"
+ ]
+ },
+ "public.databaseType": {
+ "name": "databaseType",
+ "schema": "public",
+ "values": [
+ "postgres",
+ "mariadb",
+ "mysql",
+ "mongo",
+ "web-server",
+ "libsql"
+ ]
+ },
+ "public.composeType": {
+ "name": "composeType",
+ "schema": "public",
+ "values": [
+ "docker-compose",
+ "stack"
+ ]
+ },
+ "public.sourceTypeCompose": {
+ "name": "sourceTypeCompose",
+ "schema": "public",
+ "values": [
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea",
+ "raw"
+ ]
+ },
+ "public.deploymentStatus": {
+ "name": "deploymentStatus",
+ "schema": "public",
+ "values": [
+ "running",
+ "done",
+ "error",
+ "cancelled"
+ ]
+ },
+ "public.domainType": {
+ "name": "domainType",
+ "schema": "public",
+ "values": [
+ "compose",
+ "application",
+ "preview"
+ ]
+ },
+ "public.gitProviderType": {
+ "name": "gitProviderType",
+ "schema": "public",
+ "values": [
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea"
+ ]
+ },
+ "public.mountType": {
+ "name": "mountType",
+ "schema": "public",
+ "values": [
+ "bind",
+ "volume",
+ "file"
+ ]
+ },
+ "public.serviceType": {
+ "name": "serviceType",
+ "schema": "public",
+ "values": [
+ "application",
+ "postgres",
+ "mysql",
+ "mariadb",
+ "mongo",
+ "redis",
+ "compose",
+ "libsql"
+ ]
+ },
+ "public.notificationType": {
+ "name": "notificationType",
+ "schema": "public",
+ "values": [
+ "slack",
+ "telegram",
+ "discord",
+ "email",
+ "resend",
+ "gotify",
+ "ntfy",
+ "mattermost",
+ "pushover",
+ "custom",
+ "lark",
+ "teams"
+ ]
+ },
+ "public.patchType": {
+ "name": "patchType",
+ "schema": "public",
+ "values": [
+ "create",
+ "update",
+ "delete"
+ ]
+ },
+ "public.protocolType": {
+ "name": "protocolType",
+ "schema": "public",
+ "values": [
+ "tcp",
+ "udp"
+ ]
+ },
+ "public.publishModeType": {
+ "name": "publishModeType",
+ "schema": "public",
+ "values": [
+ "ingress",
+ "host"
+ ]
+ },
+ "public.RegistryType": {
+ "name": "RegistryType",
+ "schema": "public",
+ "values": [
+ "selfHosted",
+ "cloud"
+ ]
+ },
+ "public.scheduleType": {
+ "name": "scheduleType",
+ "schema": "public",
+ "values": [
+ "application",
+ "compose",
+ "server",
+ "dokploy-server"
+ ]
+ },
+ "public.shellType": {
+ "name": "shellType",
+ "schema": "public",
+ "values": [
+ "bash",
+ "sh"
+ ]
+ },
+ "public.serverStatus": {
+ "name": "serverStatus",
+ "schema": "public",
+ "values": [
+ "active",
+ "inactive"
+ ]
+ },
+ "public.serverType": {
+ "name": "serverType",
+ "schema": "public",
+ "values": [
+ "deploy",
+ "build"
+ ]
+ },
+ "public.applicationStatus": {
+ "name": "applicationStatus",
+ "schema": "public",
+ "values": [
+ "idle",
+ "running",
+ "done",
+ "error"
+ ]
+ },
+ "public.certificateType": {
+ "name": "certificateType",
+ "schema": "public",
+ "values": [
+ "letsencrypt",
+ "none",
+ "custom"
+ ]
+ },
+ "public.sqldNode": {
+ "name": "sqldNode",
+ "schema": "public",
+ "values": [
+ "primary",
+ "replica"
+ ]
+ },
+ "public.triggerType": {
+ "name": "triggerType",
+ "schema": "public",
+ "values": [
+ "push",
+ "tag"
+ ]
+ },
+ "public.webServerProvider": {
+ "name": "webServerProvider",
+ "schema": "public",
+ "values": [
+ "traefik",
+ "caddy"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/0172_snapshot.json b/apps/dokploy/drizzle/meta/0172_snapshot.json
new file mode 100644
index 0000000000..e1742b117e
--- /dev/null
+++ b/apps/dokploy/drizzle/meta/0172_snapshot.json
@@ -0,0 +1,8377 @@
+{
+ "id": "364132ca-1f0c-4dc5-8827-daf5f4c6cc2c",
+ "prevId": "99b3eb38-5993-4945-a6ae-46e791e23c57",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is2FAEnabled": {
+ "name": "is2FAEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resetPasswordToken": {
+ "name": "resetPasswordToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resetPasswordExpiresAt": {
+ "name": "resetPasswordExpiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmationToken": {
+ "name": "confirmationToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmationExpiresAt": {
+ "name": "confirmationExpiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.apikey": {
+ "name": "apikey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start": {
+ "name": "start",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config_id": {
+ "name": "config_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'default'"
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refill_interval": {
+ "name": "refill_interval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refill_amount": {
+ "name": "refill_amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_refill_at": {
+ "name": "last_refill_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_enabled": {
+ "name": "rate_limit_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_time_window": {
+ "name": "rate_limit_time_window",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_max": {
+ "name": "rate_limit_max",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "request_count": {
+ "name": "request_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "remaining": {
+ "name": "remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "apikey_reference_id_user_id_fk": {
+ "name": "apikey_reference_id_user_id_fk",
+ "tableFrom": "apikey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "reference_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation": {
+ "name": "invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invitation_organization_id_organization_id_fk": {
+ "name": "invitation_organization_id_organization_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_inviter_id_user_id_fk": {
+ "name": "invitation_inviter_id_user_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "user",
+ "columnsFrom": [
+ "inviter_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.member": {
+ "name": "member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateProjects": {
+ "name": "canCreateProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToSSHKeys": {
+ "name": "canAccessToSSHKeys",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateServices": {
+ "name": "canCreateServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteProjects": {
+ "name": "canDeleteProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteServices": {
+ "name": "canDeleteServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToDocker": {
+ "name": "canAccessToDocker",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToAPI": {
+ "name": "canAccessToAPI",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToGitProviders": {
+ "name": "canAccessToGitProviders",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToTraefikFiles": {
+ "name": "canAccessToTraefikFiles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteEnvironments": {
+ "name": "canDeleteEnvironments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateEnvironments": {
+ "name": "canCreateEnvironments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accesedProjects": {
+ "name": "accesedProjects",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedEnvironments": {
+ "name": "accessedEnvironments",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accesedServices": {
+ "name": "accesedServices",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedGitProviders": {
+ "name": "accessedGitProviders",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedServers": {
+ "name": "accessedServers",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "member_organization_id_organization_id_fk": {
+ "name": "member_organization_id_organization_id_fk",
+ "tableFrom": "member",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "member_user_id_user_id_fk": {
+ "name": "member_user_id_user_id_fk",
+ "tableFrom": "member",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "organization_owner_id_user_id_fk": {
+ "name": "organization_owner_id_user_id_fk",
+ "tableFrom": "organization",
+ "tableTo": "user",
+ "columnsFrom": [
+ "owner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "organization_slug_unique": {
+ "name": "organization_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization_role": {
+ "name": "organization_role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "organizationRole_organizationId_idx": {
+ "name": "organizationRole_organizationId_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "organizationRole_role_idx": {
+ "name": "organizationRole_role_idx",
+ "columns": [
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "organization_role_organization_id_organization_id_fk": {
+ "name": "organization_role_organization_id_organization_id_fk",
+ "tableFrom": "organization_role",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.two_factor": {
+ "name": "two_factor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backup_codes": {
+ "name": "backup_codes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "two_factor_user_id_user_id_fk": {
+ "name": "two_factor_user_id_user_id_fk",
+ "tableFrom": "two_factor",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ai": {
+ "name": "ai",
+ "schema": "",
+ "columns": {
+ "aiId": {
+ "name": "aiId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiUrl": {
+ "name": "apiUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isEnabled": {
+ "name": "isEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ai_organizationId_organization_id_fk": {
+ "name": "ai_organizationId_organization_id_fk",
+ "tableFrom": "ai",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.application": {
+ "name": "application",
+ "schema": "",
+ "columns": {
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewEnv": {
+ "name": "previewEnv",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "watchPaths": {
+ "name": "watchPaths",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewBuildArgs": {
+ "name": "previewBuildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewBuildSecrets": {
+ "name": "previewBuildSecrets",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewLabels": {
+ "name": "previewLabels",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewWildcard": {
+ "name": "previewWildcard",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewPort": {
+ "name": "previewPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "previewHttps": {
+ "name": "previewHttps",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "previewPath": {
+ "name": "previewPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "previewCustomCertResolver": {
+ "name": "previewCustomCertResolver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewLimit": {
+ "name": "previewLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3
+ },
+ "isPreviewDeploymentsActive": {
+ "name": "isPreviewDeploymentsActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "previewRequireCollaboratorPermissions": {
+ "name": "previewRequireCollaboratorPermissions",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "rollbackActive": {
+ "name": "rollbackActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "buildArgs": {
+ "name": "buildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildSecrets": {
+ "name": "buildSecrets",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subtitle": {
+ "name": "subtitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "cleanCache": {
+ "name": "cleanCache",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildPath": {
+ "name": "buildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "triggerType": {
+ "name": "triggerType",
+ "type": "triggerType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'push'"
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBuildPath": {
+ "name": "gitlabBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaRepository": {
+ "name": "giteaRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaOwner": {
+ "name": "giteaOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBranch": {
+ "name": "giteaBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBuildPath": {
+ "name": "giteaBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepositorySlug": {
+ "name": "bitbucketRepositorySlug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBuildPath": {
+ "name": "bitbucketBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBuildPath": {
+ "name": "customGitBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableSubmodules": {
+ "name": "enableSubmodules",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerfile": {
+ "name": "dockerfile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'Dockerfile'"
+ },
+ "dockerContextPath": {
+ "name": "dockerContextPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerBuildStage": {
+ "name": "dockerBuildStage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dropBuildPath": {
+ "name": "dropBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "buildType": {
+ "name": "buildType",
+ "type": "buildType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'nixpacks'"
+ },
+ "railpackVersion": {
+ "name": "railpackVersion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0.15.4'"
+ },
+ "herokuVersion": {
+ "name": "herokuVersion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'24'"
+ },
+ "publishDirectory": {
+ "name": "publishDirectory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isStaticSpa": {
+ "name": "isStaticSpa",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createEnvFile": {
+ "name": "createEnvFile",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackRegistryId": {
+ "name": "rollbackRegistryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildServerId": {
+ "name": "buildServerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildRegistryId": {
+ "name": "buildRegistryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "application",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_registryId_registry_registryId_fk": {
+ "name": "application_registryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "registryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_rollbackRegistryId_registry_registryId_fk": {
+ "name": "application_rollbackRegistryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "rollbackRegistryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_environmentId_environment_environmentId_fk": {
+ "name": "application_environmentId_environment_environmentId_fk",
+ "tableFrom": "application",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_githubId_github_githubId_fk": {
+ "name": "application_githubId_github_githubId_fk",
+ "tableFrom": "application",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_gitlabId_gitlab_gitlabId_fk": {
+ "name": "application_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_giteaId_gitea_giteaId_fk": {
+ "name": "application_giteaId_gitea_giteaId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitea",
+ "columnsFrom": [
+ "giteaId"
+ ],
+ "columnsTo": [
+ "giteaId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "application_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "application",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_serverId_server_serverId_fk": {
+ "name": "application_serverId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_buildServerId_server_serverId_fk": {
+ "name": "application_buildServerId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "buildServerId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_buildRegistryId_registry_registryId_fk": {
+ "name": "application_buildRegistryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "buildRegistryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "application_appName_unique": {
+ "name": "application_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_log": {
+ "name": "audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_email": {
+ "name": "user_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_role": {
+ "name": "user_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resource_name": {
+ "name": "resource_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "auditLog_organizationId_idx": {
+ "name": "auditLog_organizationId_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "auditLog_userId_idx": {
+ "name": "auditLog_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "auditLog_createdAt_idx": {
+ "name": "auditLog_createdAt_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_log_organization_id_organization_id_fk": {
+ "name": "audit_log_organization_id_organization_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_log_user_id_user_id_fk": {
+ "name": "audit_log_user_id_user_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.backup": {
+ "name": "backup",
+ "schema": "",
+ "columns": {
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule": {
+ "name": "schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "database": {
+ "name": "database",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "keepLatestCount": {
+ "name": "keepLatestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backupType": {
+ "name": "backupType",
+ "type": "backupType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'database'"
+ },
+ "databaseType": {
+ "name": "databaseType",
+ "type": "databaseType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "backup_destinationId_destination_destinationId_fk": {
+ "name": "backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_composeId_compose_composeId_fk": {
+ "name": "backup_composeId_compose_composeId_fk",
+ "tableFrom": "backup",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_postgresId_postgres_postgresId_fk": {
+ "name": "backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mongoId_mongo_mongoId_fk": {
+ "name": "backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_libsqlId_libsql_libsqlId_fk": {
+ "name": "backup_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_userId_user_id_fk": {
+ "name": "backup_userId_user_id_fk",
+ "tableFrom": "backup",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "backup_appName_unique": {
+ "name": "backup_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bitbucket": {
+ "name": "bitbucket",
+ "schema": "",
+ "columns": {
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bitbucketUsername": {
+ "name": "bitbucketUsername",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketEmail": {
+ "name": "bitbucketEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "appPassword": {
+ "name": "appPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketWorkspaceName": {
+ "name": "bitbucketWorkspaceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bitbucket_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "bitbucket",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.certificate": {
+ "name": "certificate",
+ "schema": "",
+ "columns": {
+ "certificateId": {
+ "name": "certificateId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificateData": {
+ "name": "certificateData",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificatePath": {
+ "name": "certificatePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "autoRenew": {
+ "name": "autoRenew",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "certificate_organizationId_organization_id_fk": {
+ "name": "certificate_organizationId_organization_id_fk",
+ "tableFrom": "certificate",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "certificate_serverId_server_serverId_fk": {
+ "name": "certificate_serverId_server_serverId_fk",
+ "tableFrom": "certificate",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "certificate_certificatePath_unique": {
+ "name": "certificate_certificatePath_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "certificatePath"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.compose": {
+ "name": "compose",
+ "schema": "",
+ "columns": {
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeFile": {
+ "name": "composeFile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceTypeCompose",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "composeType": {
+ "name": "composeType",
+ "type": "composeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'docker-compose'"
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepositorySlug": {
+ "name": "bitbucketRepositorySlug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaRepository": {
+ "name": "giteaRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaOwner": {
+ "name": "giteaOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBranch": {
+ "name": "giteaBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enableSubmodules": {
+ "name": "enableSubmodules",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "composePath": {
+ "name": "composePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'./docker-compose.yml'"
+ },
+ "suffix": {
+ "name": "suffix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "randomize": {
+ "name": "randomize",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isolatedDeployment": {
+ "name": "isolatedDeployment",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isolatedDeploymentsVolume": {
+ "name": "isolatedDeploymentsVolume",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "triggerType": {
+ "name": "triggerType",
+ "type": "triggerType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'push'"
+ },
+ "composeStatus": {
+ "name": "composeStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "watchPaths": {
+ "name": "watchPaths",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "compose",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_environmentId_environment_environmentId_fk": {
+ "name": "compose_environmentId_environment_environmentId_fk",
+ "tableFrom": "compose",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "compose_githubId_github_githubId_fk": {
+ "name": "compose_githubId_github_githubId_fk",
+ "tableFrom": "compose",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_gitlabId_gitlab_gitlabId_fk": {
+ "name": "compose_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "compose_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "compose",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_giteaId_gitea_giteaId_fk": {
+ "name": "compose_giteaId_gitea_giteaId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitea",
+ "columnsFrom": [
+ "giteaId"
+ ],
+ "columnsTo": [
+ "giteaId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_serverId_server_serverId_fk": {
+ "name": "compose_serverId_server_serverId_fk",
+ "tableFrom": "compose",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment": {
+ "name": "deployment",
+ "schema": "",
+ "columns": {
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "deploymentStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'running'"
+ },
+ "logPath": {
+ "name": "logPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pid": {
+ "name": "pid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isPreviewDeployment": {
+ "name": "isPreviewDeployment",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "startedAt": {
+ "name": "startedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finishedAt": {
+ "name": "finishedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scheduleId": {
+ "name": "scheduleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeBackupId": {
+ "name": "volumeBackupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildServerId": {
+ "name": "buildServerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "deployment_applicationId_application_applicationId_fk": {
+ "name": "deployment_applicationId_application_applicationId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_composeId_compose_composeId_fk": {
+ "name": "deployment_composeId_compose_composeId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_serverId_server_serverId_fk": {
+ "name": "deployment_serverId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": {
+ "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "preview_deployments",
+ "columnsFrom": [
+ "previewDeploymentId"
+ ],
+ "columnsTo": [
+ "previewDeploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_scheduleId_schedule_scheduleId_fk": {
+ "name": "deployment_scheduleId_schedule_scheduleId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "schedule",
+ "columnsFrom": [
+ "scheduleId"
+ ],
+ "columnsTo": [
+ "scheduleId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_backupId_backup_backupId_fk": {
+ "name": "deployment_backupId_backup_backupId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "backup",
+ "columnsFrom": [
+ "backupId"
+ ],
+ "columnsTo": [
+ "backupId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_rollbackId_rollback_rollbackId_fk": {
+ "name": "deployment_rollbackId_rollback_rollbackId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "rollback",
+ "columnsFrom": [
+ "rollbackId"
+ ],
+ "columnsTo": [
+ "rollbackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_volumeBackupId_volume_backup_volumeBackupId_fk": {
+ "name": "deployment_volumeBackupId_volume_backup_volumeBackupId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "volume_backup",
+ "columnsFrom": [
+ "volumeBackupId"
+ ],
+ "columnsTo": [
+ "volumeBackupId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_buildServerId_server_serverId_fk": {
+ "name": "deployment_buildServerId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "buildServerId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.destination": {
+ "name": "destination",
+ "schema": "",
+ "columns": {
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accessKey": {
+ "name": "accessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secretAccessKey": {
+ "name": "secretAccessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bucket": {
+ "name": "bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "region": {
+ "name": "region",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "additionalFlags": {
+ "name": "additionalFlags",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "destination_organizationId_organization_id_fk": {
+ "name": "destination_organizationId_organization_id_fk",
+ "tableFrom": "destination",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.domain": {
+ "name": "domain",
+ "schema": "",
+ "columns": {
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "customEntrypoint": {
+ "name": "customEntrypoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domainType": {
+ "name": "domainType",
+ "type": "domainType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'application'"
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customCertResolver": {
+ "name": "customCertResolver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "internalPath": {
+ "name": "internalPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "stripPath": {
+ "name": "stripPath",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "middlewares": {
+ "name": "middlewares",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "domain_composeId_compose_composeId_fk": {
+ "name": "domain_composeId_compose_composeId_fk",
+ "tableFrom": "domain",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_applicationId_application_applicationId_fk": {
+ "name": "domain_applicationId_application_applicationId_fk",
+ "tableFrom": "domain",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": {
+ "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk",
+ "tableFrom": "domain",
+ "tableTo": "preview_deployments",
+ "columnsFrom": [
+ "previewDeploymentId"
+ ],
+ "columnsTo": [
+ "previewDeploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment": {
+ "name": "environment",
+ "schema": "",
+ "columns": {
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isDefault": {
+ "name": "isDefault",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_projectId_project_projectId_fk": {
+ "name": "environment_projectId_project_projectId_fk",
+ "tableFrom": "environment",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_provider": {
+ "name": "git_provider",
+ "schema": "",
+ "columns": {
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerType": {
+ "name": "providerType",
+ "type": "gitProviderType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sharedWithOrganization": {
+ "name": "sharedWithOrganization",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_provider_organizationId_organization_id_fk": {
+ "name": "git_provider_organizationId_organization_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_provider_userId_user_id_fk": {
+ "name": "git_provider_userId_user_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gitea": {
+ "name": "gitea",
+ "schema": "",
+ "columns": {
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "giteaUrl": {
+ "name": "giteaUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'https://gitea.com'"
+ },
+ "giteaInternalUrl": {
+ "name": "giteaInternalUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'repo,repo:status,read:user,read:org'"
+ },
+ "last_authenticated_at": {
+ "name": "last_authenticated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitea_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitea",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.github": {
+ "name": "github",
+ "schema": "",
+ "columns": {
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "githubAppName": {
+ "name": "githubAppName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubAppId": {
+ "name": "githubAppId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientId": {
+ "name": "githubClientId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientSecret": {
+ "name": "githubClientSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubInstallationId": {
+ "name": "githubInstallationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubPrivateKey": {
+ "name": "githubPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubWebhookSecret": {
+ "name": "githubWebhookSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "github_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "github_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "github",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gitlab": {
+ "name": "gitlab",
+ "schema": "",
+ "columns": {
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "gitlabUrl": {
+ "name": "gitlabUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'https://gitlab.com'"
+ },
+ "gitlabInternalUrl": {
+ "name": "gitlabInternalUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitlab_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitlab",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.libsql": {
+ "name": "libsql",
+ "schema": "",
+ "columns": {
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sqldNode": {
+ "name": "sqldNode",
+ "type": "sqldNode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'primary'"
+ },
+ "sqldPrimaryUrl": {
+ "name": "sqldPrimaryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableNamespaces": {
+ "name": "enableNamespaces",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalGRPCPort": {
+ "name": "externalGRPCPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalAdminPort": {
+ "name": "externalAdminPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "libsql_environmentId_environment_environmentId_fk": {
+ "name": "libsql_environmentId_environment_environmentId_fk",
+ "tableFrom": "libsql",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "libsql_serverId_server_serverId_fk": {
+ "name": "libsql_serverId_server_serverId_fk",
+ "tableFrom": "libsql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "libsql_appName_unique": {
+ "name": "libsql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mariadb": {
+ "name": "mariadb",
+ "schema": "",
+ "columns": {
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mariadb_environmentId_environment_environmentId_fk": {
+ "name": "mariadb_environmentId_environment_environmentId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mariadb_serverId_server_serverId_fk": {
+ "name": "mariadb_serverId_server_serverId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mariadb_appName_unique": {
+ "name": "mariadb_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mongo": {
+ "name": "mongo",
+ "schema": "",
+ "columns": {
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'mongo:8'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicaSets": {
+ "name": "replicaSets",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mongo_environmentId_environment_environmentId_fk": {
+ "name": "mongo_environmentId_environment_environmentId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mongo_serverId_server_serverId_fk": {
+ "name": "mongo_serverId_server_serverId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mongo_appName_unique": {
+ "name": "mongo_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mount": {
+ "name": "mount",
+ "schema": "",
+ "columns": {
+ "mountId": {
+ "name": "mountId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "mountType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hostPath": {
+ "name": "hostPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "mountPath": {
+ "name": "mountPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mount_applicationId_application_applicationId_fk": {
+ "name": "mount_applicationId_application_applicationId_fk",
+ "tableFrom": "mount",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_composeId_compose_composeId_fk": {
+ "name": "mount_composeId_compose_composeId_fk",
+ "tableFrom": "mount",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_libsqlId_libsql_libsqlId_fk": {
+ "name": "mount_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mariadbId_mariadb_mariadbId_fk": {
+ "name": "mount_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mongoId_mongo_mongoId_fk": {
+ "name": "mount_mongoId_mongo_mongoId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mysqlId_mysql_mysqlId_fk": {
+ "name": "mount_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_postgresId_postgres_postgresId_fk": {
+ "name": "mount_postgresId_postgres_postgresId_fk",
+ "tableFrom": "mount",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_redisId_redis_redisId_fk": {
+ "name": "mount_redisId_redis_redisId_fk",
+ "tableFrom": "mount",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mysql": {
+ "name": "mysql",
+ "schema": "",
+ "columns": {
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mysql_environmentId_environment_environmentId_fk": {
+ "name": "mysql_environmentId_environment_environmentId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mysql_serverId_server_serverId_fk": {
+ "name": "mysql_serverId_server_serverId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mysql_appName_unique": {
+ "name": "mysql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.custom": {
+ "name": "custom",
+ "schema": "",
+ "columns": {
+ "customId": {
+ "name": "customId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "headers": {
+ "name": "headers",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.discord": {
+ "name": "discord",
+ "schema": "",
+ "columns": {
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decoration": {
+ "name": "decoration",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.email": {
+ "name": "email",
+ "schema": "",
+ "columns": {
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "smtpServer": {
+ "name": "smtpServer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "smtpPort": {
+ "name": "smtpPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gotify": {
+ "name": "gotify",
+ "schema": "",
+ "columns": {
+ "gotifyId": {
+ "name": "gotifyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverUrl": {
+ "name": "serverUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appToken": {
+ "name": "appToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 5
+ },
+ "decoration": {
+ "name": "decoration",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lark": {
+ "name": "lark",
+ "schema": "",
+ "columns": {
+ "larkId": {
+ "name": "larkId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mattermost": {
+ "name": "mattermost",
+ "schema": "",
+ "columns": {
+ "mattermostId": {
+ "name": "mattermostId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification": {
+ "name": "notification",
+ "schema": "",
+ "columns": {
+ "notificationId": {
+ "name": "notificationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appDeploy": {
+ "name": "appDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "appBuildError": {
+ "name": "appBuildError",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "databaseBackup": {
+ "name": "databaseBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "volumeBackup": {
+ "name": "volumeBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployRestart": {
+ "name": "dokployRestart",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployBackup": {
+ "name": "dokployBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerCleanup": {
+ "name": "dockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "serverThreshold": {
+ "name": "serverThreshold",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "notificationType": {
+ "name": "notificationType",
+ "type": "notificationType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resendId": {
+ "name": "resendId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gotifyId": {
+ "name": "gotifyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ntfyId": {
+ "name": "ntfyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mattermostId": {
+ "name": "mattermostId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customId": {
+ "name": "customId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "larkId": {
+ "name": "larkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pushoverId": {
+ "name": "pushoverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "teamsId": {
+ "name": "teamsId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_slackId_slack_slackId_fk": {
+ "name": "notification_slackId_slack_slackId_fk",
+ "tableFrom": "notification",
+ "tableTo": "slack",
+ "columnsFrom": [
+ "slackId"
+ ],
+ "columnsTo": [
+ "slackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_telegramId_telegram_telegramId_fk": {
+ "name": "notification_telegramId_telegram_telegramId_fk",
+ "tableFrom": "notification",
+ "tableTo": "telegram",
+ "columnsFrom": [
+ "telegramId"
+ ],
+ "columnsTo": [
+ "telegramId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_discordId_discord_discordId_fk": {
+ "name": "notification_discordId_discord_discordId_fk",
+ "tableFrom": "notification",
+ "tableTo": "discord",
+ "columnsFrom": [
+ "discordId"
+ ],
+ "columnsTo": [
+ "discordId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_emailId_email_emailId_fk": {
+ "name": "notification_emailId_email_emailId_fk",
+ "tableFrom": "notification",
+ "tableTo": "email",
+ "columnsFrom": [
+ "emailId"
+ ],
+ "columnsTo": [
+ "emailId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_resendId_resend_resendId_fk": {
+ "name": "notification_resendId_resend_resendId_fk",
+ "tableFrom": "notification",
+ "tableTo": "resend",
+ "columnsFrom": [
+ "resendId"
+ ],
+ "columnsTo": [
+ "resendId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_gotifyId_gotify_gotifyId_fk": {
+ "name": "notification_gotifyId_gotify_gotifyId_fk",
+ "tableFrom": "notification",
+ "tableTo": "gotify",
+ "columnsFrom": [
+ "gotifyId"
+ ],
+ "columnsTo": [
+ "gotifyId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_ntfyId_ntfy_ntfyId_fk": {
+ "name": "notification_ntfyId_ntfy_ntfyId_fk",
+ "tableFrom": "notification",
+ "tableTo": "ntfy",
+ "columnsFrom": [
+ "ntfyId"
+ ],
+ "columnsTo": [
+ "ntfyId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_mattermostId_mattermost_mattermostId_fk": {
+ "name": "notification_mattermostId_mattermost_mattermostId_fk",
+ "tableFrom": "notification",
+ "tableTo": "mattermost",
+ "columnsFrom": [
+ "mattermostId"
+ ],
+ "columnsTo": [
+ "mattermostId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_customId_custom_customId_fk": {
+ "name": "notification_customId_custom_customId_fk",
+ "tableFrom": "notification",
+ "tableTo": "custom",
+ "columnsFrom": [
+ "customId"
+ ],
+ "columnsTo": [
+ "customId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_larkId_lark_larkId_fk": {
+ "name": "notification_larkId_lark_larkId_fk",
+ "tableFrom": "notification",
+ "tableTo": "lark",
+ "columnsFrom": [
+ "larkId"
+ ],
+ "columnsTo": [
+ "larkId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_pushoverId_pushover_pushoverId_fk": {
+ "name": "notification_pushoverId_pushover_pushoverId_fk",
+ "tableFrom": "notification",
+ "tableTo": "pushover",
+ "columnsFrom": [
+ "pushoverId"
+ ],
+ "columnsTo": [
+ "pushoverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_teamsId_teams_teamsId_fk": {
+ "name": "notification_teamsId_teams_teamsId_fk",
+ "tableFrom": "notification",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "teamsId"
+ ],
+ "columnsTo": [
+ "teamsId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_organizationId_organization_id_fk": {
+ "name": "notification_organizationId_organization_id_fk",
+ "tableFrom": "notification",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ntfy": {
+ "name": "ntfy",
+ "schema": "",
+ "columns": {
+ "ntfyId": {
+ "name": "ntfyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverUrl": {
+ "name": "serverUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "topic": {
+ "name": "topic",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "accessToken": {
+ "name": "accessToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 3
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pushover": {
+ "name": "pushover",
+ "schema": "",
+ "columns": {
+ "pushoverId": {
+ "name": "pushoverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userKey": {
+ "name": "userKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "retry": {
+ "name": "retry",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expire": {
+ "name": "expire",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resend": {
+ "name": "resend",
+ "schema": "",
+ "columns": {
+ "resendId": {
+ "name": "resendId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.slack": {
+ "name": "slack",
+ "schema": "",
+ "columns": {
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.teams": {
+ "name": "teams",
+ "schema": "",
+ "columns": {
+ "teamsId": {
+ "name": "teamsId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.telegram": {
+ "name": "telegram",
+ "schema": "",
+ "columns": {
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "botToken": {
+ "name": "botToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "messageThreadId": {
+ "name": "messageThreadId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.patch": {
+ "name": "patch",
+ "schema": "",
+ "columns": {
+ "patchId": {
+ "name": "patchId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "patchType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'update'"
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "patch_applicationId_application_applicationId_fk": {
+ "name": "patch_applicationId_application_applicationId_fk",
+ "tableFrom": "patch",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "patch_composeId_compose_composeId_fk": {
+ "name": "patch_composeId_compose_composeId_fk",
+ "tableFrom": "patch",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "patch_filepath_application_unique": {
+ "name": "patch_filepath_application_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "filePath",
+ "applicationId"
+ ]
+ },
+ "patch_filepath_compose_unique": {
+ "name": "patch_filepath_compose_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "filePath",
+ "composeId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.port": {
+ "name": "port",
+ "schema": "",
+ "columns": {
+ "portId": {
+ "name": "portId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publishedPort": {
+ "name": "publishedPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "publishMode": {
+ "name": "publishMode",
+ "type": "publishModeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'host'"
+ },
+ "targetPort": {
+ "name": "targetPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "protocolType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "port_applicationId_application_applicationId_fk": {
+ "name": "port_applicationId_application_applicationId_fk",
+ "tableFrom": "port",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.postgres": {
+ "name": "postgres",
+ "schema": "",
+ "columns": {
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "postgres_environmentId_environment_environmentId_fk": {
+ "name": "postgres_environmentId_environment_environmentId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "postgres_serverId_server_serverId_fk": {
+ "name": "postgres_serverId_server_serverId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postgres_appName_unique": {
+ "name": "postgres_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.preview_deployments": {
+ "name": "preview_deployments",
+ "schema": "",
+ "columns": {
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestId": {
+ "name": "pullRequestId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestNumber": {
+ "name": "pullRequestNumber",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestURL": {
+ "name": "pullRequestURL",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestTitle": {
+ "name": "pullRequestTitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestCommentId": {
+ "name": "pullRequestCommentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "previewStatus": {
+ "name": "previewStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "preview_deployments_applicationId_application_applicationId_fk": {
+ "name": "preview_deployments_applicationId_application_applicationId_fk",
+ "tableFrom": "preview_deployments",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "preview_deployments_domainId_domain_domainId_fk": {
+ "name": "preview_deployments_domainId_domain_domainId_fk",
+ "tableFrom": "preview_deployments",
+ "tableTo": "domain",
+ "columnsFrom": [
+ "domainId"
+ ],
+ "columnsTo": [
+ "domainId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "preview_deployments_appName_unique": {
+ "name": "preview_deployments_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.project": {
+ "name": "project",
+ "schema": "",
+ "columns": {
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_organizationId_organization_id_fk": {
+ "name": "project_organizationId_organization_id_fk",
+ "tableFrom": "project",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.redirect": {
+ "name": "redirect",
+ "schema": "",
+ "columns": {
+ "redirectId": {
+ "name": "redirectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "regex": {
+ "name": "regex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "replacement": {
+ "name": "replacement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permanent": {
+ "name": "permanent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redirect_applicationId_application_applicationId_fk": {
+ "name": "redirect_applicationId_application_applicationId_fk",
+ "tableFrom": "redirect",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.redis": {
+ "name": "redis",
+ "schema": "",
+ "columns": {
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ulimitsSwarm": {
+ "name": "ulimitsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redis_environmentId_environment_environmentId_fk": {
+ "name": "redis_environmentId_environment_environmentId_fk",
+ "tableFrom": "redis",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "redis_serverId_server_serverId_fk": {
+ "name": "redis_serverId_server_serverId_fk",
+ "tableFrom": "redis",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "redis_appName_unique": {
+ "name": "redis_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.registry": {
+ "name": "registry",
+ "schema": "",
+ "columns": {
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "registryName": {
+ "name": "registryName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "imagePrefix": {
+ "name": "imagePrefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "selfHosted": {
+ "name": "selfHosted",
+ "type": "RegistryType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'cloud'"
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "registry_organizationId_organization_id_fk": {
+ "name": "registry_organizationId_organization_id_fk",
+ "tableFrom": "registry",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rollback": {
+ "name": "rollback",
+ "schema": "",
+ "columns": {
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fullContext": {
+ "name": "fullContext",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "rollback_deploymentId_deployment_deploymentId_fk": {
+ "name": "rollback_deploymentId_deployment_deploymentId_fk",
+ "tableFrom": "rollback",
+ "tableTo": "deployment",
+ "columnsFrom": [
+ "deploymentId"
+ ],
+ "columnsTo": [
+ "deploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.schedule": {
+ "name": "schedule",
+ "schema": "",
+ "columns": {
+ "scheduleId": {
+ "name": "scheduleId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cronExpression": {
+ "name": "cronExpression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shellType": {
+ "name": "shellType",
+ "type": "shellType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'bash'"
+ },
+ "scheduleType": {
+ "name": "scheduleType",
+ "type": "scheduleType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "script": {
+ "name": "script",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "schedule_applicationId_application_applicationId_fk": {
+ "name": "schedule_applicationId_application_applicationId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_composeId_compose_composeId_fk": {
+ "name": "schedule_composeId_compose_composeId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_serverId_server_serverId_fk": {
+ "name": "schedule_serverId_server_serverId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_organizationId_organization_id_fk": {
+ "name": "schedule_organizationId_organization_id_fk",
+ "tableFrom": "schedule",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.security": {
+ "name": "security",
+ "schema": "",
+ "columns": {
+ "securityId": {
+ "name": "securityId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "security_applicationId_application_applicationId_fk": {
+ "name": "security_applicationId_application_applicationId_fk",
+ "tableFrom": "security",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "security_username_applicationId_unique": {
+ "name": "security_username_applicationId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username",
+ "applicationId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.server": {
+ "name": "server",
+ "schema": "",
+ "columns": {
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ipAddress": {
+ "name": "ipAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'root'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "webServerProvider": {
+ "name": "webServerProvider",
+ "type": "webServerProvider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'traefik'"
+ },
+ "caddyTrustedProxyConfig": {
+ "name": "caddyTrustedProxyConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'null'::jsonb"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverStatus": {
+ "name": "serverStatus",
+ "type": "serverStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "serverType": {
+ "name": "serverType",
+ "type": "serverType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'deploy'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metricsConfig": {
+ "name": "metricsConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "server_organizationId_organization_id_fk": {
+ "name": "server_organizationId_organization_id_fk",
+ "tableFrom": "server",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "server_sshKeyId_ssh-key_sshKeyId_fk": {
+ "name": "server_sshKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "server",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "sshKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active_organization_id": {
+ "name": "active_organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ssh-key": {
+ "name": "ssh-key",
+ "schema": "",
+ "columns": {
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "publicKey": {
+ "name": "publicKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lastUsedAt": {
+ "name": "lastUsedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh-key_organizationId_organization_id_fk": {
+ "name": "ssh-key_organizationId_organization_id_fk",
+ "tableFrom": "ssh-key",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_provider": {
+ "name": "sso_provider",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "saml_config": {
+ "name": "saml_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_provider_user_id_user_id_fk": {
+ "name": "sso_provider_user_id_user_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "sso_provider_organization_id_organization_id_fk": {
+ "name": "sso_provider_organization_id_organization_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_provider_provider_id_unique": {
+ "name": "sso_provider_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.project_tag": {
+ "name": "project_tag",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_tag_projectId_project_projectId_fk": {
+ "name": "project_tag_projectId_project_projectId_fk",
+ "tableFrom": "project_tag",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "project_tag_tagId_tag_tagId_fk": {
+ "name": "project_tag_tagId_tag_tagId_fk",
+ "tableFrom": "project_tag",
+ "tableTo": "tag",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "tagId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "unique_project_tag": {
+ "name": "unique_project_tag",
+ "nullsNotDistinct": false,
+ "columns": [
+ "projectId",
+ "tagId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tag": {
+ "name": "tag",
+ "schema": "",
+ "columns": {
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "tag_organizationId_organization_id_fk": {
+ "name": "tag_organizationId_organization_id_fk",
+ "tableFrom": "tag",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "unique_org_tag_name": {
+ "name": "unique_org_tag_name",
+ "nullsNotDistinct": false,
+ "columns": [
+ "organizationId",
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "firstName": {
+ "name": "firstName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "lastName": {
+ "name": "lastName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "isRegistered": {
+ "name": "isRegistered",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "expirationDate": {
+ "name": "expirationDate",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "two_factor_enabled": {
+ "name": "two_factor_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'user'"
+ },
+ "enablePaidFeatures": {
+ "name": "enablePaidFeatures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "allowImpersonation": {
+ "name": "allowImpersonation",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enableEnterpriseFeatures": {
+ "name": "enableEnterpriseFeatures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "licenseKey": {
+ "name": "licenseKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isValidEnterpriseLicense": {
+ "name": "isValidEnterpriseLicense",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "stripeCustomerId": {
+ "name": "stripeCustomerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripeSubscriptionId": {
+ "name": "stripeSubscriptionId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serversQuantity": {
+ "name": "serversQuantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "sendInvoiceNotifications": {
+ "name": "sendInvoiceNotifications",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isEnterpriseCloud": {
+ "name": "isEnterpriseCloud",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "trustedOrigins": {
+ "name": "trustedOrigins",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bookmarkedTemplates": {
+ "name": "bookmarkedTemplates",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.volume_backup": {
+ "name": "volume_backup",
+ "schema": "",
+ "columns": {
+ "volumeBackupId": {
+ "name": "volumeBackupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "turnOff": {
+ "name": "turnOff",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cronExpression": {
+ "name": "cronExpression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "keepLatestCount": {
+ "name": "keepLatestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "libsqlId": {
+ "name": "libsqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "volume_backup_applicationId_application_applicationId_fk": {
+ "name": "volume_backup_applicationId_application_applicationId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_postgresId_postgres_postgresId_fk": {
+ "name": "volume_backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "volume_backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mongoId_mongo_mongoId_fk": {
+ "name": "volume_backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "volume_backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_redisId_redis_redisId_fk": {
+ "name": "volume_backup_redisId_redis_redisId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_libsqlId_libsql_libsqlId_fk": {
+ "name": "volume_backup_libsqlId_libsql_libsqlId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "libsql",
+ "columnsFrom": [
+ "libsqlId"
+ ],
+ "columnsTo": [
+ "libsqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_composeId_compose_composeId_fk": {
+ "name": "volume_backup_composeId_compose_composeId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_destinationId_destination_destinationId_fk": {
+ "name": "volume_backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webServerSettings": {
+ "name": "webServerSettings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webServerProvider": {
+ "name": "webServerProvider",
+ "type": "webServerProvider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'traefik'"
+ },
+ "caddyTrustedProxyConfig": {
+ "name": "caddyTrustedProxyConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'null'::jsonb"
+ },
+ "requestLogsEnabled": {
+ "name": "requestLogsEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "serverIp": {
+ "name": "serverIp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "letsEncryptEmail": {
+ "name": "letsEncryptEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sshPrivateKey": {
+ "name": "sshPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "logCleanupCron": {
+ "name": "logCleanupCron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 0 * * *'"
+ },
+ "metricsConfig": {
+ "name": "metricsConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb"
+ },
+ "whitelabelingConfig": {
+ "name": "whitelabelingConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{\"appName\":null,\"appDescription\":null,\"logoUrl\":null,\"faviconUrl\":null,\"customCss\":null,\"loginLogoUrl\":null,\"supportUrl\":null,\"docsUrl\":null,\"errorPageTitle\":null,\"errorPageDescription\":null,\"metaTitle\":null,\"footerText\":null}'::jsonb"
+ },
+ "remoteServersOnly": {
+ "name": "remoteServersOnly",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enforceSSO": {
+ "name": "enforceSSO",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheApplications": {
+ "name": "cleanupCacheApplications",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheOnPreviews": {
+ "name": "cleanupCacheOnPreviews",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheOnCompose": {
+ "name": "cleanupCacheOnCompose",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.buildType": {
+ "name": "buildType",
+ "schema": "public",
+ "values": [
+ "dockerfile",
+ "heroku_buildpacks",
+ "paketo_buildpacks",
+ "nixpacks",
+ "static",
+ "railpack"
+ ]
+ },
+ "public.sourceType": {
+ "name": "sourceType",
+ "schema": "public",
+ "values": [
+ "docker",
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea",
+ "drop"
+ ]
+ },
+ "public.backupType": {
+ "name": "backupType",
+ "schema": "public",
+ "values": [
+ "database",
+ "compose"
+ ]
+ },
+ "public.databaseType": {
+ "name": "databaseType",
+ "schema": "public",
+ "values": [
+ "postgres",
+ "mariadb",
+ "mysql",
+ "mongo",
+ "web-server",
+ "libsql"
+ ]
+ },
+ "public.composeType": {
+ "name": "composeType",
+ "schema": "public",
+ "values": [
+ "docker-compose",
+ "stack"
+ ]
+ },
+ "public.sourceTypeCompose": {
+ "name": "sourceTypeCompose",
+ "schema": "public",
+ "values": [
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea",
+ "raw"
+ ]
+ },
+ "public.deploymentStatus": {
+ "name": "deploymentStatus",
+ "schema": "public",
+ "values": [
+ "running",
+ "done",
+ "error",
+ "cancelled"
+ ]
+ },
+ "public.domainType": {
+ "name": "domainType",
+ "schema": "public",
+ "values": [
+ "compose",
+ "application",
+ "preview"
+ ]
+ },
+ "public.gitProviderType": {
+ "name": "gitProviderType",
+ "schema": "public",
+ "values": [
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea"
+ ]
+ },
+ "public.mountType": {
+ "name": "mountType",
+ "schema": "public",
+ "values": [
+ "bind",
+ "volume",
+ "file"
+ ]
+ },
+ "public.serviceType": {
+ "name": "serviceType",
+ "schema": "public",
+ "values": [
+ "application",
+ "postgres",
+ "mysql",
+ "mariadb",
+ "mongo",
+ "redis",
+ "compose",
+ "libsql"
+ ]
+ },
+ "public.notificationType": {
+ "name": "notificationType",
+ "schema": "public",
+ "values": [
+ "slack",
+ "telegram",
+ "discord",
+ "email",
+ "resend",
+ "gotify",
+ "ntfy",
+ "mattermost",
+ "pushover",
+ "custom",
+ "lark",
+ "teams"
+ ]
+ },
+ "public.patchType": {
+ "name": "patchType",
+ "schema": "public",
+ "values": [
+ "create",
+ "update",
+ "delete"
+ ]
+ },
+ "public.protocolType": {
+ "name": "protocolType",
+ "schema": "public",
+ "values": [
+ "tcp",
+ "udp"
+ ]
+ },
+ "public.publishModeType": {
+ "name": "publishModeType",
+ "schema": "public",
+ "values": [
+ "ingress",
+ "host"
+ ]
+ },
+ "public.RegistryType": {
+ "name": "RegistryType",
+ "schema": "public",
+ "values": [
+ "selfHosted",
+ "cloud"
+ ]
+ },
+ "public.scheduleType": {
+ "name": "scheduleType",
+ "schema": "public",
+ "values": [
+ "application",
+ "compose",
+ "server",
+ "dokploy-server"
+ ]
+ },
+ "public.shellType": {
+ "name": "shellType",
+ "schema": "public",
+ "values": [
+ "bash",
+ "sh"
+ ]
+ },
+ "public.serverStatus": {
+ "name": "serverStatus",
+ "schema": "public",
+ "values": [
+ "active",
+ "inactive"
+ ]
+ },
+ "public.serverType": {
+ "name": "serverType",
+ "schema": "public",
+ "values": [
+ "deploy",
+ "build"
+ ]
+ },
+ "public.applicationStatus": {
+ "name": "applicationStatus",
+ "schema": "public",
+ "values": [
+ "idle",
+ "running",
+ "done",
+ "error"
+ ]
+ },
+ "public.certificateType": {
+ "name": "certificateType",
+ "schema": "public",
+ "values": [
+ "letsencrypt",
+ "none",
+ "custom"
+ ]
+ },
+ "public.sqldNode": {
+ "name": "sqldNode",
+ "schema": "public",
+ "values": [
+ "primary",
+ "replica"
+ ]
+ },
+ "public.triggerType": {
+ "name": "triggerType",
+ "schema": "public",
+ "values": [
+ "push",
+ "tag"
+ ]
+ },
+ "public.webServerProvider": {
+ "name": "webServerProvider",
+ "schema": "public",
+ "values": [
+ "traefik",
+ "caddy"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json
index 573e42b56e..842d1f82b9 100644
--- a/apps/dokploy/drizzle/meta/_journal.json
+++ b/apps/dokploy/drizzle/meta/_journal.json
@@ -1191,6 +1191,27 @@
"when": 1780127552074,
"tag": "0169_parched_johnny_storm",
"breakpoints": true
+ },
+ {
+ "idx": 170,
+ "version": "7",
+ "when": 1780337191309,
+ "tag": "0170_web_server_provider",
+ "breakpoints": true
+ },
+ {
+ "idx": 171,
+ "version": "7",
+ "when": 1780360688821,
+ "tag": "0171_caddy_trusted_proxy_config",
+ "breakpoints": true
+ },
+ {
+ "idx": 172,
+ "version": "7",
+ "when": 1780371136997,
+ "tag": "0172_normal_gauntlet",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/apps/dokploy/esbuild.config.ts b/apps/dokploy/esbuild.config.ts
index fa053d726c..e590de8254 100644
--- a/apps/dokploy/esbuild.config.ts
+++ b/apps/dokploy/esbuild.config.ts
@@ -29,6 +29,7 @@ try {
"reset-password": "reset-password.ts",
"reset-2fa": "reset-2fa.ts",
"migrate-auth-secret": "scripts/migrate-auth-secret.ts",
+ "caddy-migration-rollback": "scripts/caddy-migration-rollback.ts",
},
bundle: true,
platform: "node",
diff --git a/apps/dokploy/migration.ts b/apps/dokploy/migration.ts
index 984197b2ae..0f077acdfa 100644
--- a/apps/dokploy/migration.ts
+++ b/apps/dokploy/migration.ts
@@ -1,19 +1,7 @@
-import { dbUrl } from "@dokploy/server/db";
-import { drizzle } from "drizzle-orm/postgres-js";
-import { migrate } from "drizzle-orm/postgres-js/migrator";
-import postgres from "postgres";
+import { runRuntimeMigrations } from "./server/db/run-migrations";
-const sql = postgres(dbUrl, { max: 1 });
-const db = drizzle(sql);
-
-await migrate(db, { migrationsFolder: "drizzle" })
- .then(() => {
- console.log("Migration complete");
- sql.end();
- })
- .catch((error) => {
- console.log("Migration failed", error);
- })
- .finally(() => {
- sql.end();
- });
+try {
+ await runRuntimeMigrations();
+} catch {
+ process.exit(1);
+}
diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json
index c271f32bd4..4c24f12bb0 100644
--- a/apps/dokploy/package.json
+++ b/apps/dokploy/package.json
@@ -20,6 +20,8 @@
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
"migration:run": "tsx -r dotenv/config migration.ts",
"manual-migration:run": "tsx -r dotenv/config migrate.ts",
+ "caddy:migration:rollback": "tsx -r dotenv/config scripts/caddy-migration-rollback.ts",
+ "caddy:migration:rollback:runtime": "node -r dotenv/config dist/caddy-migration-rollback.mjs",
"migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts",
"migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts",
"db:push": "drizzle-kit push --config ./server/db/drizzle.config.ts",
diff --git a/apps/dokploy/scripts/caddy-migration-rollback.ts b/apps/dokploy/scripts/caddy-migration-rollback.ts
new file mode 100644
index 0000000000..40e9936a3f
--- /dev/null
+++ b/apps/dokploy/scripts/caddy-migration-rollback.ts
@@ -0,0 +1,87 @@
+import { pathToFileURL } from "node:url";
+import type { CaddyMigrationReport } from "@dokploy/server";
+
+interface RollbackArgs {
+ migrationId: string;
+ serverId?: string;
+}
+
+interface CliIo {
+ stdout: Pick;
+ stderr: Pick;
+}
+
+const usage =
+ "Usage: caddy-migration-rollback --migration-id [--server-id ]";
+
+export const parseCaddyRollbackArgs = (argv: string[]): RollbackArgs => {
+ let migrationId = "";
+ let serverId: string | undefined;
+ for (let index = 0; index < argv.length; index++) {
+ const arg = argv[index];
+ if (arg === "--migration-id") {
+ migrationId = argv[++index] ?? "";
+ } else if (arg === "--server-id") {
+ serverId = argv[++index] || undefined;
+ } else if (arg === "--help" || arg === "-h") {
+ throw new Error(usage);
+ } else {
+ throw new Error(`Unknown argument "${arg}". ${usage}`);
+ }
+ }
+ if (!migrationId) {
+ throw new Error(`Missing --migration-id. ${usage}`);
+ }
+ return { migrationId, serverId };
+};
+
+const isHelpRequest = (argv: string[]) =>
+ argv.some((arg) => arg === "--help" || arg === "-h");
+
+const buildOutput = (report: CaddyMigrationReport) => ({
+ migrationId: report.migrationId,
+ status: report.status,
+ providerTarget: "traefik",
+ warnings: report.warnings,
+ summary: report.summary,
+ reportPath: report.artifactPaths.reportJson,
+});
+
+export const runCaddyMigrationRollbackCli = async (
+ argv = process.argv.slice(2),
+ io: CliIo = process,
+) => {
+ if (isHelpRequest(argv)) {
+ io.stdout.write(`${usage}\n`);
+ return 0;
+ }
+
+ try {
+ const args = parseCaddyRollbackArgs(argv);
+ const { rollbackCaddyMigration } = await import("@dokploy/server");
+ const report = await rollbackCaddyMigration(args);
+ const output = buildOutput(report);
+ io.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
+ return report.status === "rolled_back" ? 0 : 1;
+ } catch (error) {
+ const message = error instanceof Error ? error.message : "Rollback failed";
+ io.stderr.write(`${message}\n`);
+ io.stdout.write(
+ `${JSON.stringify(
+ {
+ status: "failed",
+ providerTarget: "traefik",
+ error: message,
+ },
+ null,
+ 2,
+ )}\n`,
+ );
+ return 1;
+ }
+};
+
+if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
+ const code = await runCaddyMigrationRollbackCli();
+ process.exit(code);
+}
diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts
index 81e03fe26a..fc8c43ee90 100644
--- a/apps/dokploy/server/api/routers/ai.ts
+++ b/apps/dokploy/server/api/routers/ai.ts
@@ -5,7 +5,7 @@ import {
deploySuggestionSchema,
} from "@dokploy/server/db/schema/ai";
import {
- createDomain,
+ createComposeDomain,
createMount,
findEnvironmentById,
} from "@dokploy/server/index";
@@ -352,12 +352,17 @@ ${input.logs}`,
if (input.domains && input.domains?.length > 0) {
for (const domain of input.domains) {
- await createDomain({
- ...domain,
- domainType: "compose",
- certificateType: "none",
- composeId: compose.composeId,
- });
+ await createComposeDomain(
+ compose,
+ {
+ ...domain,
+ domainType: "compose",
+ certificateType: "none",
+ composeId: compose.composeId,
+ },
+ undefined,
+ ctx.session.activeOrganizationId,
+ );
}
}
if (input.configFiles && input.configFiles?.length > 0) {
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts
index c7b1f8642f..2cb64046e5 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -1,8 +1,10 @@
import {
clearOldDeployments,
createApplication,
+ createCaddyApplicationRouteFragment,
deleteAllMiddlewares,
findApplicationById,
+ findDomainsByApplicationId,
findEnvironmentById,
findGitProviderById,
findProjectById,
@@ -19,6 +21,7 @@ import {
removeMonitoringDirectory,
removeService,
removeTraefikConfig,
+ resolveWebServerProvider,
startService,
startServiceRemote,
stopService,
@@ -796,6 +799,45 @@ export const applicationRouter = createTRPCRouter({
}
return traefikConfig;
}),
+ readWebServerConfig: protectedProcedure
+ .input(apiFindOneApplication)
+ .query(async ({ input, ctx }) => {
+ await checkServicePermissionAndAccess(ctx, input.applicationId, {
+ traefikFiles: ["read"],
+ });
+ const application = await findApplicationById(input.applicationId);
+ const provider = await resolveWebServerProvider(
+ application.serverId || undefined,
+ );
+
+ if (provider === "traefik") {
+ if (application.serverId) {
+ return await readRemoteConfig(
+ application.serverId,
+ application.appName,
+ );
+ }
+ return readConfig(application.appName);
+ }
+
+ const domains = await findDomainsByApplicationId(input.applicationId);
+ const fragments = domains.map((domain) =>
+ createCaddyApplicationRouteFragment(
+ application as never,
+ domain as never,
+ ),
+ );
+ return `${JSON.stringify(
+ {
+ provider,
+ message:
+ "Generated Caddy route fragments for this application. Caddy manages HTTPS certificates automatically for HTTPS domains; Traefik custom certificate resolvers do not apply.",
+ fragments,
+ },
+ null,
+ 2,
+ )}\n`;
+ }),
dropDeployment: protectedProcedure
.input(
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index 51e257ce65..cc2f0efcde 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -1,11 +1,11 @@
import {
- addDomainToCompose,
+ addDomainToComposeForWebServer,
clearOldDeployments,
cloneCompose,
createCommand,
createCompose,
createComposeByTemplate,
- createDomain,
+ createComposeDomain,
createMount,
deleteMount,
execAsync,
@@ -26,8 +26,8 @@ import {
randomizeIsolatedDeploymentComposeFile,
removeCompose,
removeComposeDirectory,
+ removeComposeDomainsForWebServer,
removeDeploymentsByComposeId,
- removeDomainById,
startCompose,
stopCompose,
updateCompose,
@@ -411,7 +411,10 @@ export const composeRouter = createTRPCRouter({
});
const compose = await findComposeById(input.composeId);
const domains = await findDomainsByComposeId(input.composeId);
- const composeFile = await addDomainToCompose(compose, domains);
+ const composeFile = await addDomainToComposeForWebServer(
+ compose,
+ domains,
+ );
return stringify(composeFile, {
lineWidth: 1000,
});
@@ -668,13 +671,18 @@ export const composeRouter = createTRPCRouter({
if (generate.domains && generate.domains?.length > 0) {
for (const domain of generate.domains) {
- await createDomain({
- ...domain,
- domainType: "compose",
- certificateType: "none",
- composeId: compose.composeId,
- host: domain.host || "",
- });
+ await createComposeDomain(
+ compose,
+ {
+ ...domain,
+ domainType: "compose",
+ certificateType: "none",
+ composeId: compose.composeId,
+ host: domain.host || "",
+ },
+ undefined,
+ ctx.session.activeOrganizationId,
+ );
}
}
@@ -961,9 +969,12 @@ export const composeRouter = createTRPCRouter({
await deleteMount(mount.mountId);
}
- for (const domain of compose.domains) {
- await removeDomainById(domain.domainId);
- }
+ await removeComposeDomainsForWebServer(
+ compose,
+ compose.domains,
+ undefined,
+ ctx.session.activeOrganizationId,
+ );
let serverIp = "127.0.0.1";
@@ -1024,13 +1035,18 @@ export const composeRouter = createTRPCRouter({
if (processedTemplate.domains && processedTemplate.domains.length > 0) {
for (const domain of processedTemplate.domains) {
- await createDomain({
- ...domain,
- domainType: "compose",
- certificateType: "none",
- composeId: compose.composeId,
- host: domain.host || "",
- });
+ await createComposeDomain(
+ compose,
+ {
+ ...domain,
+ domainType: "compose",
+ certificateType: "none",
+ composeId: compose.composeId,
+ host: domain.host || "",
+ },
+ undefined,
+ ctx.session.activeOrganizationId,
+ );
}
}
diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts
index 8210fcf8a5..0b975afc96 100644
--- a/apps/dokploy/server/api/routers/domain.ts
+++ b/apps/dokploy/server/api/routers/domain.ts
@@ -1,6 +1,9 @@
import {
+ assertCaddyDomainSupported,
+ createComposeDomain,
createDomain,
findApplicationById,
+ findComposeById,
findDomainById,
findDomainsByApplicationId,
findDomainsByComposeId,
@@ -8,9 +11,11 @@ import {
findServerById,
generateTraefikMeDomain,
getWebServerSettings,
- manageDomain,
- removeDomain,
+ manageWebServerDomain,
+ refreshCaddyComposeRoutes,
removeDomainById,
+ removeWebServerDomain,
+ resolveWebServerProvider,
updateDomainById,
validateDomain,
} from "@dokploy/server";
@@ -31,6 +36,23 @@ import {
apiUpdateDomain,
} from "@/server/db/schema";
+const toDomainUpdateFields = (
+ domain: Awaited>,
+) => ({
+ host: domain.host,
+ https: domain.https,
+ port: domain.port,
+ customEntrypoint: domain.customEntrypoint,
+ path: domain.path,
+ serviceName: domain.serviceName,
+ domainType: domain.domainType,
+ customCertResolver: domain.customCertResolver,
+ certificateType: domain.certificateType,
+ internalPath: domain.internalPath,
+ stripPath: domain.stripPath,
+ middlewares: domain.middlewares,
+});
+
export const domainRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateDomain)
@@ -40,7 +62,23 @@ export const domainRouter = createTRPCRouter({
await checkServicePermissionAndAccess(ctx, input.composeId, {
domain: ["create"],
});
- } else if (input.domainType === "application" && input.applicationId) {
+ const compose = await findComposeById(input.composeId);
+ const provider = await resolveWebServerProvider(compose.serverId);
+ const domain = await createComposeDomain(
+ compose,
+ input,
+ provider,
+ ctx.session.activeOrganizationId,
+ );
+ await audit(ctx, {
+ action: "create",
+ resourceType: "domain",
+ resourceId: domain.domainId,
+ resourceName: domain.host,
+ });
+ return domain;
+ }
+ if (input.domainType === "application" && input.applicationId) {
await checkServicePermissionAndAccess(ctx, input.applicationId, {
domain: ["create"],
});
@@ -118,6 +156,101 @@ export const domainRouter = createTRPCRouter({
});
}
+ const nextDomain = { ...currentDomain, ...input };
+ if (currentDomain.applicationId) {
+ const application = await findApplicationById(
+ currentDomain.applicationId,
+ );
+ if (
+ (await resolveWebServerProvider(application.serverId)) === "caddy"
+ ) {
+ assertCaddyDomainSupported(nextDomain);
+ await manageWebServerDomain(application, nextDomain);
+ try {
+ const result = await updateDomainById(input.domainId, input);
+ if (!result) {
+ throw new Error("Error updating domain");
+ }
+ await audit(ctx, {
+ action: "update",
+ resourceType: "domain",
+ resourceId: result.domainId,
+ resourceName: result.host,
+ });
+ return result;
+ } catch (error) {
+ await manageWebServerDomain(application, currentDomain);
+ throw error;
+ }
+ }
+ } else if (currentDomain.previewDeploymentId) {
+ const previewDeployment = await findPreviewDeploymentById(
+ currentDomain.previewDeploymentId,
+ );
+ const application = await findApplicationById(
+ previewDeployment.applicationId,
+ );
+ application.appName = previewDeployment.appName;
+ if (
+ (await resolveWebServerProvider(application.serverId)) === "caddy"
+ ) {
+ assertCaddyDomainSupported(nextDomain);
+ await manageWebServerDomain(application, nextDomain);
+ try {
+ const result = await updateDomainById(input.domainId, input);
+ if (!result) {
+ throw new Error("Error updating domain");
+ }
+ await audit(ctx, {
+ action: "update",
+ resourceType: "domain",
+ resourceId: result.domainId,
+ resourceName: result.host,
+ });
+ return result;
+ } catch (error) {
+ await manageWebServerDomain(application, currentDomain);
+ throw error;
+ }
+ }
+ } else if (currentDomain.composeId) {
+ const compose = await findComposeById(currentDomain.composeId);
+ if ((await resolveWebServerProvider(compose.serverId)) === "caddy") {
+ assertCaddyDomainSupported(nextDomain);
+ const result = await updateDomainById(input.domainId, input);
+ if (!result) {
+ throw new Error("Error updating domain");
+ }
+ try {
+ await refreshCaddyComposeRoutes(
+ compose,
+ undefined,
+ "caddy",
+ ctx.session.activeOrganizationId,
+ );
+ await audit(ctx, {
+ action: "update",
+ resourceType: "domain",
+ resourceId: result.domainId,
+ resourceName: result.host,
+ });
+ return result;
+ } catch (error) {
+ await updateDomainById(
+ input.domainId,
+ toDomainUpdateFields(currentDomain),
+ );
+ await refreshCaddyComposeRoutes(
+ compose,
+ undefined,
+ "caddy",
+ ctx.session.activeOrganizationId,
+ );
+ throw error;
+ }
+ }
+ }
+
const result = await updateDomainById(input.domainId, input);
const domain = await findDomainById(input.domainId);
await audit(ctx, {
@@ -128,7 +261,7 @@ export const domainRouter = createTRPCRouter({
});
if (domain.applicationId) {
const application = await findApplicationById(domain.applicationId);
- await manageDomain(application, domain);
+ await manageWebServerDomain(application, domain);
} else if (domain.previewDeploymentId) {
const previewDeployment = await findPreviewDeploymentById(
domain.previewDeploymentId,
@@ -137,7 +270,7 @@ export const domainRouter = createTRPCRouter({
previewDeployment.applicationId,
);
application.appName = previewDeployment.appName;
- await manageDomain(application, domain);
+ await manageWebServerDomain(application, domain);
}
return result;
}),
@@ -176,6 +309,86 @@ export const domainRouter = createTRPCRouter({
});
}
+ if (domain.applicationId) {
+ const application = await findApplicationById(domain.applicationId);
+ if (
+ (await resolveWebServerProvider(application.serverId)) === "caddy"
+ ) {
+ await removeWebServerDomain(application, domain.uniqueConfigKey);
+ try {
+ const result = await removeDomainById(input.domainId);
+ await audit(ctx, {
+ action: "delete",
+ resourceType: "domain",
+ resourceId: domain.domainId,
+ resourceName: domain.host,
+ });
+ return result;
+ } catch (error) {
+ await manageWebServerDomain(application, domain);
+ throw error;
+ }
+ }
+ } else if (domain.previewDeploymentId) {
+ const previewDeployment = await findPreviewDeploymentById(
+ domain.previewDeploymentId,
+ );
+ const application = await findApplicationById(
+ previewDeployment.applicationId,
+ );
+ application.appName = previewDeployment.appName;
+ if (
+ (await resolveWebServerProvider(application.serverId)) === "caddy"
+ ) {
+ await removeWebServerDomain(application, domain.uniqueConfigKey);
+ try {
+ const result = await removeDomainById(input.domainId);
+ await audit(ctx, {
+ action: "delete",
+ resourceType: "domain",
+ resourceId: domain.domainId,
+ resourceName: domain.host,
+ });
+ return result;
+ } catch (error) {
+ await manageWebServerDomain(application, domain);
+ throw error;
+ }
+ }
+ } else if (domain.composeId) {
+ const compose = await findComposeById(domain.composeId);
+ if ((await resolveWebServerProvider(compose.serverId)) === "caddy") {
+ const domains = await findDomainsByComposeId(compose.composeId);
+ const remainingDomains = domains.filter(
+ (item) => item.domainId !== domain.domainId,
+ );
+ try {
+ await refreshCaddyComposeRoutes(
+ compose,
+ remainingDomains,
+ "caddy",
+ ctx.session.activeOrganizationId,
+ );
+ const result = await removeDomainById(input.domainId);
+ await audit(ctx, {
+ action: "delete",
+ resourceType: "domain",
+ resourceId: domain.domainId,
+ resourceName: domain.host,
+ });
+ return result;
+ } catch (error) {
+ await refreshCaddyComposeRoutes(
+ compose,
+ undefined,
+ "caddy",
+ ctx.session.activeOrganizationId,
+ );
+ throw error;
+ }
+ }
+ }
+
const result = await removeDomainById(input.domainId);
await audit(ctx, {
action: "delete",
@@ -186,7 +399,16 @@ export const domainRouter = createTRPCRouter({
if (domain.applicationId) {
const application = await findApplicationById(domain.applicationId);
- await removeDomain(application, domain.uniqueConfigKey);
+ await removeWebServerDomain(application, domain.uniqueConfigKey);
+ } else if (domain.previewDeploymentId) {
+ const previewDeployment = await findPreviewDeploymentById(
+ domain.previewDeploymentId,
+ );
+ const application = await findApplicationById(
+ previewDeployment.applicationId,
+ );
+ application.appName = previewDeployment.appName;
+ await removeWebServerDomain(application, domain.uniqueConfigKey);
}
return result;
diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts
index 2e35aee2a1..47af6cde03 100644
--- a/apps/dokploy/server/api/routers/project.ts
+++ b/apps/dokploy/server/api/routers/project.ts
@@ -2,6 +2,7 @@ import {
createApplication,
createBackup,
createCompose,
+ createComposeDomain,
createDomain,
createLibsql,
createMariadb,
@@ -977,11 +978,16 @@ export const projectRouter = createTRPCRouter({
for (const domain of domains) {
const { domainId, ...rest } = domain;
- await createDomain({
- ...rest,
- composeId: newCompose.composeId,
- domainType: "compose",
- });
+ await createComposeDomain(
+ newCompose,
+ {
+ ...rest,
+ composeId: newCompose.composeId,
+ domainType: "compose",
+ },
+ undefined,
+ ctx.session.activeOrganizationId,
+ );
}
break;
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index aff6650f17..3a79b3065b 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -1,10 +1,14 @@
+import { createReadStream, existsSync } from "node:fs";
+import { createInterface } from "node:readline";
import {
+ ACCESS_LOG_RETAINED_LINES,
+ applyCaddyMigration as applyCaddyMigrationCutover,
CLEANUP_CRON_JOB,
checkGPUStatus,
checkPortInUse,
checkPostgresHealth,
checkRedisHealth,
- checkTraefikHealth,
+ checkWebServerHealth,
cleanupAll,
cleanupAllBackground,
cleanupBuilders,
@@ -12,19 +16,29 @@ import {
cleanupImages,
cleanupSystem,
cleanupVolumes,
+ compileWriteAndReloadCaddyConfigSafely,
+ compileWriteAndValidateCaddyConfigSafely,
DEFAULT_UPDATE_DATA,
execAsync,
findServerById,
+ getCaddyCompileSettings,
+ getCaddyTrustedProxySettings,
getDockerDiskUsage,
getDokployImageTag,
getLogCleanupStatus,
getUpdateData,
+ getWebServerPaths,
+ getWebServerResourceName,
getWebServerSettings,
IS_CLOUD,
+ isCaddyAdminAdditionalPort,
parseRawConfig,
paths,
+ prepareCaddyMigration as prepareCaddyMigrationDryRun,
prepareEnvironmentVariables,
processLogs,
+ readCaddyConfigFileIfExists,
+ getCaddyMigrationReport as readCaddyMigrationReport,
readConfig,
readConfigInPath,
readDirectory,
@@ -33,20 +47,29 @@ import {
readMonitoringConfig,
readPorts,
recreateDirectory,
+ reloadCaddyAfterValidation,
reloadDockerResource,
+ resolveWebServerProvider,
+ rollbackCaddyMigration as rollbackCaddyMigrationCutover,
sendDockerCleanupNotifications,
setupGPUSupport,
spawnAsync,
startLogCleanup,
stopLogCleanup,
+ updateCaddyTrustedProxySettings,
updateLetsEncryptEmail,
+ updateLocalWebServerProvider,
+ updateRemoteWebServerProvider,
updateServerById,
+ updateServerCaddy,
updateServerTraefik,
updateWebServerSettings,
+ type WebServerProvider,
writeConfig,
writeMainConfig,
writeTraefikConfigInPath,
writeTraefikSetup,
+ writeWebServerSetup,
} from "@dokploy/server";
import { db } from "@dokploy/server/db";
import { checkPermission } from "@dokploy/server/services/permission";
@@ -82,6 +105,349 @@ import {
publicProcedure,
} from "../trpc";
+const webServerProviderSchema = z.enum(["traefik", "caddy"]);
+
+const apiWebServerProvider = z.object({
+ provider: webServerProviderSchema,
+ serverId: z.string().optional(),
+});
+
+const apiWebServerConfig = z.object({
+ webServerConfig: z.string().min(1),
+ serverId: z.string().optional(),
+});
+
+const apiReadWebServerFile = z.object({
+ path: z.string().min(1),
+ serverId: z.string().optional(),
+});
+
+const apiModifyWebServerFile = apiReadWebServerFile.extend({
+ webServerConfig: z.string().min(1),
+});
+
+const apiWriteWebServerEnv = z.object({
+ env: z.string(),
+ serverId: z.string().optional(),
+});
+
+const apiWebServerPorts = z.object({
+ serverId: z.string().optional(),
+ additionalPorts: z.array(
+ z.object({
+ targetPort: z.number(),
+ publishedPort: z.number(),
+ protocol: z.enum(["tcp", "udp", "sctp"]),
+ }),
+ ),
+});
+
+const apiCaddyTrustedProxySettings = z.object({
+ serverId: z.string().optional(),
+ mode: z.enum(["disabled", "cloudflare", "static"]),
+ ranges: z.array(z.string()).optional().nullable(),
+ clientIpHeaders: z.array(z.string()).optional().nullable(),
+ strict: z.boolean().optional().nullable(),
+});
+
+const apiCaddyMigration = z.object({
+ serverId: z.string().optional(),
+});
+
+const apiCaddyMigrationById = apiCaddyMigration.extend({
+ migrationId: z.string().min(1),
+});
+
+const apiApplyCaddyMigration = apiCaddyMigrationById.extend({
+ confirmMaintenanceWindow: z.literal(true),
+});
+
+const ensureServerAccess = async (
+ ctx: { session?: { activeOrganizationId?: string | null } | null },
+ serverId?: string,
+) => {
+ if (!serverId) return;
+ const remoteServer = await findServerById(serverId);
+ if (remoteServer.organizationId !== ctx.session?.activeOrganizationId) {
+ throw new TRPCError({ code: "UNAUTHORIZED" });
+ }
+};
+
+const normalizeWebServerPath = (filePath: string) =>
+ filePath.replace(/\\/g, "/").replace(/\/+$/g, "");
+
+const isPathWithin = (targetPath: string, basePath: string) => {
+ const normalizedTarget = normalizeWebServerPath(targetPath);
+ const normalizedBase = normalizeWebServerPath(basePath);
+ return (
+ normalizedTarget === normalizedBase ||
+ normalizedTarget.startsWith(`${normalizedBase}/`)
+ );
+};
+
+const isCaddyMigrationBackupPath = (filePath: string, serverId?: string) => {
+ const caddyPaths = paths(!!serverId);
+ const normalizedPath = normalizeWebServerPath(filePath);
+ const migrationsPath = normalizeWebServerPath(
+ caddyPaths.CADDY_MIGRATIONS_PATH,
+ );
+ if (!isPathWithin(normalizedPath, migrationsPath)) {
+ return false;
+ }
+ return normalizedPath
+ .slice(migrationsPath.length)
+ .split("/")
+ .filter(Boolean)
+ .includes("backups");
+};
+
+const assertCaddyReadableFilePath = (filePath: string, serverId?: string) => {
+ const caddyPaths = paths(!!serverId);
+ const normalizedPath = normalizeWebServerPath(filePath);
+ if (isCaddyMigrationBackupPath(normalizedPath, serverId)) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Caddy migration backups may contain TLS or internal configuration and are not readable from the file editor.",
+ });
+ }
+ if (
+ normalizedPath === normalizeWebServerPath(caddyPaths.CADDY_CONFIG_PATH) ||
+ isPathWithin(normalizedPath, caddyPaths.CADDY_FRAGMENTS_PATH) ||
+ isPathWithin(normalizedPath, caddyPaths.CADDY_MIGRATIONS_PATH)
+ ) {
+ return;
+ }
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Caddy file access is limited to caddy.json, route fragments, and non-backup migration artifacts.",
+ });
+};
+
+type WebServerDirectoryNode = {
+ id: string;
+ name: string;
+ type: "file" | "directory";
+ children?: WebServerDirectoryNode[];
+};
+
+const pruneCaddyBackupDirectoryNodes = (
+ nodes: WebServerDirectoryNode[],
+ serverId?: string,
+): WebServerDirectoryNode[] =>
+ nodes
+ .filter((node) => !isCaddyMigrationBackupPath(node.id, serverId))
+ .map((node) =>
+ node.children
+ ? {
+ ...node,
+ children: pruneCaddyBackupDirectoryNodes(node.children, serverId),
+ }
+ : node,
+ );
+
+const readCaddySafeDirectoryTree = async (serverId?: string) => {
+ const caddyPaths = paths(!!serverId);
+ const readOptionalDirectory = async (dirPath: string) => {
+ try {
+ return await readDirectory(dirPath, serverId);
+ } catch (error) {
+ if (
+ error instanceof Error &&
+ (error as NodeJS.ErrnoException).code === "ENOENT"
+ ) {
+ return [];
+ }
+ throw error;
+ }
+ };
+ return [
+ {
+ id: caddyPaths.CADDY_CONFIG_PATH,
+ name: "caddy.json",
+ type: "file" as const,
+ },
+ {
+ id: caddyPaths.CADDY_FRAGMENTS_PATH,
+ name: "fragments",
+ type: "directory" as const,
+ children: await readOptionalDirectory(caddyPaths.CADDY_FRAGMENTS_PATH),
+ },
+ {
+ id: caddyPaths.CADDY_MIGRATIONS_PATH,
+ name: "migrations",
+ type: "directory" as const,
+ children: pruneCaddyBackupDirectoryNodes(
+ await readOptionalDirectory(caddyPaths.CADDY_MIGRATIONS_PATH),
+ serverId,
+ ),
+ },
+ ];
+};
+
+const resolveWebServerFilePath = (
+ filePath: string,
+ provider: WebServerProvider,
+ serverId?: string,
+) => {
+ if (
+ filePath.includes("../") ||
+ filePath.includes("..\\") ||
+ filePath.includes("\0") ||
+ filePath.includes("\x00")
+ ) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Invalid path: path traversal or null bytes are not allowed",
+ });
+ }
+
+ const basePath = getWebServerPaths(provider, !!serverId).basePath;
+ if (filePath.startsWith("/")) {
+ if (filePath !== basePath && !filePath.startsWith(`${basePath}/`)) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Invalid path: outside of active web server directory",
+ });
+ }
+ return filePath;
+ }
+
+ return `${basePath}/${filePath.replace(/^\/+/, "")}`;
+};
+
+const getMainTraefikConfigPath = (serverId?: string) =>
+ `${paths(!!serverId).MAIN_TRAEFIK_PATH}/traefik.yml`;
+
+const readProviderMainConfig = async (
+ provider: WebServerProvider,
+ serverId?: string,
+) => {
+ if (provider === "caddy") {
+ return readCaddyConfigFileIfExists({ serverId });
+ }
+
+ if (serverId) {
+ return readConfigInPath(getMainTraefikConfigPath(serverId), serverId);
+ }
+ return readMainConfig();
+};
+
+const writeProviderMainConfig = async (
+ provider: WebServerProvider,
+ content: string,
+ serverId?: string,
+) => {
+ if (provider === "caddy") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Caddy caddy.json is generated from route fragments and is read-only. Use Caddy migration/domain actions to update it.",
+ });
+ }
+
+ if (serverId) {
+ await writeTraefikConfigInPath(
+ getMainTraefikConfigPath(serverId),
+ content,
+ serverId,
+ );
+ return;
+ }
+ writeMainConfig(content);
+};
+
+const reloadWebServerProvider = async (
+ provider: WebServerProvider,
+ serverId?: string,
+) => {
+ if (provider === "caddy") {
+ await reloadCaddyAfterValidation(serverId);
+ return;
+ }
+ await reloadDockerResource(getWebServerResourceName(provider), serverId);
+};
+
+const getCaddySetupOptions = async (
+ provider: WebServerProvider,
+ serverId?: string,
+) => {
+ if (provider !== "caddy") return {};
+ return getCaddyCompileSettings(serverId);
+};
+
+const getLocalAccessLogPath = (provider: WebServerProvider) => {
+ const currentPaths = paths();
+ return provider === "caddy"
+ ? currentPaths.CADDY_ACCESS_LOG_PATH
+ : `${currentPaths.DYNAMIC_TRAEFIK_PATH}/access.log`;
+};
+
+const readAccessLogFile = async (filePath: string) => {
+ if (!existsSync(filePath)) {
+ return "";
+ }
+
+ const recentLines: string[] = [];
+ const fileStream = createReadStream(filePath, { encoding: "utf8" });
+ const readline = createInterface({
+ input: fileStream,
+ crlfDelay: Number.POSITIVE_INFINITY,
+ });
+ for await (const line of readline) {
+ const trimmed = line.trim();
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
+ recentLines.push(line);
+ if (recentLines.length > ACCESS_LOG_RETAINED_LINES) {
+ recentLines.shift();
+ }
+ }
+ }
+ return recentLines.length ? `${recentLines.join("\n")}\n` : "";
+};
+
+const readActiveRequestAccessLog = async (readAll = false) => {
+ const provider = await resolveWebServerProvider();
+ if (provider === "traefik") {
+ return (await readMonitoringConfig(readAll)) ?? "";
+ }
+ return readAccessLogFile(getLocalAccessLogPath(provider));
+};
+
+const getRequestAnalyticsState = async () => {
+ if (IS_CLOUD) {
+ return {
+ provider: "traefik" as const,
+ enabled: true,
+ reloadResourceName: "dokploy-traefik",
+ accessLogPath: getLocalAccessLogPath("traefik"),
+ };
+ }
+
+ const provider = await resolveWebServerProvider();
+ if (provider === "caddy") {
+ const settings = await getWebServerSettings();
+ return {
+ provider,
+ enabled: Boolean(settings?.requestLogsEnabled),
+ reloadResourceName: getWebServerResourceName(provider),
+ accessLogPath: getLocalAccessLogPath(provider),
+ };
+ }
+
+ const config = readMainConfig();
+ const parsedConfig = config
+ ? (parse(config) as { accessLog?: { filePath?: string } })
+ : null;
+ return {
+ provider,
+ enabled: Boolean(parsedConfig?.accessLog?.filePath),
+ reloadResourceName: getWebServerResourceName(provider),
+ accessLogPath: getLocalAccessLogPath(provider),
+ };
+};
+
export const settingsRouter = createTRPCRouter({
getWebServerSettings: protectedProcedure.query(async () => {
if (IS_CLOUD) {
@@ -165,9 +531,223 @@ export const settingsRouter = createTRPCRouter({
});
return true;
}),
+ getActiveWebServerProvider: adminProcedure
+ .input(apiServerSchema)
+ .query(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input?.serverId);
+ return resolveWebServerProvider(input?.serverId);
+ }),
+ getCaddyTrustedProxySettings: adminProcedure
+ .input(apiServerSchema)
+ .query(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input?.serverId);
+ if (IS_CLOUD && !input?.serverId) {
+ return {
+ mode: "disabled" as const,
+ ranges: [],
+ clientIpHeaders: [],
+ strict: true,
+ };
+ }
+
+ const settings = await getCaddyTrustedProxySettings(input?.serverId);
+ return (
+ settings ?? {
+ mode: "disabled" as const,
+ ranges: [],
+ clientIpHeaders: [],
+ strict: true,
+ }
+ );
+ }),
+ updateCaddyTrustedProxySettings: adminProcedure
+ .input(apiCaddyTrustedProxySettings)
+ .mutation(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input.serverId);
+ if (IS_CLOUD && !input.serverId) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Caddy trusted proxy settings are only available for a local or remote web server.",
+ });
+ }
+
+ const previousSettings = await getCaddyTrustedProxySettings(
+ input.serverId,
+ );
+ const nextSettings =
+ input.mode === "disabled"
+ ? null
+ : {
+ mode: input.mode,
+ ranges: input.ranges,
+ clientIpHeaders: input.clientIpHeaders,
+ strict: input.strict,
+ };
+
+ const provider = await resolveWebServerProvider(input.serverId);
+ await updateCaddyTrustedProxySettings(nextSettings, input.serverId);
+ try {
+ if (provider === "caddy") {
+ await compileWriteAndReloadCaddyConfigSafely({
+ serverId: input.serverId,
+ ...(await getCaddyCompileSettings(input.serverId)),
+ });
+ }
+ } catch (error) {
+ await updateCaddyTrustedProxySettings(previousSettings, input.serverId);
+ throw error;
+ }
+
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "caddy-trusted-proxy",
+ });
+ return (
+ (await getCaddyTrustedProxySettings(input.serverId)) ?? {
+ mode: "disabled" as const,
+ ranges: [],
+ clientIpHeaders: [],
+ strict: true,
+ }
+ );
+ }),
+ updateActiveWebServerProvider: adminProcedure
+ .input(apiWebServerProvider)
+ .mutation(async ({ input, ctx }) => {
+ if (input.provider === "caddy") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Caddy can only be activated through the migration apply flow after validation succeeds.",
+ });
+ }
+ if (input.serverId) {
+ await ensureServerAccess(ctx, input.serverId);
+ }
+ const currentProvider = await resolveWebServerProvider(input.serverId);
+ if (currentProvider === "caddy" && input.provider === "traefik") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Use the Caddy migration rollback flow to return to Traefik safely.",
+ });
+ }
+ if (input.serverId) {
+ await updateRemoteWebServerProvider(input.serverId, input.provider);
+ } else {
+ await updateLocalWebServerProvider(input.provider);
+ }
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "web-server-provider",
+ });
+ return true;
+ }),
+ reloadWebServer: adminProcedure
+ .input(apiServerSchema)
+ .mutation(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input?.serverId);
+ const provider = await resolveWebServerProvider(input?.serverId);
+ void reloadWebServerProvider(provider, input?.serverId).catch((err) => {
+ console.error("reloadWebServer background:", err);
+ });
+ await audit(ctx, {
+ action: "reload",
+ resourceType: "settings",
+ resourceName: getWebServerResourceName(provider),
+ });
+ return true;
+ }),
+ prepareCaddyMigration: adminProcedure
+ .input(apiCaddyMigration)
+ .mutation(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input.serverId);
+ const report = await prepareCaddyMigrationDryRun({
+ serverId: input.serverId,
+ });
+ await audit(ctx, {
+ action: "create",
+ resourceType: "settings",
+ resourceName: "caddy-migration-dry-run",
+ });
+ return report;
+ }),
+ getCaddyMigrationReport: adminProcedure
+ .input(apiCaddyMigrationById)
+ .query(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input.serverId);
+ return readCaddyMigrationReport({
+ migrationId: input.migrationId,
+ serverId: input.serverId,
+ });
+ }),
+ applyCaddyMigration: adminProcedure
+ .input(apiApplyCaddyMigration)
+ .mutation(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input.serverId);
+ const report = await readCaddyMigrationReport({
+ migrationId: input.migrationId,
+ serverId: input.serverId,
+ });
+ if (report.summary.blockingWarnings > 0) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `Cannot apply Caddy migration with ${report.summary.blockingWarnings} blocking warning(s)`,
+ });
+ }
+ if (report.validation.status !== "passed") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Cannot apply Caddy migration because draft validation did not pass",
+ });
+ }
+ void applyCaddyMigrationCutover({
+ migrationId: input.migrationId,
+ serverId: input.serverId,
+ }).catch((err) => {
+ console.error("applyCaddyMigration background:", err);
+ });
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "caddy-migration-apply",
+ });
+ return { started: true, migrationId: input.migrationId };
+ }),
+ rollbackCaddyMigration: adminProcedure
+ .input(apiCaddyMigrationById)
+ .mutation(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input.serverId);
+ void rollbackCaddyMigrationCutover({
+ migrationId: input.migrationId,
+ serverId: input.serverId,
+ }).catch((err) => {
+ console.error("rollbackCaddyMigration background:", err);
+ });
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "caddy-migration-rollback",
+ });
+ return { started: true, migrationId: input.migrationId };
+ }),
toggleDashboard: adminProcedure
.input(apiEnableDashboard)
.mutation(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input.serverId);
+ const provider = await resolveWebServerProvider(input.serverId);
+ if (provider !== "traefik") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "The dashboard toggle is only available for Traefik. The Caddy admin API is kept local-only and is not exposed through Dokploy.",
+ });
+ }
+
const ports = await readPorts("dokploy-traefik", input.serverId);
const env = await readEnvironmentVariables(
"dokploy-traefik",
@@ -321,6 +901,14 @@ export const settingsRouter = createTRPCRouter({
if (IS_CLOUD) {
return true;
}
+ const previousSettings = await getWebServerSettings();
+ if (!previousSettings) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Web server settings not found",
+ });
+ }
+
const settings = await updateWebServerSettings({
host: input.host,
letsEncryptEmail: input.letsEncryptEmail,
@@ -335,9 +923,24 @@ export const settingsRouter = createTRPCRouter({
});
}
- updateServerTraefik(settings, input.host);
- if (input.letsEncryptEmail) {
- updateLetsEncryptEmail(input.letsEncryptEmail);
+ const provider = await resolveWebServerProvider();
+ if (provider === "caddy") {
+ try {
+ await updateServerCaddy(settings, input.host);
+ } catch (error) {
+ await updateWebServerSettings({
+ host: previousSettings.host,
+ letsEncryptEmail: previousSettings.letsEncryptEmail,
+ certificateType: previousSettings.certificateType,
+ https: previousSettings.https,
+ });
+ throw error;
+ }
+ } else {
+ updateServerTraefik(settings, input.host);
+ if (input.letsEncryptEmail) {
+ updateLetsEncryptEmail(input.letsEncryptEmail);
+ }
}
await audit(ctx, {
@@ -513,6 +1116,38 @@ export const settingsRouter = createTRPCRouter({
return true;
}),
+ readWebServerConfig: adminProcedure
+ .input(apiServerSchema)
+ .query(async ({ input, ctx }) => {
+ if (IS_CLOUD && !input?.serverId) {
+ return true;
+ }
+ await ensureServerAccess(ctx, input?.serverId);
+ const provider = await resolveWebServerProvider(input?.serverId);
+ return readProviderMainConfig(provider, input?.serverId);
+ }),
+
+ updateWebServerConfig: adminProcedure
+ .input(apiWebServerConfig)
+ .mutation(async ({ input, ctx }) => {
+ if (IS_CLOUD && !input.serverId) {
+ return true;
+ }
+ await ensureServerAccess(ctx, input.serverId);
+ const provider = await resolveWebServerProvider(input.serverId);
+ await writeProviderMainConfig(
+ provider,
+ input.webServerConfig,
+ input.serverId,
+ );
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "web-server-config",
+ });
+ return true;
+ }),
+
readWebServerTraefikConfig: adminProcedure.query(() => {
if (IS_CLOUD) {
return true;
@@ -608,6 +1243,30 @@ export const settingsRouter = createTRPCRouter({
}
}),
+ readWebServerDirectories: protectedProcedure
+ .input(apiServerSchema)
+ .query(async ({ ctx, input }) => {
+ await checkPermission(ctx, { traefikFiles: ["read"] });
+ await ensureServerAccess(ctx, input?.serverId);
+ const provider = await resolveWebServerProvider(input?.serverId);
+ if (provider === "caddy") {
+ return readCaddySafeDirectoryTree(input?.serverId);
+ }
+ const { basePath } = getWebServerPaths(provider, !!input?.serverId);
+ try {
+ const result = await readDirectory(basePath, input?.serverId);
+ return result || [];
+ } catch (error) {
+ if (
+ error instanceof Error &&
+ (error as NodeJS.ErrnoException).code === "ENOENT"
+ ) {
+ return [];
+ }
+ throw error;
+ }
+ }),
+
updateTraefikFile: protectedProcedure
.input(apiModifyTraefikConfig)
.mutation(async ({ input, ctx }) => {
@@ -640,6 +1299,53 @@ export const settingsRouter = createTRPCRouter({
return readConfigInPath(input.path, input.serverId);
}),
+ updateWebServerFile: protectedProcedure
+ .input(apiModifyWebServerFile)
+ .mutation(async ({ input, ctx }) => {
+ await checkPermission(ctx, { traefikFiles: ["write"] });
+ await ensureServerAccess(ctx, input.serverId);
+ const provider = await resolveWebServerProvider(input.serverId);
+ if (provider === "caddy") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Caddy generated config files are read-only from the file editor.",
+ });
+ }
+ const filePath = resolveWebServerFilePath(
+ input.path,
+ provider,
+ input.serverId,
+ );
+ await writeTraefikConfigInPath(
+ filePath,
+ input.webServerConfig,
+ input.serverId,
+ );
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "web-server-file",
+ });
+ return true;
+ }),
+
+ readWebServerFile: protectedProcedure
+ .input(apiReadWebServerFile)
+ .query(async ({ input, ctx }) => {
+ await checkPermission(ctx, { traefikFiles: ["read"] });
+ await ensureServerAccess(ctx, input.serverId);
+ const provider = await resolveWebServerProvider(input.serverId);
+ const filePath = resolveWebServerFilePath(
+ input.path,
+ provider,
+ input.serverId,
+ );
+ if (provider === "caddy") {
+ assertCaddyReadableFilePath(filePath, input.serverId);
+ }
+ return readConfigInPath(filePath, input.serverId);
+ }),
getIp: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return "";
@@ -761,6 +1467,16 @@ export const settingsRouter = createTRPCRouter({
);
return envVars;
}),
+ readWebServerEnv: adminProcedure
+ .input(apiServerSchema)
+ .query(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input?.serverId);
+ const provider = await resolveWebServerProvider(input?.serverId);
+ return readEnvironmentVariables(
+ getWebServerResourceName(provider),
+ input?.serverId,
+ );
+ }),
writeTraefikEnv: adminProcedure
.input(z.object({ env: z.string(), serverId: z.string().optional() }))
@@ -783,13 +1499,55 @@ export const settingsRouter = createTRPCRouter({
});
return true;
}),
+ writeWebServerEnv: adminProcedure
+ .input(apiWriteWebServerEnv)
+ .mutation(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input.serverId);
+ const provider = await resolveWebServerProvider(input.serverId);
+ const resourceName = getWebServerResourceName(provider);
+ const envs = prepareEnvironmentVariables(input.env);
+ const ports = await readPorts(resourceName, input.serverId);
+
+ void writeWebServerSetup(provider, {
+ env: envs,
+ additionalPorts: ports,
+ serverId: input.serverId,
+ ...(await getCaddySetupOptions(provider, input.serverId)),
+ }).catch((err) => {
+ console.error("writeWebServerEnv background writeWebServerSetup:", err);
+ });
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "web-server-env",
+ });
+ return true;
+ }),
haveTraefikDashboardPortEnabled: adminProcedure
.input(apiServerSchema)
.query(async ({ input }) => {
const ports = await readPorts("dokploy-traefik", input?.serverId);
return ports.some((port) => port.targetPort === 8080);
}),
+ getWebServerDashboardState: adminProcedure
+ .input(apiServerSchema)
+ .query(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input?.serverId);
+ const provider = await resolveWebServerProvider(input?.serverId);
+ if (provider === "traefik") {
+ const ports = await readPorts("dokploy-traefik", input?.serverId);
+ return {
+ provider,
+ enabled: ports.some((port) => port.targetPort === 8080),
+ };
+ }
+
+ return { provider, enabled: false };
+ }),
+ getRequestAnalyticsState: protectedProcedure.query(async () => {
+ return getRequestAnalyticsState();
+ }),
readStatsLogs: protectedProcedure
.meta({
openapi: {
@@ -807,7 +1565,7 @@ export const settingsRouter = createTRPCRouter({
totalCount: 0,
};
}
- const rawConfig = await readMonitoringConfig(
+ const rawConfig = await readActiveRequestAccessLog(
!!input.dateRange?.start && !!input.dateRange?.end,
);
@@ -847,26 +1605,14 @@ export const settingsRouter = createTRPCRouter({
if (IS_CLOUD) {
return [];
}
- const rawConfig = await readMonitoringConfig(
+ const rawConfig = await readActiveRequestAccessLog(
!!input?.dateRange?.start || !!input?.dateRange?.end,
);
const processedLogs = processLogs(rawConfig as string, input?.dateRange);
return processedLogs || [];
}),
haveActivateRequests: protectedProcedure.query(async () => {
- if (IS_CLOUD) {
- return true;
- }
- const config = readMainConfig();
-
- if (!config) return false;
- const parsedConfig = parse(config) as {
- accessLog?: {
- filePath: string;
- };
- };
-
- return !!parsedConfig?.accessLog?.filePath;
+ return (await getRequestAnalyticsState()).enabled;
}),
toggleRequests: protectedProcedure
.input(
@@ -878,6 +1624,31 @@ export const settingsRouter = createTRPCRouter({
if (IS_CLOUD) {
return true;
}
+ const provider = await resolveWebServerProvider();
+ if (provider === "caddy") {
+ const previousSettings = await getWebServerSettings();
+ const previousEnabled = Boolean(previousSettings?.requestLogsEnabled);
+ await updateWebServerSettings({
+ requestLogsEnabled: input.enable,
+ });
+ try {
+ await compileWriteAndValidateCaddyConfigSafely(
+ await getCaddyCompileSettings(),
+ );
+ } catch (error) {
+ await updateWebServerSettings({
+ requestLogsEnabled: previousEnabled,
+ });
+ throw error;
+ }
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "toggle-requests",
+ });
+ return true;
+ }
+
const mainConfig = readMainConfig();
if (!mainConfig) return false;
@@ -933,21 +1704,29 @@ export const settingsRouter = createTRPCRouter({
}
}),
checkInfrastructureHealth: adminProcedure.query(async () => {
+ const provider = await resolveWebServerProvider();
if (IS_CLOUD) {
+ const webServer = { provider, status: "healthy" as const };
return {
postgres: { status: "healthy" as const },
redis: { status: "healthy" as const },
- traefik: { status: "healthy" as const },
+ webServer,
+ traefik: { status: webServer.status },
};
}
- const [postgres, redis, traefik] = await Promise.all([
+ const [postgres, redis, webServer] = await Promise.all([
checkPostgresHealth(),
checkRedisHealth(),
- checkTraefikHealth(),
+ checkWebServerHealth(provider),
]);
- return { postgres, redis, traefik };
+ return {
+ postgres,
+ redis,
+ webServer,
+ traefik: { status: webServer.status, message: webServer.message },
+ };
}),
setupGPU: adminProcedure
.input(
@@ -1081,6 +1860,85 @@ export const settingsRouter = createTRPCRouter({
const ports = await readPorts("dokploy-traefik", input?.serverId);
return ports;
}),
+ updateWebServerPorts: adminProcedure
+ .input(apiWebServerPorts)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ await ensureServerAccess(ctx, input.serverId);
+ if (IS_CLOUD && !input.serverId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Please set a serverId to update web server ports",
+ });
+ }
+ const provider = await resolveWebServerProvider(input.serverId);
+ const resourceName = getWebServerResourceName(provider);
+ const env = await readEnvironmentVariables(
+ resourceName,
+ input.serverId,
+ );
+
+ if (provider === "caddy") {
+ const reservedPort = input.additionalPorts.find(
+ isCaddyAdminAdditionalPort,
+ );
+ if (reservedPort) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `Caddy target port ${reservedPort.targetPort}/${reservedPort.protocol ?? "tcp"} is reserved and cannot be published.`,
+ });
+ }
+ }
+
+ for (const port of input.additionalPorts) {
+ const portCheck = await checkPortInUse(
+ port.publishedPort,
+ input.serverId,
+ );
+ if (portCheck.isInUse) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: `Port ${port.publishedPort} is already in use by ${portCheck.conflictingContainer}`,
+ });
+ }
+ }
+ const preparedEnv = prepareEnvironmentVariables(env);
+
+ void writeWebServerSetup(provider, {
+ env: preparedEnv,
+ additionalPorts: input.additionalPorts,
+ serverId: input.serverId,
+ ...(await getCaddySetupOptions(provider, input.serverId)),
+ }).catch((err) => {
+ console.error(
+ "updateWebServerPorts background writeWebServerSetup:",
+ err,
+ );
+ });
+ await audit(ctx, {
+ action: "update",
+ resourceType: "settings",
+ resourceName: "web-server-ports",
+ });
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ error instanceof Error
+ ? error.message
+ : "Error updating web server ports",
+ cause: error,
+ });
+ }
+ }),
+ getWebServerPorts: adminProcedure
+ .input(apiServerSchema)
+ .query(async ({ input, ctx }) => {
+ await ensureServerAccess(ctx, input?.serverId);
+ const provider = await resolveWebServerProvider(input?.serverId);
+ return readPorts(getWebServerResourceName(provider), input?.serverId);
+ }),
updateLogCleanup: protectedProcedure
.input(
z.object({
diff --git a/apps/dokploy/server/db/migration.ts b/apps/dokploy/server/db/migration.ts
index 8a24afdc50..da381cfea4 100644
--- a/apps/dokploy/server/db/migration.ts
+++ b/apps/dokploy/server/db/migration.ts
@@ -1,20 +1,5 @@
-import { dbUrl } from "@dokploy/server/db";
-import { drizzle } from "drizzle-orm/postgres-js";
-import { migrate } from "drizzle-orm/postgres-js/migrator";
-import postgres from "postgres";
+import { runRuntimeMigrations } from "./run-migrations";
-const sql = postgres(dbUrl, { max: 1 });
-const db = drizzle(sql);
-
-export const migration = async () =>
- await migrate(db, { migrationsFolder: "drizzle" })
- .then(() => {
- console.log("Migration complete");
- sql.end();
- })
- .catch((error) => {
- console.log("Migration failed", error);
- })
- .finally(() => {
- sql.end();
- });
+export const migration = async () => {
+ await runRuntimeMigrations();
+};
diff --git a/apps/dokploy/server/db/run-migrations.ts b/apps/dokploy/server/db/run-migrations.ts
new file mode 100644
index 0000000000..202c5fbf24
--- /dev/null
+++ b/apps/dokploy/server/db/run-migrations.ts
@@ -0,0 +1,29 @@
+import { dbUrl } from "@dokploy/server/db/constants";
+import { drizzle } from "drizzle-orm/postgres-js";
+import { migrate } from "drizzle-orm/postgres-js/migrator";
+import postgres from "postgres";
+
+export type RuntimeMigrationLogger = Pick;
+
+export type RunRuntimeMigrationsOptions = {
+ logger?: RuntimeMigrationLogger;
+ migrationsFolder?: string;
+};
+
+export const runRuntimeMigrations = async ({
+ logger = console,
+ migrationsFolder = "drizzle",
+}: RunRuntimeMigrationsOptions = {}) => {
+ const sql = postgres(dbUrl, { max: 1 });
+
+ try {
+ const db = drizzle(sql);
+ await migrate(db, { migrationsFolder });
+ logger.log("Migration complete");
+ } catch (error) {
+ logger.error("Migration failed", error);
+ throw error;
+ } finally {
+ await sql.end();
+ }
+};
diff --git a/docs/caddy-provider-upstream-readiness.md b/docs/caddy-provider-upstream-readiness.md
new file mode 100644
index 0000000000..615094f432
--- /dev/null
+++ b/docs/caddy-provider-upstream-readiness.md
@@ -0,0 +1,509 @@
+# Caddy Provider Upstream Readiness
+
+This document tracks the work needed to turn the Caddy web server provider from a working proof of concept into an upstream-ready Dokploy alternative to Traefik.
+
+PR: https://github.com/Dokploy/dokploy/pull/4534
+
+## Scope and Safety
+
+- The upstream PR should contain generic Dokploy code, tests, and user-facing documentation only.
+- Keep platform-specific route manifests, hostnames, production logs, secrets, and operational evidence outside this repository.
+- Runtime evidence is not part of the upstream PR surface; PR readiness is tracked through source inspection, focused tests, typechecks, formatter checks, and sanitized proof notes.
+- Read-only production checks are allowed for proof gathering.
+- Live apply, rollback, provider switching, service updates, restarts, or config writes require explicit human approval in the current work session.
+
+## Current PR State
+
+Observed with `gh pr view 4534 --repo Dokploy/dokploy`:
+
+- State: open
+- Base: `canary`
+- Base SHA: `6a0acd9cad0a00f2ef09a10124d2d40f57e220f6`
+- Head: `codex/caddy-web-server-v0296-clean-pr`
+- Mergeable: mergeable
+- Review decision: review required
+- Visible checks: none reported immediately after the rebased branch push
+
+Local state observed after the rebased branch push:
+
+- Repo: local Dokploy checkout
+- Branch: `codex/caddy-web-server-v0296-clean-pr`
+- Working tree: clean
+- Upstream base: `upstream/canary`
+- Remote branch: `origin/codex/caddy-web-server-v0296-clean-pr` matched local `HEAD`
+
+## Readiness Matrix
+
+| Area | Current evidence | Status | Next proof needed |
+| --- | --- | --- | --- |
+| Application domain create under Caddy | `createDomain()` inserts the row, dispatches through `manageWebServerDomain()` for application domains, and removes the row if provider route creation fails. Focused service tests prove provider dispatch and cleanup when provider route creation fails. | Locally covered | Add router-level test only if maintainers want end-to-end TRPC proof. |
+| Application domain update under Caddy | `domainRouter.update` writes the next Caddy fragment before DB update and restores the old fragment on DB failure. `manageCaddyDomain()` also restores previous fragments when reload fails. | Locally covered | None before PR update; runtime proof stays external to this PR surface. |
+| Application domain delete under Caddy | `domainRouter.delete` removes provider config before DB deletion, preserves the DB row if Caddy route removal fails before delete, and restores on DB failure. `removeCaddyDomain()` also restores previous fragments when reload fails. | Locally covered | None before PR update; runtime proof stays external to this PR surface. |
+| Compose domain add/update/remove under Caddy | `createComposeDomain()` removes new rows if refresh fails. `removeComposeDomainsForWebServer()` refreshes Caddy with remaining compose domains before non-router delete flows remove rows and restores old fragments if DB deletion fails. `domainRouter.update` restores previous compose domain fields and fragments if refresh fails. `domainRouter.delete` preserves DB rows when route refresh fails before delete and restores all compose routes if DB delete fails. Compose route refresh call sites pass organization context when validating uploaded Caddy certificates. | Locally covered | None before PR update; rerun helper/source-contract tests after edits. |
+| Domain added while creating/duplicating a new application service | `application.create` has no domain payload. The application new-service-with-domain path is project duplication: copied application domains call `createDomain()` with `applicationId`, which invokes provider dispatch. | Locally covered | Add router-level duplication proof only if maintainers want end-to-end TRPC coverage beyond the service contract. |
+| Domain added during new compose/template/AI/import creation | `createComposeDomain()` now creates compose domains and refreshes Caddy compose routes with rollback if refresh fails. Template create, template import, AI compose generation, project compose duplication, and `domainRouter.create` use it with active organization context. Template import also removes prior compose domains through `removeComposeDomainsForWebServer()` so importing a template with zero replacement domains removes stale Caddy fragments. Source-contract tests cover these non-domain-router call sites so they do not regress to raw compose-domain row writes. | Locally covered | None before PR update; rerun helper/source-contract tests after edits. |
+| Preview domains under Caddy | Preview deployment uses `manageWebServerDomain()` for create and requires `removeWebServerDomain()` cleanup before deleting a preview deployment row when a preview domain exists. Focused tests prove the preview app name is set before provider dispatch, cleanup removes by `uniqueConfigKey`, and route cleanup failures preserve the DB row across preview-deployment cleanup and domain-router delete flows. | Locally covered | None before PR update; runtime proof stays external to this PR surface. |
+| Custom SSL certificates | Caddy domains can now select uploaded certificates. Application and compose route intents reference certificate/key files and compiled Caddy JSON emits `apps.tls.certificates.load_files`. Migration dry-runs keep valid uploaded certificates and block missing/cross-org/unreadable references. Backend guards require matching server/org context and readable `chain.crt` plus `privkey.key`; uploaded Caddy certificate validation now fails closed when organization context is missing instead of silently skipping org scoping. Active Caddy domains block certificate file replacement/deletion until the domain is changed. Domain create/update API schemas now enforce the same HTTPS custom-certificate resolver requirement as the frontend and shared validation layer. | Locally covered | None before PR update; provider-neutral schema naming remains a follow-up unless maintainers request it in this PR. |
+| LetsEncrypt / automatic HTTPS | Caddy application and compose fragments set `https` from domain settings and pass local LetsEncrypt email where available. Config and route lifecycle tests cover the generated ACME paths. | Locally covered | None before PR update; runtime proof stays external to this PR surface. |
+| Cloudflare proxy awareness | `compileCaddyConfig()` supports explicit static trusted proxy CIDRs and a Cloudflare preset using static Cloudflare IP ranges plus `CF-Connecting-IP` and `X-Forwarded-For`. Local and remote settings persist the selected mode and Caddy rebuild paths pass it into generated JSON. Default config still trusts no forwarded headers. Strict mode emits a Caddy-compatible boolean. The settings mutation persists without rebuilding under Traefik, rebuilds active local and remote Caddy with settings read after persistence, restores previous DB settings if reload fails, scopes remote rollback to the selected server, and the safe reload helper reloads the restored runtime config while preserving the original failure if the restore attempt also fails. | Locally covered | None before PR update; runtime proof stays external to this PR surface. |
+| Requests / access-log analytics | Requests analytics are provider-aware for the local active web server. Traefik keeps its existing YAML `accessLog.filePath` toggle and log parser behavior. Caddy persists a local `requestLogsEnabled` setting only through the provider-aware toggle flow, validates the rewritten Caddy config before returning success, compiles JSON access logs to `/etc/caddy/access.log`, preserves that setting across Caddy env/port setup rewrites and Dokploy dashboard route rewrites, normalizes Caddy JSON access-log entries into the existing Requests table/chart shape while preserving query strings in `RequestPath`, reads the latest retained local Caddy log lines for paginated and date-range views, rejects invalid cleanup cron schedules before persistence, cleans Caddy logs without signaling Traefik, and marks prepared migration artifacts stale if request-log compile settings change before apply. The current API/UI is local active web-server only; remote-server Requests analytics are a follow-up decision. | Locally covered | None before PR update; runtime proof stays external to this PR surface. |
+| Caddy settings UI | Migration panel, provider selector, Caddy domain certificate labels, Caddy trusted proxy settings, provider-neutral uploaded certificate copy, provider-neutral port-mapping copy, provider-aware update-server health checks, provider-aware Requests copy, provider-neutral web-server file permission copy, provider-neutral local web-server file nav/search/account labels, provider-neutral custom-role file permission labels, and a provider-neutral local web-server environment editor entry point now exist. Application domain, redirect, and security mutation paths invalidate the provider-aware application web-server config cache so the Caddy route-fragment preview does not stay stale. The application Advanced web-server config viewer now uses generic `Web Server` copy while provider resolution is pending and only exposes the legacy Traefik modify dialog after the active provider resolves to Traefik; Caddy previews remain read-only. Remote server file-system inspection now uses active-provider labels and provider-aware endpoints; the local file-system page resolves the active provider when the prop is omitted so Traefik stays editable; Caddy generated artifacts are inspectable through safe read-only paths. Local isolated browser QA covered Settings -> Web Server, Requests, file browser, the Caddy trusted-proxy dialog, and the application domain modal under active Caddy with an uploaded certificate fixture. Source contract tests cover the application domain tab/modal path, active-Caddy provider resolution, Caddy uploaded certificate query/filter/copy, provider-neutral web-server file-browser labels and environment editor endpoints, unresolved-provider application config copy, and hiding Traefik-only controls under active Caddy or unresolved providers. | Locally covered | None before PR update; local browser QA can be repeated if UI files change again. |
+| Caddy dashboard | Caddy admin endpoint is local-only in the current design, `compileCaddyConfig()` binds admin to `localhost:2019`, `getWebServerDashboardState` reports Caddy dashboard disabled instead of exposing the admin API through Dokploy, direct dashboard toggle API calls reject while Caddy is active before reading or rewriting legacy Traefik resources, active Caddy setup filters only reserved `2019/tcp` admin publishing, migration carry-over drops Traefik-only `8080/8082` plus `2019`, and the ports mutation rejects Caddy admin-port targets before rebuild while allowing non-admin custom Caddy ports. Dokploy dashboard route rewrites now preserve local Caddy compile settings and restore previous route fragments if the Caddy reload fails. | Deferred | Prefer no public dashboard in this PR; track any dashboard proxy as a follow-up with auth and network controls. |
+| Migration dry-run/apply/rollback | Current PR includes prepare/apply/rollback, runtime preflight, rollback CLI, fail-closed runtime migration tests, and focused migration tests. Prepare records compile settings and apply rejects stale dry-runs if trusted proxy/ACME settings changed after prepare. Approved Caddy custom-certificate `load_files` artifacts are validated before cutover, applied into the active config, and rollback restores the previous config. | Locally covered | None before PR update; runtime proof stays external to this PR surface. |
+| Developer paper cuts | Generated RepoPrompt exports, private docs, stale Traefik labels, broad noisy tests, and provider-specific UI names can cause review friction. Current upstream-surface audit found no forbidden prompt exports/private docs/platform runbooks and commit subjects are generic. Provider-neutral env-editor wiring and compose-domain call-site source contracts now guard two review-friction areas that could otherwise regress silently. | Locally covered | Re-run the hygiene audit and `git diff --check` after any further commits before push. |
+
+## Code Evidence
+
+Provider selection and resources:
+
+- `packages/server/src/utils/web-server/providers.ts`
+- `packages/server/src/services/web-server-settings.ts`
+- `apps/dokploy/drizzle/0170_web_server_provider.sql`
+- `apps/dokploy/drizzle/0171_caddy_trusted_proxy_config.sql`
+
+Application domain Caddy routing:
+
+- `packages/server/src/utils/web-server/domain.ts`
+- `packages/server/src/utils/caddy/domain.ts`
+- `packages/server/src/utils/caddy/config.ts`
+- `apps/dokploy/server/api/routers/domain.ts`
+
+Compose domain Caddy routing:
+
+- `packages/server/src/utils/caddy/compose.ts`
+- `packages/server/src/utils/docker/domain.ts`
+- `apps/dokploy/server/api/routers/domain.ts`
+
+Known domain creation call sites now using the shared provider-aware helpers:
+
+- `apps/dokploy/server/api/routers/project.ts`: project duplication creates application domains with `createDomain()` and compose domains with `createComposeDomain()`.
+- `apps/dokploy/server/api/routers/compose.ts`: template create and template import create compose domains with `createComposeDomain()`.
+- `apps/dokploy/server/api/routers/ai.ts`: AI compose generation creates compose domains with `createComposeDomain()`.
+
+Preview route evidence:
+
+- `packages/server/src/services/preview-deployment.ts` creates a preview domain, mutates `application.appName`, and calls provider-aware web-server domain helpers.
+- `apps/dokploy/__test__/caddy/preview-deployment.test.ts` proves provider-aware preview create/remove behavior.
+
+Custom certificate evidence:
+
+- `packages/server/src/utils/caddy/domain.ts` maps Caddy custom certificate domains to uploaded certificate files.
+- `packages/server/src/utils/caddy/compose.ts` carries custom certificate file references for compose Caddy routes.
+- `packages/server/src/utils/caddy/config.ts` compiles manual certificate files into `apps.tls.certificates.load_files`.
+- `apps/dokploy/components/dashboard/application/domains/handle-domain.tsx` lets Caddy domains choose an uploaded certificate from the existing certificate UI surface.
+- `apps/dokploy/components/dashboard/settings/certificates/handle-certificate.tsx` is the existing uploaded certificate creation surface.
+
+Trusted proxy evidence:
+
+- `packages/server/src/db/schema/web-server-settings.ts` persists local Caddy trusted proxy settings.
+- `packages/server/src/db/schema/server.ts` persists remote-server Caddy trusted proxy settings.
+- `packages/server/src/services/web-server-settings.ts` normalizes trusted proxy settings and maps them into Caddy compile options.
+- `apps/dokploy/server/api/routers/settings.ts` exposes Caddy trusted proxy read/update endpoints and rebuilds Caddy config when active.
+- `apps/dokploy/components/dashboard/settings/web-server/caddy-trusted-proxy-settings.tsx` provides the local/remote UI dialog.
+
+Request analytics evidence:
+
+- `packages/server/src/db/schema/web-server-settings.ts` persists local Caddy request-log enablement.
+- `packages/server/src/utils/caddy/config.ts` compiles Caddy access logging to a JSON file writer.
+- `packages/server/src/utils/caddy/web-server.ts` preserves local Caddy compile settings when rewriting the Dokploy dashboard route.
+- `packages/server/src/utils/access-log/utils.ts` normalizes Caddy and Traefik JSON access logs into the Requests table shape.
+- `packages/server/src/utils/access-log/handler.ts` cleans the active web-server access log and only signals Traefik when Traefik is active.
+- `packages/server/src/setup/caddy-setup.ts` and `packages/server/src/services/settings.ts` preserve Caddy access-log compile settings during env/port setup rewrites.
+- `apps/dokploy/server/api/routers/settings.ts` exposes provider-aware request analytics state, toggle, stats, and stats-log endpoints.
+- `apps/dokploy/components/dashboard/requests/show-requests.tsx` uses active-provider copy for request analytics activation and reload guidance.
+
+Migration and live-readiness evidence:
+
+- `packages/server/src/utils/caddy/migration/prepare.ts`
+- `packages/server/src/utils/caddy/migration/apply.ts`
+- `packages/server/src/utils/caddy/migration/rollback.ts`
+- `packages/server/src/utils/caddy/migration/upstream-preflight.ts`
+- `apps/dokploy/scripts/caddy-migration-rollback.ts`
+- `apps/dokploy/__test__/caddy/migration/*`
+- `apps/dokploy/__test__/db/runtime-migration.test.ts`
+
+## Implementation Plan
+
+### 1. Preserve the proof document
+
+- Keep this file upstream-safe.
+- Update the readiness matrix as gaps close.
+- Add proof log entries with command summaries, commit SHAs, and sanitized notes.
+- Do not add prompt exports, private route manifests, or platform-specific production evidence to the PR.
+
+### 2. Harden compose domain refresh for non-router creation flows
+
+Original issue: `domainRouter.create` refreshed compose Caddy routes, but several service creation/import flows called `createDomain()` directly. That meant compose domains could be present in the DB without matching Caddy fragments until a later domain edit or deploy refresh.
+
+Preferred design:
+
+- Move the router-local Caddy compose refresh behavior into a reusable helper.
+- Make it no-op for Traefik and refresh Caddy compose fragments when the active provider is Caddy.
+- Call it after compose domain creation in template create, template import, AI compose generation, and project duplication paths.
+- Keep rollback behavior local to shared compose domain create/delete helpers.
+
+Implemented:
+
+- `packages/server/src/services/domain.ts` exports `refreshCaddyComposeRoutes()`, `createComposeDomain()`, and `removeComposeDomainsForWebServer()`.
+- `domainRouter.create`, template create, template import, AI compose generation, and project compose duplication now use `createComposeDomain()`.
+- Template import removes old compose domain rows through `removeComposeDomainsForWebServer()` so Caddy fragments are refreshed even when the imported template has no replacement domains.
+- `apps/dokploy/__test__/caddy/compose/domain.test.ts` proves the helper writes Caddy fragments for domains created outside the domain router and skips refresh for Traefik.
+- `apps/dokploy/__test__/caddy/application/domain-service.test.ts` proves imported-template domain deletion refreshes Caddy with zero remaining domains, restores old routes if DB deletion fails, and skips Caddy refresh under Traefik.
+
+Required tests:
+
+- New compose from template under active Caddy writes fragments.
+- Template import under active Caddy writes fragments.
+- AI compose generation under active Caddy writes fragments.
+- Project compose duplication under active Caddy writes fragments.
+- Failure to write/reload Caddy leaves no orphaned domain rows or restores previous route fragments.
+- Template import with zero replacement domains removes stale Caddy fragments.
+
+### 3. Make preview domains provider-aware
+
+Original issue: preview deployment route management imported and called Traefik `manageDomain()` directly after creating a preview domain.
+
+Preferred design:
+
+- Route preview domain management through `manageWebServerDomain()`.
+- Ensure `application.appName` is still set to the preview deployment app name before provider dispatch.
+- Add tests for preview domain creation under Caddy.
+
+Implemented:
+
+- Preview create now calls `manageWebServerDomain()`.
+- Preview removal now calls `removeWebServerDomain()` when the preview deployment has a domain.
+- `apps/dokploy/__test__/caddy/preview-deployment.test.ts` proves provider-aware create/remove behavior.
+
+### 4. Prove application and compose domain lifecycle rollback
+
+Application domains:
+
+- Create should route through the active web server provider when a domain is created for a copied/new application service. Covered by `apps/dokploy/__test__/caddy/application/domain-service.test.ts`.
+- Create should not leave a DB row if Caddy write/reload fails. Covered by `apps/dokploy/__test__/caddy/application/domain-service.test.ts`.
+- Update should restore the old fragment if Caddy write/reload fails. Covered by `apps/dokploy/__test__/caddy/config.test.ts`.
+- Update should restore the old fragment if DB update fails. Covered by `apps/dokploy/__test__/caddy/domain-router-lifecycle.test.ts`.
+- Delete should restore the fragment if Caddy write/reload fails. Covered by `apps/dokploy/__test__/caddy/config.test.ts`.
+- Delete should not remove the DB row if provider route removal fails before deletion. Covered by `apps/dokploy/__test__/caddy/domain-router-lifecycle.test.ts`.
+- Delete should restore the fragment if DB delete fails. Covered by `apps/dokploy/__test__/caddy/domain-router-lifecycle.test.ts`.
+
+Compose domains:
+
+- Create should remove the new DB row and restore previous fragments if Caddy refresh fails. Covered by `apps/dokploy/__test__/caddy/application/domain-service.test.ts`.
+- Update should restore DB fields and previous fragments if Caddy refresh fails. Covered by `apps/dokploy/__test__/caddy/domain-router-lifecycle.test.ts`.
+- Delete should not remove the DB row if Caddy route refresh fails before deletion. Covered by `apps/dokploy/__test__/caddy/domain-router-lifecycle.test.ts`.
+- Delete should restore fragments if Caddy refresh or DB delete fails. Covered by `apps/dokploy/__test__/caddy/domain-router-lifecycle.test.ts`.
+
+### 5. Decide and implement custom certificate behavior
+
+Implemented behavior: support uploaded certificates for Caddy domains.
+
+Implemented:
+
+- Caddy application and compose route intents can carry uploaded certificate file references.
+- Caddy config compilation emits manual certificate file loaders while keeping automatic HTTPS available for other routes.
+- The Caddy domain UI labels the `custom` certificate provider as an uploaded certificate flow and restricts selection to certificates available to the current server.
+- Missing custom certificate references fail before writing Caddy config.
+- Migration dry-runs keep DB fallback routes with readable uploaded custom certificates and include the manual certificate files in the draft Caddy JSON.
+- Migration dry-runs emit a blocking warning instead of generating DB fallback Caddy routes when a custom certificate reference does not map to an uploaded certificate with readable files for the same server and organization.
+- Custom certificate guards ignore stale uploaded-certificate fields when HTTPS is disabled.
+- Shared domain validation only requires `customCertResolver` when HTTPS custom certificates are selected, so preview-domain and API submissions are not blocked by hidden certificate fields.
+- Active Caddy domains block deleting an uploaded certificate or replacing its cert/key files until the domain no longer references it.
+
+Tradeoff:
+
+- The implementation reuses the existing `customCertResolver` column to store the uploaded certificate path for Caddy, while Traefik keeps using it as a resolver name.
+- This avoids a database migration in the PR, but the field name is Traefik-shaped. A provider-neutral schema rename or additive field remains a cleanup candidate if maintainers want stricter data semantics.
+
+### 6. Add Cloudflare/proxy awareness
+
+Default behavior:
+
+- Do not trust arbitrary forwarded headers by default.
+
+Cloudflare opt-in behavior:
+
+- Provide a Caddy trusted-proxy mode for Cloudflare only when public traffic is expected to reach Dokploy through Cloudflare.
+- Use static trusted proxy ranges in the standard Caddy image; the dynamic Cloudflare IP-source module is non-standard and should not be required by this PR.
+- Validate any custom CIDRs before writing config.
+- Document SSL mode caveats:
+ - DNS-only works normally.
+ - Orange-cloud proxy hides the real TCP peer behind Cloudflare.
+ - Flexible SSL is not recommended for Dokploy origins.
+ - Full strict is preferred with a valid origin certificate.
+
+Tests:
+
+- Default config does not trust forwarded headers globally.
+- Cloudflare mode emits trusted proxy config.
+- Custom CIDRs validate and compile.
+- Invalid CIDRs fail before config write.
+
+Implemented:
+
+- `packages/server/src/utils/caddy/config.ts` supports trusted proxy compile options.
+- `packages/server/src/utils/caddy/types.ts` defines compile and persisted trusted proxy setting types.
+- Local `webServerSettings` and remote `server` rows persist Caddy trusted proxy settings.
+- Caddy domain, compose, dashboard, migration dry-run, migration apply setup, and web-server setup paths pass persisted trusted proxy options into Caddy compilation.
+- Settings -> Web Server exposes a Caddy trusted proxy dialog for local settings; remote web-server actions expose the same dialog for remote Caddy.
+- The Caddy trusted proxy dialog includes Cloudflare origin SSL mode guidance: DNS-only or Full (strict) are acceptable; Flexible SSL is not recommended.
+- `apps/dokploy/__test__/caddy/config.test.ts` covers default no-trust behavior, Cloudflare trusted proxy config, custom static CIDRs, invalid CIDR/header rejection, and persisted-setting normalization.
+- `apps/dokploy/__test__/caddy/trusted-proxy-settings-router.test.ts` covers settings mutation behavior under Traefik, active local and remote Caddy rebuild using compile settings read after persistence, remote dashboard state reads, local and remote rollback if active Caddy reload fails, Caddy admin-port update rejection, and non-admin Caddy additional-port allowance.
+- `apps/dokploy/__test__/caddy/config.test.ts` covers reloading the restored runtime Caddy config after a failed safe reload and preserving the original error if the restore attempt also fails.
+
+Still needed:
+
+- Runtime proof after explicit approval.
+
+### 7. Polish UI and dashboard behavior
+
+UI changes:
+
+- Use provider-neutral visible labels where users still see Traefik-specific wording for generic web server controls.
+- Add a Caddy settings section for trusted proxy mode if Cloudflare support lands.
+- Keep the provider selector guardrails: direct Caddy activation should go through migration apply, and Caddy to Traefik should go through rollback.
+- Make dry-run/apply/rollback safety copy explicit.
+- Warn in the migration panel that changing Caddy settings after a dry run requires preparing a fresh dry run before apply.
+- Make update-server health checks provider-aware so the modal checks and displays Caddy when Caddy is the active web server.
+- Keep the provider-aware application web-server config preview fresh when application domains, redirects, or security settings change.
+- Use active-provider labels and endpoints for remote web-server file-system inspection, with Caddy generated artifacts read-only and backup migration artifacts hidden from the tree.
+
+Dashboard decision:
+
+- Do not expose a public Caddy dashboard or admin API in this PR.
+- Keep Caddy admin local-only.
+- Reject direct dashboard toggle API calls while Caddy is active so hidden UI
+ controls cannot mutate legacy Traefik resources.
+- If a dashboard is desired later, it should be an authenticated Dokploy proxy and likely a follow-up PR.
+- `apps/dokploy/__test__/caddy/dashboard-route.test.ts` covers the local-only Caddy admin bind.
+- `apps/dokploy/__test__/caddy/trusted-proxy-settings-router.test.ts` covers the Caddy dashboard disabled API state, direct dashboard toggle rejection under active Caddy, and Traefik dashboard-port contrast.
+
+### 8. Keep migration hardening intact
+
+Do not weaken these gates:
+
+- Apply fails closed on blocking warnings.
+- Apply fails closed on runtime upstream preflight failure.
+- Caddy config validation runs before Traefik is stopped.
+- Apply fails closed if Caddy compile settings changed after the dry-run artifact was prepared.
+- Rollback uses backup metadata and restores Traefik resources.
+- Rollback CLI exits non-zero on failure.
+- Runtime DB migration failure exits non-zero.
+
+## Validation Ladder
+
+Run and record the narrowest checks that prove the changed behavior.
+
+Static checks:
+
+```bash
+git diff --check
+```
+
+Focused Caddy tests:
+
+```bash
+pnpm --filter=dokploy test --run __test__/caddy
+pnpm --filter=dokploy test --run __test__/db/runtime-migration.test.ts
+```
+
+Targeted type and format checks:
+
+```bash
+pnpm --filter=dokploy typecheck
+pnpm --filter=@dokploy/server typecheck
+pnpm format
+```
+
+Shell checks, only if shell scripts are touched:
+
+```bash
+bash -n