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",