Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
78 changes: 78 additions & 0 deletions apps/dokploy/__test__/settings/traefik-ports.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>) => (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();
});
});
4 changes: 2 additions & 2 deletions apps/dokploy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion apps/schedules/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 11 additions & 14 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/services/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
Expand Down