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

View File

@@ -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 (

View File

@@ -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({

View File

@@ -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;

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 (
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 };
}