diff --git a/apps/api/package.json b/apps/api/package.json index d1f3ff48956..c8889036145 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,7 +21,7 @@ "react": "19.3.0", "react-dom": "19.3.0", "redis": "4.7.1", - "zod": "^4.6.5" + "zod": "^4.6.7" }, "devDependencies": { "@types/node": "^24.19.1", diff --git a/apps/dokploy/__test__/settings/traefik-ports.test.ts b/apps/dokploy/__test__/settings/traefik-ports.test.ts new file mode 100644 index 00000000000..0410708eab3 --- /dev/null +++ b/apps/dokploy/__test__/settings/traefik-ports.test.ts @@ -0,0 +1,78 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + execAsync: vi.fn(), + execAsyncRemote: vi.fn(), +})); + +vi.mock("@dokploy/server/db", () => ({ + db: {}, +})); + +vi.mock("@dokploy/server/utils/process/execAsync", () => ({ + execAsync: mocks.execAsync, + execAsyncRemote: mocks.execAsyncRemote, +})); + +const { readPorts } = await import("@dokploy/server/services/settings"); + +// `docker container inspect --format '{{json .NetworkSettings.Ports}}'` of a +// Traefik container that publishes only 443: the image still exposes 80. +const traefikPorts = { + "80/tcp": null, + "443/tcp": [ + { HostIp: "0.0.0.0", HostPort: "443" }, + { HostIp: "::", HostPort: "443" }, + ], + "443/udp": [ + { HostIp: "0.0.0.0", HostPort: "443" }, + { HostIp: "::", HostPort: "443" }, + ], +}; + +const standaloneWith = (ports: Record) => (command: string) => + Promise.resolve({ + stdout: command.includes("RESOURCE_NAME=") + ? "standalone\n" + : `${JSON.stringify(ports)}\n`, + stderr: "", + }); + +describe("readPorts for a standalone Traefik container", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("skips exposed ports that have no host binding", async () => { + mocks.execAsync.mockImplementation(standaloneWith(traefikPorts)); + + await expect(readPorts("dokploy-traefik")).resolves.toEqual([]); + }); + + it("still lists additional published ports next to an unpublished one", async () => { + mocks.execAsync.mockImplementation( + standaloneWith({ + ...traefikPorts, + "8080/tcp": null, + "5432/tcp": [ + { HostIp: "0.0.0.0", HostPort: "5433" }, + { HostIp: "::", HostPort: "5433" }, + ], + }), + ); + + await expect(readPorts("dokploy-traefik")).resolves.toEqual([ + { targetPort: 5432, publishedPort: 5433, protocol: "tcp" }, + ]); + }); + + it("reads the ports of a remote server's Traefik the same way", async () => { + mocks.execAsyncRemote.mockImplementation( + (_serverId: string, command: string) => + standaloneWith(traefikPorts)(command), + ); + + await expect(readPorts("dokploy-traefik", "server-1")).resolves.toEqual([]); + expect(mocks.execAsync).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index eb3d6e628a0..f8cd88d1d10 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -137,7 +137,7 @@ "recharts": "^3.10.1", "resend": "^6.32.1", "semver": "7.8.5", - "shadcn": "^4.21.4", + "shadcn": "^4.22.1", "shell-quote": "^1.12.0", "slugify": "^1.6.9", "sonner": "^2.0.8", @@ -154,7 +154,7 @@ "ws": "8.22.0", "xterm-addon-fit": "^0.8.0", "yaml": "2.9.1", - "zod": "^4.6.5", + "zod": "^4.6.7", "zod-form-data": "^3.0.3" }, "devDependencies": { diff --git a/apps/schedules/package.json b/apps/schedules/package.json index 898e50fbe5b..1466f5b8681 100644 --- a/apps/schedules/package.json +++ b/apps/schedules/package.json @@ -20,7 +20,7 @@ "pino-pretty": "13.2.0", "react": "19.3.0", "react-dom": "19.3.0", - "zod": "^4.6.5" + "zod": "^4.6.7" }, "devDependencies": { "rimraf": "6.1.3", diff --git a/bun.lock b/bun.lock index 808cad9177f..85b1338f638 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ "react": "19.3.0", "react-dom": "19.3.0", "redis": "4.7.1", - "zod": "^4.6.5", + "zod": "^4.6.7", }, "devDependencies": { "@types/node": "^24.19.1", @@ -138,7 +138,7 @@ "recharts": "^3.10.1", "resend": "^6.32.1", "semver": "7.8.5", - "shadcn": "^4.21.4", + "shadcn": "^4.22.1", "shell-quote": "^1.12.0", "slugify": "^1.6.9", "sonner": "^2.0.8", @@ -155,7 +155,7 @@ "ws": "8.22.0", "xterm-addon-fit": "^0.8.0", "yaml": "2.9.1", - "zod": "^4.6.5", + "zod": "^4.6.7", "zod-form-data": "^3.0.3", }, "devDependencies": { @@ -202,7 +202,7 @@ "pino-pretty": "13.2.0", "react": "19.3.0", "react-dom": "19.3.0", - "zod": "^4.6.5", + "zod": "^4.6.7", }, "devDependencies": { "@types/node": "^24.19.1", @@ -278,7 +278,7 @@ "undici": "8.11.2", "ws": "8.22.0", "yaml": "2.9.1", - "zod": "^4.6.5", + "zod": "^4.6.7", }, "devDependencies": { "@types/bcrypt": "5.0.2", @@ -322,6 +322,9 @@ "h3": "1.15.11", "hono": "4.13.13", "http-cache-semantics": ">=4.3.0 <5.0.0", + "inngest": { + "zod": "^4.6.7", + }, "ip-address": ">=10.7.4 <11.0.0", "js-yaml": "4.3.2", "lodash": "4.18.1", @@ -1336,7 +1339,7 @@ "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], - "@shadcn/registry": ["@shadcn/registry@0.1.3", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "cn": "^0.2.4", "code-block-writer": "^13.0.3", "deepmerge": "^4.3.1", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "ora": "^8.2.0", "postcss": "^8.5.23", "prompts": "^2.4.2", "recast": "^0.23.11", "socks": "^2.8.8", "stringify-object": "^5.0.0", "tsconfig-paths": "^4.2.0", "type-fest": "^4.41.0", "undici": "^7.29.0", "zod": "^3.24.1" } }, "sha512-82/OmKAVM9pChDHFVAsDP9PcimPGSKZGdbcdwdGGGcSI69ImpN4ZnqsfZ4hkOFEMal92t3y9djYJHMjO4PcVZA=="], + "@shadcn/registry": ["@shadcn/registry@0.2.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "cn": "^0.2.4", "code-block-writer": "^13.0.3", "deepmerge": "^4.3.1", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "ora": "^8.2.0", "postcss": "^8.5.23", "prompts": "^2.4.2", "recast": "^0.23.11", "socks": "^2.8.8", "stringify-object": "^5.0.0", "tsconfig-paths": "^4.2.0", "type-fest": "^4.41.0", "undici": "^7.29.1", "zod": "^4.2.0" } }, "sha512-m3TeqU/+av2kgOLfESHkWzsmhuQuY4NXZd5Y1Rd4lzy4IX3JJ5+k2mYfkq6OT8oMuk2W4GQPAkO7+CC7KlpSbw=="], "@simplewebauthn/browser": ["@simplewebauthn/browser@13.3.0", "", {}, "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ=="], @@ -3042,7 +3045,7 @@ "sha.js": ["sha.js@2.4.12", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" } }, "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w=="], - "shadcn": ["shadcn@4.21.4", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@shadcn/registry": "0.1.3", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "cn": "^0.2.4", "commander": "^14.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.23", "postcss-selector-parser": "^7.1.3", "prompts": "^2.4.2", "recast": "^0.23.11", "socks": "^2.8.8", "stringify-object": "^5.0.0", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.29.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-YYfn/hsHbRH2mGwnKBx9tk+DjTQH+RUMS5iCxjKzwzuJ5uoHPqLOFUCJSeX0sXfrcBSfgQaXiOLGKNLjlIQxxw=="], + "shadcn": ["shadcn@4.22.1", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.32.0", "@shadcn/registry": "0.2.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.29.3", "cn": "^0.2.4", "commander": "^14.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.4", "ora": "^8.2.0", "postcss": "^8.5.23", "postcss-selector-parser": "^7.1.6", "prompts": "^2.4.2", "recast": "^0.23.11", "socks": "^2.8.8", "stringify-object": "^5.0.0", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.29.1", "validate-npm-package-name": "^7.0.1", "zod": "^4.2.0" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-LbB4Lro+b0qSVki/tgL5edtKylEnXe1ppxMJC12d7NHPgaT+eSgJGMQULwP72V7X9tbiUq7xfx3VDAfB7jdGoA=="], "sharp": ["sharp@0.35.5", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.5", "@img/sharp-darwin-x64": "0.35.5", "@img/sharp-freebsd-wasm32": "0.35.5", "@img/sharp-libvips-darwin-arm64": "1.3.4", "@img/sharp-libvips-darwin-x64": "1.3.4", "@img/sharp-libvips-linux-arm": "1.3.4", "@img/sharp-libvips-linux-arm64": "1.3.4", "@img/sharp-libvips-linux-ppc64": "1.3.4", "@img/sharp-libvips-linux-riscv64": "1.3.4", "@img/sharp-libvips-linux-s390x": "1.3.4", "@img/sharp-libvips-linux-x64": "1.3.4", "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", "@img/sharp-libvips-linuxmusl-x64": "1.3.4", "@img/sharp-linux-arm": "0.35.5", "@img/sharp-linux-arm64": "0.35.5", "@img/sharp-linux-ppc64": "0.35.5", "@img/sharp-linux-riscv64": "0.35.5", "@img/sharp-linux-s390x": "0.35.5", "@img/sharp-linux-x64": "0.35.5", "@img/sharp-linuxmusl-arm64": "0.35.5", "@img/sharp-linuxmusl-x64": "0.35.5", "@img/sharp-webcontainers-wasm32": "0.35.5", "@img/sharp-win32-arm64": "0.35.5", "@img/sharp-win32-ia32": "0.35.5", "@img/sharp-win32-x64": "0.35.5" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g=="], @@ -3378,7 +3381,7 @@ "zenscroll": ["zenscroll@4.0.2", "", {}, "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg=="], - "zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="], + "zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="], "zod-form-data": ["zod-form-data@3.0.3", "", { "dependencies": { "@rvf/set-get": "^7.0.3" }, "peerDependencies": { "zod": ">= 3.25.0" } }, "sha512-1qXJ6hayyUr3TqxfXqmm5qwCa8f/mnG/o8Itcny03iFyrwX/dZkCwfCcYoJDyQP8RxEkycw0c/ldCJXBeaCstg=="], @@ -3550,8 +3553,6 @@ "@shadcn/registry/undici": ["undici@7.30.0", "", {}, "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ=="], - "@shadcn/registry/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@swagger-api/apidom-parser-adapter-yaml-1-2/tree-sitter": ["tree-sitter@0.22.4", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], @@ -3658,8 +3659,6 @@ "import-in-the-middle/es-module-lexer": ["es-module-lexer@3.0.2", "", {}, "sha512-BuIB67FngDSyQ/dpQNOZybwdEBDUGJQvOqwWr4ha/ufYiqzuEwPkKO2zLhRAgay28tStRIHUeWmszZAJo3GCOg=="], - "inngest/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "listr2/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], @@ -3712,8 +3711,6 @@ "shadcn/undici": ["undici@7.30.0", "", {}, "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ=="], - "shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], diff --git a/package.json b/package.json index a22d4be9930..dad04ae076b 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "@modelcontextprotocol/sdk": ">=1.32.1 <2.0.0", "ip-address": ">=10.7.4 <11.0.0", "@tailwindcss/typography>postcss-selector-parser": "7.1.6", - "remarkable>argparse": "2.0.1" + "remarkable>argparse": "2.0.1", + "inngest>zod": "^4.6.7" } } diff --git a/packages/server/package.json b/packages/server/package.json index 8c99959d8f6..1cc80461722 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -99,7 +99,7 @@ "undici": "8.11.2", "ws": "8.22.0", "yaml": "2.9.1", - "zod": "^4.6.5" + "zod": "^4.6.7" }, "devDependencies": { "@types/bcrypt": "5.0.2", diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index 08f042509c0..ffbe0e99377 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -1070,8 +1070,10 @@ export const readPorts = async ( const protocol = key.split("/")[1]; const targetPort = Number.parseInt(key.split("/")[0] ?? "0", 10); + // A port the image exposes but the container does not publish comes + // back as null (Traefik exposes 80, which is never published here). // Take only the first mapping to avoid duplicates (IPv4 and IPv6) - const firstMapping = containerPortMappings[0]; + const firstMapping = containerPortMappings?.[0]; if (firstMapping) { const publishedPort = Number.parseInt(firstMapping.HostPort, 10); const portKey = `${targetPort}-${publishedPort}-${protocol}`;