From c459997453fd9432b714358b63e27a8aac64b0b0 Mon Sep 17 00:00:00 2001
From: HarikrishnanD
Date: Thu, 13 Nov 2025 11:52:06 +0530
Subject: [PATCH 1/3] fix(traefik): validate port 8080 before enabling
dashboard
---
.../servers/actions/show-traefik-actions.tsx | 7 +-
apps/dokploy/server/api/routers/settings.ts | 12 ++++
packages/server/src/services/docker.ts | 6 +-
packages/server/src/services/settings.ts | 72 +++++++++++++++++++
4 files changed, 93 insertions(+), 4 deletions(-)
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 d9573ca74..578c4df8a 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
@@ -97,7 +97,12 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
);
refetchDashboard();
})
- .catch(() => {});
+ .catch((error) => {
+ const errorMessage =
+ error?.message ||
+ "Failed to toggle dashboard. Please check if port 8080 is available.";
+ toast.error(errorMessage);
+ });
}}
className="w-full cursor-pointer space-x-3"
>
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index b4968c260..48f80c167 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -1,6 +1,7 @@
import {
canAccessToTraefikFiles,
checkGPUStatus,
+ checkPortInUse,
cleanStoppedContainers,
cleanUpDockerBuilder,
cleanUpSystemPrune,
@@ -130,6 +131,17 @@ export const settingsRouter = createTRPCRouter({
let newPorts = ports;
// If receive true, add 8080 to ports
if (input.enableDashboard) {
+ // Check if port 8080 is already in use before enabling dashboard
+ const portCheck = await checkPortInUse(8080, input.serverId);
+ if (portCheck.isInUse) {
+ const conflictingContainer = portCheck.conflictingContainer
+ ? ` by container "${portCheck.conflictingContainer}"`
+ : "";
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: `Port 8080 is already in use${conflictingContainer}. Please stop the conflicting service or use a different port for the Traefik dashboard.`,
+ });
+ }
newPorts.push({
targetPort: 8080,
publishedPort: 8080,
diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts
index 2194c89c6..ab454b7ea 100644
--- a/packages/server/src/services/docker.ts
+++ b/packages/server/src/services/docker.ts
@@ -290,10 +290,10 @@ export const getContainersByAppLabel = async (
const command =
type === "swarm"
- ? `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`
+ ? `docker ps -a --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`
: type === "standalone"
- ? `docker ps --filter "name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`
- : `docker ps --filter "label=com.docker.compose.project=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`;
+ ? `docker ps -a --filter "name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`
+ : `docker ps -a --filter "label=com.docker.compose.project=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`;
if (serverId) {
const result = await execAsyncRemote(serverId, command);
stdout = result.stdout;
diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts
index 301573cb4..33bd6500a 100644
--- a/packages/server/src/services/settings.ts
+++ b/packages/server/src/services/settings.ts
@@ -394,6 +394,78 @@ export const readPorts = async (
);
};
+/**
+ * Check if a port is already in use by another container
+ * @param port - The port number to check
+ * @param serverId - Optional server ID for remote Docker
+ * @returns Object with isInUse boolean and conflictingContainer name if found
+ */
+export const checkPortInUse = async (
+ port: number,
+ serverId?: string,
+): Promise<{ isInUse: boolean; conflictingContainer?: string }> => {
+ try {
+ // Method 1: Check all containers and inspect their port bindings
+ const listContainersCommand = `docker ps -a --format '{{.Names}}'`;
+ let containersList = "";
+ if (serverId) {
+ const result = await execAsyncRemote(serverId, listContainersCommand);
+ containersList = result.stdout.trim();
+ } else {
+ const result = await execAsync(listContainersCommand);
+ containersList = result.stdout.trim();
+ }
+
+ const containerNames = containersList
+ .split("\n")
+ .filter((name) => name && name !== "dokploy-traefik");
+
+ // Check each container's port bindings
+ for (const containerName of containerNames) {
+ const portCheckCommand = `docker port ${containerName} 2>/dev/null | grep ':${port}' || true`;
+ let portOutput = "";
+ if (serverId) {
+ const result = await execAsyncRemote(serverId, portCheckCommand);
+ portOutput = result.stdout.trim();
+ } else {
+ const result = await execAsync(portCheckCommand);
+ portOutput = result.stdout.trim();
+ }
+
+ if (portOutput) {
+ return {
+ isInUse: true,
+ conflictingContainer: containerName,
+ };
+ }
+ }
+
+ // Method 2: Check using ss/netstat for any process using the port
+ const portCheckCommand = `ss -tuln 2>/dev/null | grep ':${port} ' || netstat -tuln 2>/dev/null | grep ':${port} ' || true`;
+ let portCheckOutput = "";
+ if (serverId) {
+ const result = await execAsyncRemote(serverId, portCheckCommand);
+ portCheckOutput = result.stdout.trim();
+ } else {
+ const result = await execAsync(portCheckCommand);
+ portCheckOutput = result.stdout.trim();
+ }
+
+ if (portCheckOutput) {
+ // Port is in use but we couldn't identify the container
+ // This could be a non-Docker process or a container we couldn't detect
+ return { isInUse: true };
+ }
+
+ return { isInUse: false };
+ } catch (error) {
+ // If check fails, log error but don't block the operation
+ // The actual Docker bind will fail if port is truly in use
+ console.error("Error checking port availability:", error);
+ return { isInUse: false };
+ }
+};
+
export const writeTraefikSetup = async (input: TraefikOptions) => {
const resourceType = await getDockerResourceType(
"dokploy-traefik",
From 439fba1f4bcba4b696a1ee67e1d488aa3dcab8aa Mon Sep 17 00:00:00 2001
From: Mauricio Siu
Date: Sat, 6 Dec 2025 13:58:03 -0600
Subject: [PATCH 2/3] fix: improve error handling for Traefik port updates and
enhance port availability checks
---
.../web-server/manage-traefik-ports.tsx | 4 +-
apps/dokploy/server/api/routers/settings.ts | 13 ++++
packages/server/src/services/docker.ts | 6 +-
packages/server/src/services/settings.ts | 68 +++----------------
4 files changed, 28 insertions(+), 63 deletions(-)
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 282f1fddd..c67422220 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
@@ -105,7 +105,9 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
});
toast.success(t("settings.server.webServer.traefik.portsUpdated"));
setOpen(false);
- } catch {}
+ } catch (error) {
+ toast.error((error as Error).message || "Error updating Traefik ports");
+ }
};
return (
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index 90f895d0d..4894859df 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -822,6 +822,19 @@ export const settingsRouter = createTRPCRouter({
"dokploy-traefik",
input?.serverId,
);
+
+ 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.targetPort} is already in use by ${portCheck.conflictingContainer}`,
+ });
+ }
+ }
const preparedEnv = prepareEnvironmentVariables(env);
await writeTraefikSetup({
diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts
index ab454b7ea..2194c89c6 100644
--- a/packages/server/src/services/docker.ts
+++ b/packages/server/src/services/docker.ts
@@ -290,10 +290,10 @@ export const getContainersByAppLabel = async (
const command =
type === "swarm"
- ? `docker ps -a --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`
+ ? `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`
: type === "standalone"
- ? `docker ps -a --filter "name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`
- : `docker ps -a --filter "label=com.docker.compose.project=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`;
+ ? `docker ps --filter "name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`
+ : `docker ps --filter "label=com.docker.compose.project=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`;
if (serverId) {
const result = await execAsyncRemote(serverId, command);
stdout = result.stdout;
diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts
index a33ea9988..3a7222dab 100644
--- a/packages/server/src/services/settings.ts
+++ b/packages/server/src/services/settings.ts
@@ -392,73 +392,23 @@ export const readPorts = async (
);
};
-/**
- * Check if a port is already in use by another container
- * @param port - The port number to check
- * @param serverId - Optional server ID for remote Docker
- * @returns Object with isInUse boolean and conflictingContainer name if found
- */
export const checkPortInUse = async (
port: number,
serverId?: string,
): Promise<{ isInUse: boolean; conflictingContainer?: string }> => {
try {
- // Method 1: Check all containers and inspect their port bindings
- const listContainersCommand = `docker ps -a --format '{{.Names}}'`;
- let containersList = "";
- if (serverId) {
- const result = await execAsyncRemote(serverId, listContainersCommand);
- containersList = result.stdout.trim();
- } else {
- const result = await execAsync(listContainersCommand);
- containersList = result.stdout.trim();
- }
+ const command = `docker ps -a --format '{{.Names}}' | grep -v '^dokploy-traefik$' | while read name; do docker port "$name" 2>/dev/null | grep -q ':${port}' && echo "$name" && break; done || true`;
+ const { stdout } = serverId
+ ? await execAsyncRemote(serverId, command)
+ : await execAsync(command);
- const containerNames = containersList
- .split("\n")
- .filter((name) => name && name !== "dokploy-traefik");
+ const container = stdout.trim();
- // Check each container's port bindings
- for (const containerName of containerNames) {
- const portCheckCommand = `docker port ${containerName} 2>/dev/null | grep ':${port}' || true`;
- let portOutput = "";
- if (serverId) {
- const result = await execAsyncRemote(serverId, portCheckCommand);
- portOutput = result.stdout.trim();
- } else {
- const result = await execAsync(portCheckCommand);
- portOutput = result.stdout.trim();
- }
-
- if (portOutput) {
- return {
- isInUse: true,
- conflictingContainer: containerName,
- };
- }
- }
-
- // Method 2: Check using ss/netstat for any process using the port
- const portCheckCommand = `ss -tuln 2>/dev/null | grep ':${port} ' || netstat -tuln 2>/dev/null | grep ':${port} ' || true`;
- let portCheckOutput = "";
- if (serverId) {
- const result = await execAsyncRemote(serverId, portCheckCommand);
- portCheckOutput = result.stdout.trim();
- } else {
- const result = await execAsync(portCheckCommand);
- portCheckOutput = result.stdout.trim();
- }
-
- if (portCheckOutput) {
- // Port is in use but we couldn't identify the container
- // This could be a non-Docker process or a container we couldn't detect
- return { isInUse: true };
- }
-
- return { isInUse: false };
+ return {
+ isInUse: !!container,
+ conflictingContainer: container || undefined,
+ };
} catch (error) {
- // If check fails, log error but don't block the operation
- // The actual Docker bind will fail if port is truly in use
console.error("Error checking port availability:", error);
return { isInUse: false };
}
From 287dfb540201cd3337ba0c1a10ed051c7506eb1c Mon Sep 17 00:00:00 2001
From: Mauricio Siu
Date: Sat, 6 Dec 2025 14:06:43 -0600
Subject: [PATCH 3/3] feat: add dialog action for enabling/disabling Traefik
dashboard and enhance manage ports component with warning alert
---
.../servers/actions/show-traefik-actions.tsx | 40 ++++++++++++++++---
.../web-server/manage-traefik-ports.tsx | 10 ++++-
2 files changed, 42 insertions(+), 8 deletions(-)
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 578c4df8a..aebba8877 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
@@ -1,5 +1,7 @@
import { useTranslation } from "next-i18next";
import { toast } from "sonner";
+import { AlertBlock } from "@/components/shared/alert-block";
+import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@@ -85,7 +87,26 @@ export const ShowTraefikActions = ({ 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.
+
+
+ Are you sure you want to{" "}
+ {haveTraefikDashboardPortEnabled ? "disable" : "enable"} the
+ Traefik dashboard?
+
+
+ }
onClick={async () => {
await toggleDashboard({
enableDashboard: !haveTraefikDashboardPortEnabled,
@@ -104,12 +125,19 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
toast.error(errorMessage);
});
}}
- className="w-full cursor-pointer space-x-3"
+ disabled={toggleDashboardIsLoading}
+ 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/web-server/manage-traefik-ports.tsx b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx
index c67422220..3ce95aa1f 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
@@ -158,11 +158,11 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
) : (
-
+
{fields.map((field, index) => (
-
+
{
)}
+
+
+ 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.
+