diff --git a/apps/dokploy/__test__/compose/domain/labels.test.ts b/apps/dokploy/__test__/compose/domain/labels.test.ts index add7637a5..56ffb3b1d 100644 --- a/apps/dokploy/__test__/compose/domain/labels.test.ts +++ b/apps/dokploy/__test__/compose/domain/labels.test.ts @@ -173,12 +173,12 @@ describe("createDomainLabels", () => { "websecure", ); - // Web entrypoint should have both middlewares with redirect first + // Web entrypoint with HTTPS should only have redirect expect(webLabels).toContain( - "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,addprefix-test-app-1", + "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file", ); - // Websecure should only have the addprefix middleware + // Websecure should have the addprefix middleware expect(websecureLabels).toContain( "traefik.http.routers.test-app-1-websecure.middlewares=addprefix-test-app-1", ); @@ -210,9 +210,9 @@ describe("createDomainLabels", () => { "traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello", ); - // Should have middlewares in correct order: redirect, stripprefix, addprefix + // Web router with HTTPS should only have redirect expect(webLabels).toContain( - "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,stripprefix-test-app-1,addprefix-test-app-1", + "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file", ); }); @@ -267,7 +267,7 @@ describe("createDomainLabels", () => { ); }); - it("should combine custom middlewares with HTTPS redirect in correct order", async () => { + it("should only have redirect on web router when HTTPS is enabled with custom middlewares", async () => { const combinedDomain = { ...baseDomain, https: true, @@ -275,13 +275,14 @@ describe("createDomainLabels", () => { }; const labels = await createDomainLabels(appName, combinedDomain, "web"); - // HTTPS redirect should come before custom middleware + // Web router with HTTPS should only redirect, custom middlewares go on websecure expect(labels).toContain( - "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,auth@file", + "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file", ); + expect(labels).not.toContain("auth@file"); }); - it("should combine custom middlewares with stripPath middleware", async () => { + it("should combine custom middlewares with stripPath middleware (no HTTPS)", async () => { const combinedDomain = { ...baseDomain, path: "/api", @@ -296,7 +297,7 @@ describe("createDomainLabels", () => { ); }); - it("should combine all built-in middlewares with custom middlewares in correct order", async () => { + it("should only have redirect on web router even with all built-in middlewares and custom middlewares", async () => { const fullDomain = { ...baseDomain, https: true, @@ -307,10 +308,21 @@ describe("createDomainLabels", () => { }; const webLabels = await createDomainLabels(appName, fullDomain, "web"); - // Order: redirect-to-https, stripprefix, addprefix, then custom middlewares + // Web router with HTTPS should only redirect expect(webLabels).toContain( - "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,stripprefix-test-app-1,addprefix-test-app-1,auth@file,rate-limit@file", + "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file", ); + // Middleware definitions should still be present (Traefik needs them registered) + expect(webLabels).toContain( + "traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api", + ); + expect(webLabels).toContain( + "traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello", + ); + // But they should NOT be attached to the router + expect(webLabels).not.toContain("stripprefix-test-app-1,"); + expect(webLabels).not.toContain("auth@file"); + expect(webLabels).not.toContain("rate-limit@file"); }); it("should include custom middlewares on websecure entrypoint", async () => { @@ -332,6 +344,22 @@ describe("createDomainLabels", () => { expect(websecureLabels).not.toContain("redirect-to-https"); }); + it("should NOT include custom middlewares on web router when HTTPS is enabled (only redirect)", async () => { + const domain = { + ...baseDomain, + https: true, + middlewares: ["rate-limit@file", "auth@file"], + }; + const webLabels = await createDomainLabels(appName, domain, "web"); + + // Web router with HTTPS should ONLY have redirect, not custom middlewares + expect(webLabels).toContain( + "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file", + ); + expect(webLabels).not.toContain("rate-limit@file"); + expect(webLabels).not.toContain("auth@file"); + }); + it("should create basic labels for custom entrypoint", async () => { const labels = await createDomainLabels( appName, diff --git a/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx index 0b8d635d4..771570229 100644 --- a/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx +++ b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx @@ -290,6 +290,9 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => { composeId: id, }), ...data, + customEntrypoint: data.useCustomEntrypoint + ? data.customEntrypoint + : null, }) .then(async () => { toast.success(dictionary.success); @@ -674,7 +677,12 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => { { + field.onChange(checked); + if (!checked) { + form.setValue("customEntrypoint", undefined); + } + }} /> diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index 92c44f8b5..1a6e77a28 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -279,38 +279,44 @@ export const createDomainLabels = ( // Collect middlewares for this router const middlewares: string[] = []; + const isRedirectRouter = entrypoint === "web" && https && !customEntrypoint; - // Add HTTPS redirect for web entrypoint (must be first) - if (entrypoint === "web" && https) { + // Web router with HTTPS only needs redirect — all other middlewares + // run on the websecure router where the request actually lands. + if (isRedirectRouter) { middlewares.push("redirect-to-https@file"); } // Add stripPath middleware if needed if (stripPath && path && path !== "/") { const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`; - // Only define middleware once (on web entrypoint) + // Define middleware on web (or custom) entrypoint so Traefik registers it if (entrypoint === "web" || customEntrypoint) { labels.push( `traefik.http.middlewares.${middlewareName}.stripprefix.prefixes=${path}`, ); } - middlewares.push(middlewareName); + if (!isRedirectRouter) { + middlewares.push(middlewareName); + } } // Add internalPath middleware if needed if (internalPath && internalPath !== "/" && internalPath.startsWith("/")) { const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`; - // Only define middleware once (on web entrypoint) + // Define middleware on web (or custom) entrypoint so Traefik registers it if (entrypoint === "web" || customEntrypoint) { labels.push( `traefik.http.middlewares.${middlewareName}.addprefix.prefix=${internalPath}`, ); } - middlewares.push(middlewareName); + if (!isRedirectRouter) { + middlewares.push(middlewareName); + } } - // Add custom middlewares - if (domain.middlewares?.length) { + // Add custom middlewares (skip for redirect-only router) + if (!isRedirectRouter && domain.middlewares?.length) { middlewares.push(...domain.middlewares); } diff --git a/packages/server/src/utils/traefik/domain.ts b/packages/server/src/utils/traefik/domain.ts index 4cf09a7f4..c1745ddab 100644 --- a/packages/server/src/utils/traefik/domain.ts +++ b/packages/server/src/utils/traefik/domain.ts @@ -143,22 +143,24 @@ export const createRouterConfig = async ( entryPoints: [entryPoint], }; - // Add path rewriting middleware if needed - if (internalPath && internalPath !== "/" && internalPath !== path) { - const pathMiddleware = `addprefix-${appName}-${uniqueConfigKey}`; - routerConfig.middlewares?.push(pathMiddleware); - } + const isRedirectRouter = entryPoint === "web" && https && !customEntrypoint; - if (stripPath && path && path !== "/") { - const stripMiddleware = `stripprefix-${appName}-${uniqueConfigKey}`; - routerConfig.middlewares?.push(stripMiddleware); - } + // Web router with HTTPS only needs redirect — all other middlewares + // run on the websecure router where the request actually lands. + if (isRedirectRouter) { + routerConfig.middlewares?.push("redirect-to-https"); + } else { + // Add path rewriting middleware if needed + if (internalPath && internalPath !== "/" && internalPath !== path) { + const pathMiddleware = `addprefix-${appName}-${uniqueConfigKey}`; + routerConfig.middlewares?.push(pathMiddleware); + } - if (entryPoint === "web" && https) { - routerConfig.middlewares?.unshift("redirect-to-https"); - } + if (stripPath && path && path !== "/") { + const stripMiddleware = `stripprefix-${appName}-${uniqueConfigKey}`; + routerConfig.middlewares?.push(stripMiddleware); + } - if ((entryPoint === "websecure" && https) || !https) { // redirects - skip for preview deployments as wildcard subdomains // should not inherit parent redirect rules (e.g., www-redirect) if (domain.domainType !== "preview") {