Skip to content

Commit 4873baa

Browse files
authored
Merge pull request #2997 from Harikrishnan1367709/Traefik--Enable-dashboard-dokploy-traefik-container-gone,all-services-domains-down
fix(traefik): validate port 8080 before enabling dashboard -#2996
2 parents de6c1a7 + 287dfb5 commit 4873baa

File tree

4 files changed

+98
-10
lines changed

4 files changed

+98
-10
lines changed

apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useTranslation } from "next-i18next";
22
import { toast } from "sonner";
3+
import { AlertBlock } from "@/components/shared/alert-block";
4+
import { DialogAction } from "@/components/shared/dialog-action";
35
import { Button } from "@/components/ui/button";
46
import {
57
DropdownMenu,
@@ -85,7 +87,26 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
8587
</DropdownMenuItem>
8688
</EditTraefikEnv>
8789

88-
<DropdownMenuItem
90+
<DialogAction
91+
title={
92+
haveTraefikDashboardPortEnabled
93+
? "Disable Traefik Dashboard"
94+
: "Enable Traefik Dashboard"
95+
}
96+
description={
97+
<div className="space-y-4">
98+
<AlertBlock type="warning">
99+
The Traefik container will be recreated from scratch. This
100+
means the container will be deleted and created again, which
101+
may cause downtime in your applications.
102+
</AlertBlock>
103+
<p>
104+
Are you sure you want to{" "}
105+
{haveTraefikDashboardPortEnabled ? "disable" : "enable"} the
106+
Traefik dashboard?
107+
</p>
108+
</div>
109+
}
89110
onClick={async () => {
90111
await toggleDashboard({
91112
enableDashboard: !haveTraefikDashboardPortEnabled,
@@ -97,14 +118,26 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
97118
);
98119
refetchDashboard();
99120
})
100-
.catch(() => {});
121+
.catch((error) => {
122+
const errorMessage =
123+
error?.message ||
124+
"Failed to toggle dashboard. Please check if port 8080 is available.";
125+
toast.error(errorMessage);
126+
});
101127
}}
102-
className="w-full cursor-pointer space-x-3"
128+
disabled={toggleDashboardIsLoading}
129+
type="default"
103130
>
104-
<span>
105-
{haveTraefikDashboardPortEnabled ? "Disable" : "Enable"} Dashboard
106-
</span>
107-
</DropdownMenuItem>
131+
<DropdownMenuItem
132+
onSelect={(e) => e.preventDefault()}
133+
className="w-full cursor-pointer space-x-3"
134+
>
135+
<span>
136+
{haveTraefikDashboardPortEnabled ? "Disable" : "Enable"}{" "}
137+
Dashboard
138+
</span>
139+
</DropdownMenuItem>
140+
</DialogAction>
108141
<ManageTraefikPorts serverId={serverId}>
109142
<DropdownMenuItem
110143
onSelect={(e) => e.preventDefault()}

apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
105105
});
106106
toast.success(t("settings.server.webServer.traefik.portsUpdated"));
107107
setOpen(false);
108-
} catch {}
108+
} catch (error) {
109+
toast.error((error as Error).message || "Error updating Traefik ports");
110+
}
109111
};
110112

111113
return (
@@ -156,11 +158,11 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
156158
</p>
157159
</div>
158160
) : (
159-
<ScrollArea className="h-[400px] pr-4">
161+
<ScrollArea className="pr-4">
160162
<div className="grid gap-4">
161163
{fields.map((field, index) => (
162164
<Card key={field.id} className="bg-transparent">
163-
<CardContent className="grid grid-cols-[1fr_1fr_1fr_auto] gap-4 p-4 transparent">
165+
<CardContent className="grid grid-cols-4 gap-4 p-4 transparent">
164166
<FormField
165167
control={form.control}
166168
name={`ports.${index}.targetPort`}
@@ -303,6 +305,12 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
303305
</div>
304306
</AlertBlock>
305307
)}
308+
309+
<AlertBlock type="warning">
310+
The Traefik container will be recreated from scratch. This
311+
means the container will be deleted and created again, which
312+
may cause downtime in your applications.
313+
</AlertBlock>
306314
</div>
307315
<DialogFooter>
308316
<Button

apps/dokploy/server/api/routers/settings.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
canAccessToTraefikFiles,
33
checkGPUStatus,
4+
checkPortInUse,
45
cleanStoppedContainers,
56
cleanUpDockerBuilder,
67
cleanUpSystemPrune,
@@ -130,6 +131,17 @@ export const settingsRouter = createTRPCRouter({
130131
let newPorts = ports;
131132
// If receive true, add 8080 to ports
132133
if (input.enableDashboard) {
134+
// Check if port 8080 is already in use before enabling dashboard
135+
const portCheck = await checkPortInUse(8080, input.serverId);
136+
if (portCheck.isInUse) {
137+
const conflictingContainer = portCheck.conflictingContainer
138+
? ` by container "${portCheck.conflictingContainer}"`
139+
: "";
140+
throw new TRPCError({
141+
code: "CONFLICT",
142+
message: `Port 8080 is already in use${conflictingContainer}. Please stop the conflicting service or use a different port for the Traefik dashboard.`,
143+
});
144+
}
133145
newPorts.push({
134146
targetPort: 8080,
135147
publishedPort: 8080,
@@ -810,6 +822,19 @@ export const settingsRouter = createTRPCRouter({
810822
"dokploy-traefik",
811823
input?.serverId,
812824
);
825+
826+
for (const port of input.additionalPorts) {
827+
const portCheck = await checkPortInUse(
828+
port.publishedPort,
829+
input.serverId,
830+
);
831+
if (portCheck.isInUse) {
832+
throw new TRPCError({
833+
code: "CONFLICT",
834+
message: `Port ${port.targetPort} is already in use by ${portCheck.conflictingContainer}`,
835+
});
836+
}
837+
}
813838
const preparedEnv = prepareEnvironmentVariables(env);
814839

815840
await writeTraefikSetup({

packages/server/src/services/settings.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,28 @@ export const readPorts = async (
392392
);
393393
};
394394

395+
export const checkPortInUse = async (
396+
port: number,
397+
serverId?: string,
398+
): Promise<{ isInUse: boolean; conflictingContainer?: string }> => {
399+
try {
400+
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`;
401+
const { stdout } = serverId
402+
? await execAsyncRemote(serverId, command)
403+
: await execAsync(command);
404+
405+
const container = stdout.trim();
406+
407+
return {
408+
isInUse: !!container,
409+
conflictingContainer: container || undefined,
410+
};
411+
} catch (error) {
412+
console.error("Error checking port availability:", error);
413+
return { isInUse: false };
414+
}
415+
};
416+
395417
export const writeTraefikSetup = async (input: TraefikOptions) => {
396418
const resourceType = await getDockerResourceType(
397419
"dokploy-traefik",

0 commit comments

Comments
 (0)