fix: improve error handling for Traefik port updates and enhance port availability checks

This commit is contained in:
Mauricio Siu
2025-12-06 13:58:03 -06:00
parent 1ba24630a8
commit 439fba1f4b
4 changed files with 28 additions and 63 deletions
@@ -105,7 +105,9 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
}); });
toast.success(t("settings.server.webServer.traefik.portsUpdated")); toast.success(t("settings.server.webServer.traefik.portsUpdated"));
setOpen(false); setOpen(false);
} catch {} } catch (error) {
toast.error((error as Error).message || "Error updating Traefik ports");
}
}; };
return ( return (
@@ -822,6 +822,19 @@ export const settingsRouter = createTRPCRouter({
"dokploy-traefik", "dokploy-traefik",
input?.serverId, 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); const preparedEnv = prepareEnvironmentVariables(env);
await writeTraefikSetup({ await writeTraefikSetup({
+3 -3
View File
@@ -290,10 +290,10 @@ export const getContainersByAppLabel = async (
const command = const command =
type === "swarm" 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" : type === "standalone"
? `docker ps -a --filter "name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'` ? `docker ps --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 "label=com.docker.compose.project=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`;
if (serverId) { if (serverId) {
const result = await execAsyncRemote(serverId, command); const result = await execAsyncRemote(serverId, command);
stdout = result.stdout; stdout = result.stdout;
+9 -59
View File
@@ -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 ( export const checkPortInUse = async (
port: number, port: number,
serverId?: string, serverId?: string,
): Promise<{ isInUse: boolean; conflictingContainer?: string }> => { ): Promise<{ isInUse: boolean; conflictingContainer?: string }> => {
try { try {
// Method 1: Check all containers and inspect their port bindings 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 listContainersCommand = `docker ps -a --format '{{.Names}}'`; const { stdout } = serverId
let containersList = ""; ? await execAsyncRemote(serverId, command)
if (serverId) { : await execAsync(command);
const result = await execAsyncRemote(serverId, listContainersCommand);
containersList = result.stdout.trim();
} else {
const result = await execAsync(listContainersCommand);
containersList = result.stdout.trim();
}
const containerNames = containersList const container = stdout.trim();
.split("\n")
.filter((name) => name && name !== "dokploy-traefik");
// Check each container's port bindings return {
for (const containerName of containerNames) { isInUse: !!container,
const portCheckCommand = `docker port ${containerName} 2>/dev/null | grep ':${port}' || true`; conflictingContainer: container || undefined,
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) { } 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); console.error("Error checking port availability:", error);
return { isInUse: false }; return { isInUse: false };
} }