From 7184b7d4b2093278146d9df97271885a83a23d34 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 10 Feb 2026 22:42:44 -0600 Subject: [PATCH 1/2] feat(traefik): add support for internationalized domain names (IDN) - Implemented a function to convert IDNs to ASCII punycode format, ensuring compatibility with Traefik requirements. - Added tests to verify the conversion of IDNs and the handling of ASCII domains in router configurations. --- apps/dokploy/__test__/traefik/traefik.test.ts | 24 +++++++++++++++++++ packages/server/src/utils/traefik/domain.ts | 17 ++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index bcbc74623..642533d61 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -275,3 +275,27 @@ test("CertificateType on websecure entrypoint", async () => { expect(router.tls?.certResolver).toBe("letsencrypt"); }); + +/** IDN/Punycode */ + +test("Internationalized domain name is converted to punycode", async () => { + const router = await createRouterConfig( + baseApp, + { ...baseDomain, host: "тест.рф" }, + "web", + ); + + // тест.рф in punycode is xn--e1aybc.xn--p1ai + expect(router.rule).toContain("Host(`xn--e1aybc.xn--p1ai`)"); + expect(router.rule).not.toContain("тест.рф"); +}); + +test("ASCII domain remains unchanged", async () => { + const router = await createRouterConfig( + baseApp, + { ...baseDomain, host: "example.com" }, + "web", + ); + + expect(router.rule).toContain("Host(`example.com`)"); +}); diff --git a/packages/server/src/utils/traefik/domain.ts b/packages/server/src/utils/traefik/domain.ts index 68095fa80..97400b1b9 100644 --- a/packages/server/src/utils/traefik/domain.ts +++ b/packages/server/src/utils/traefik/domain.ts @@ -104,6 +104,20 @@ export const removeDomain = async ( } }; +/** + * Converts an internationalized domain name (IDN) to ASCII punycode format. + * Traefik requires domain names in ASCII format, so non-ASCII characters + * must be converted (e.g., "тест.рф" → "xn--e1aybc.xn--p1ai"). + */ +const toPunycode = (host: string): string => { + try { + return new URL(`http://${host}`).hostname; + } catch { + // If URL parsing fails, return the original host + return host; + } +}; + export const createRouterConfig = async ( app: ApplicationNested, domain: Domain, @@ -114,8 +128,9 @@ export const createRouterConfig = async ( const { host, path, https, uniqueConfigKey, internalPath, stripPath } = domain; + const punycodeHost = toPunycode(host); const routerConfig: HttpRouter = { - rule: `Host(\`${host}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`, + rule: `Host(\`${punycodeHost}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`, service: `${appName}-service-${uniqueConfigKey}`, middlewares: [], entryPoints: [entryPoint], From 3b753ecfbfe1f7c5d9a1a2e0137521d23608abfc Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 10 Feb 2026 22:44:17 -0600 Subject: [PATCH 2/2] test(traefik): add tests for punycode conversion of Russian IDNs - Added tests to verify the conversion of Russian Cyrillic domains and subdomains with IDN TLDs to punycode format, ensuring proper handling in router configurations. - Confirmed that non-ASCII parts are correctly converted while ASCII parts remain unchanged. --- apps/dokploy/__test__/traefik/traefik.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index 642533d61..9121dc8a1 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -299,3 +299,27 @@ test("ASCII domain remains unchanged", async () => { expect(router.rule).toContain("Host(`example.com`)"); }); + +test("Russian Cyrillic label with .ru TLD is converted to punycode", async () => { + const router = await createRouterConfig( + baseApp, + { ...baseDomain, host: "сайт.ru" }, + "web", + ); + + // сайт in punycode is xn--80aswg + expect(router.rule).toContain("Host(`xn--80aswg.ru`)"); + expect(router.rule).not.toContain("сайт"); +}); + +test("Subdomain with Russian IDN TLD converts non-ASCII part to punycode", async () => { + const router = await createRouterConfig( + baseApp, + { ...baseDomain, host: "app.тест.рф" }, + "web", + ); + + // app stays ASCII, тест.рф becomes xn--e1aybc.xn--p1ai + expect(router.rule).toContain("Host(`app.xn--e1aybc.xn--p1ai`)"); + expect(router.rule).not.toContain("тест.рф"); +});