From 7f8f6ac64cd83e45e50cac1528cd5b4464bcc18f Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Mar 2025 03:14:54 -0600 Subject: [PATCH] refactor(traefik): migrate from Docker Swarm to standalone container - Replace Docker service commands with standalone container management - Update Traefik initialization to use container-based deployment - Modify port inspection and environment variable retrieval methods - Improve container creation and port binding logic - Remove Swarm-specific constraints and deployment strategies --- .../servers/actions/show-traefik-actions.tsx | 9 -- apps/dokploy/server/api/routers/settings.ts | 95 +++++++++++-------- packages/server/src/setup/server-setup.ts | 19 ++-- packages/server/src/setup/traefik-setup.ts | 58 +++++------ 4 files changed, 91 insertions(+), 90 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 bd168edc..b43686bd 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 @@ -112,15 +112,6 @@ export const ShowTraefikActions = ({ serverId }: Props) => { {haveTraefikDashboardPortEnabled ? "Disable" : "Enable"} Dashboard - {/* - - e.preventDefault()} - > - Enter the terminal - - */} e.preventDefault()} diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index fc1255fc..03711fe1 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -42,10 +42,6 @@ import { recreateDirectory, sendDockerCleanupNotifications, spawnAsync, - startService, - startServiceRemote, - stopService, - stopServiceRemote, updateLetsEncryptEmail, updateServerById, updateServerTraefik, @@ -86,11 +82,9 @@ export const settingsRouter = createTRPCRouter({ .mutation(async ({ input }) => { try { if (input?.serverId) { - await stopServiceRemote(input.serverId, "dokploy-traefik"); - await startServiceRemote(input.serverId, "dokploy-traefik"); + await execAsync("docker restart dokploy-traefik"); } else if (!IS_CLOUD) { - await stopService("dokploy-traefik"); - await startService("dokploy-traefik"); + await execAsync("docker restart dokploy-traefik"); } } catch (err) { console.error(err); @@ -104,6 +98,7 @@ export const settingsRouter = createTRPCRouter({ await initializeTraefik({ enableDashboard: input.enableDashboard, serverId: input.serverId, + force: true, }); return true; }), @@ -511,16 +506,18 @@ export const settingsRouter = createTRPCRouter({ .input(apiServerSchema) .query(async ({ input }) => { const command = - "docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik"; + "docker container inspect dokploy-traefik --format '{{json .Config.Env}}'"; + let result = ""; if (input?.serverId) { - const result = await execAsyncRemote(input.serverId, command); - return result.stdout.trim(); - } - if (!IS_CLOUD) { - const result = await execAsync(command); - return result.stdout.trim(); + const execResult = await execAsyncRemote(input.serverId, command); + result = execResult.stdout; + } else { + const execResult = await execAsync(command); + result = execResult.stdout; } + const envVars = JSON.parse(result.trim()); + return envVars.join("\n"); }), writeTraefikEnv: adminProcedure @@ -530,6 +527,7 @@ export const settingsRouter = createTRPCRouter({ await initializeTraefik({ env: envs, serverId: input.serverId, + force: true, }); return true; @@ -537,27 +535,22 @@ export const settingsRouter = createTRPCRouter({ haveTraefikDashboardPortEnabled: adminProcedure .input(apiServerSchema) .query(async ({ input }) => { - const command = `docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik`; + const command = `docker container inspect --format='{{json .NetworkSettings.Ports}}' dokploy-traefik`; let stdout = ""; if (input?.serverId) { const result = await execAsyncRemote(input.serverId, command); stdout = result.stdout; } else if (!IS_CLOUD) { - const result = await execAsync( - "docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik", - ); + const result = await execAsync(command); stdout = result.stdout; } - const parsed: any[] = JSON.parse(stdout.trim()); - for (const port of parsed) { - if (port.PublishedPort === 8080) { - return true; - } - } - - return false; + const ports = JSON.parse(stdout.trim()); + return Object.entries(ports).some(([containerPort, bindings]) => { + const [port] = containerPort.split("/"); + return port === "8080" && bindings && (bindings as any[]).length > 0; + }); }), readStatsLogs: adminProcedure @@ -767,6 +760,7 @@ export const settingsRouter = createTRPCRouter({ await initializeTraefik({ serverId: input.serverId, additionalPorts: input.additionalPorts, + force: true, }); return true; } catch (error) { @@ -783,7 +777,7 @@ export const settingsRouter = createTRPCRouter({ getTraefikPorts: adminProcedure .input(apiServerSchema) .query(async ({ input }) => { - const command = `docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik`; + const command = `docker container inspect --format='{{json .NetworkSettings.Ports}}' dokploy-traefik`; try { let stdout = ""; @@ -795,21 +789,38 @@ export const settingsRouter = createTRPCRouter({ stdout = result.stdout; } - const ports: { - Protocol: string; - TargetPort: number; - PublishedPort: number; - PublishMode: string; - }[] = JSON.parse(stdout.trim()); + const portsMap = JSON.parse(stdout.trim()); + const additionalPorts: Array<{ + targetPort: number; + publishedPort: number; + publishMode: "host" | "ingress"; + }> = []; - // Filter out the default ports (80, 443, and optionally 8080) - const additionalPorts = ports - .filter((port) => ![80, 443, 8080].includes(port.PublishedPort)) - .map((port) => ({ - targetPort: port.TargetPort, - publishedPort: port.PublishedPort, - publishMode: port.PublishMode.toLowerCase() as "host" | "ingress", - })); + // Convert the Docker container port format to our expected format + for (const [containerPort, bindings] of Object.entries(portsMap)) { + if (!bindings) continue; + + const [port = ""] = containerPort.split("/"); + if (!port) continue; + + const targetPortNum = Number.parseInt(port, 10); + if (Number.isNaN(targetPortNum)) continue; + + // Skip default ports + if ([80, 443, 8080].includes(targetPortNum)) continue; + + for (const binding of bindings as Array<{ HostPort: string }>) { + if (!binding.HostPort) continue; + const publishedPort = Number.parseInt(binding.HostPort, 10); + if (Number.isNaN(publishedPort)) continue; + + additionalPorts.push({ + targetPort: targetPortNum, + publishedPort, + publishMode: "host", // Docker standalone uses host mode by default + }); + } + } return additionalPorts; } catch (error) { diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts index dc760407..0e2a1811 100644 --- a/packages/server/src/setup/server-setup.ts +++ b/packages/server/src/setup/server-setup.ts @@ -539,22 +539,21 @@ export const installRClone = () => ` export const createTraefikInstance = () => { const command = ` # Check if dokpyloy-traefik exists - if docker service ls | grep -q 'dokploy-traefik'; then + if docker ps -a --format '{{.Names}}' | grep -q '^dokploy-traefik$'; then echo "Traefik already exists ✅" else - # Create the dokploy-traefik service + # Create the dokploy-traefik container TRAEFIK_VERSION=${TRAEFIK_VERSION} - docker service create \ + docker run -d \ --name dokploy-traefik \ - --replicas 1 \ - --constraint 'node.role==manager' \ --network dokploy-network \ - --mount type=bind,src=/etc/dokploy/traefik/traefik.yml,dst=/etc/traefik/traefik.yml \ - --mount type=bind,src=/etc/dokploy/traefik/dynamic,dst=/etc/dokploy/traefik/dynamic \ - --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \ + --restart unless-stopped \ + -v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \ + -v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \ + -v /var/run/docker.sock:/var/run/docker.sock \ --label traefik.enable=true \ - --publish mode=host,target=${TRAEFIK_SSL_PORT},published=${TRAEFIK_SSL_PORT} \ - --publish mode=host,target=${TRAEFIK_PORT},published=${TRAEFIK_PORT} \ + -p ${TRAEFIK_SSL_PORT}:${TRAEFIK_SSL_PORT} \ + -p ${TRAEFIK_PORT}:${TRAEFIK_PORT} \ traefik:v$TRAEFIK_VERSION echo "Traefik version $TRAEFIK_VERSION installed ✅" fi diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts index 5a904eb8..73ebaa75 100644 --- a/packages/server/src/setup/traefik-setup.ts +++ b/packages/server/src/setup/traefik-setup.ts @@ -23,6 +23,7 @@ interface TraefikOptions { publishedPort: number; publishMode?: "ingress" | "host"; }[]; + force?: boolean; } export const initializeTraefik = async ({ @@ -30,10 +31,33 @@ export const initializeTraefik = async ({ env, serverId, additionalPorts = [], + force = false, }: TraefikOptions = {}) => { const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId); const imageName = `traefik:v${TRAEFIK_VERSION}`; const containerName = "dokploy-traefik"; + + const exposedPorts: Record = { + [`${TRAEFIK_PORT}/tcp`]: {}, + [`${TRAEFIK_SSL_PORT}/tcp`]: {}, + }; + + const portBindings: Record> = { + [`${TRAEFIK_PORT}/tcp`]: [{ HostPort: TRAEFIK_PORT.toString() }], + [`${TRAEFIK_SSL_PORT}/tcp`]: [{ HostPort: TRAEFIK_SSL_PORT.toString() }], + }; + + if (enableDashboard) { + exposedPorts["8080/tcp"] = {}; + portBindings["8080/tcp"] = [{ HostPort: "8080" }]; + } + + for (const port of additionalPorts) { + const portKey = `${port.targetPort}/tcp`; + exposedPorts[portKey] = {}; + portBindings[portKey] = [{ HostPort: port.publishedPort.toString() }]; + } + const settings: ContainerCreateOptions = { name: containerName, Image: imageName, @@ -42,6 +66,7 @@ export const initializeTraefik = async ({ "dokploy-network": {}, }, }, + ExposedPorts: exposedPorts, HostConfig: { RestartPolicy: { Name: "always", @@ -51,37 +76,11 @@ export const initializeTraefik = async ({ `${DYNAMIC_TRAEFIK_PATH}:/etc/dokploy/traefik/dynamic`, "/var/run/docker.sock:/var/run/docker.sock", ], - PortBindings: { - [`${TRAEFIK_SSL_PORT}/tcp`]: [ - { - HostPort: TRAEFIK_SSL_PORT.toString(), - }, - ], - [`${TRAEFIK_PORT}/tcp`]: [ - { - HostPort: TRAEFIK_PORT.toString(), - }, - ], - ...(enableDashboard && { - [`${8080}/tcp`]: [ - { - HostPort: "8080", - }, - ], - }), - ...additionalPorts.map((port) => { - return { - [`${port.targetPort}/tcp`]: [ - { - HostPort: port.publishedPort.toString(), - }, - ], - }; - }), - }, + PortBindings: portBindings, }, Env: env, }; + const docker = await getRemoteDocker(serverId); try { if (serverId) { @@ -93,7 +92,7 @@ export const initializeTraefik = async ({ const container = docker.getContainer(containerName); try { const inspect = await container.inspect(); - if (inspect.State.Status === "running") { + if (inspect.State.Status === "running" && !force) { console.log("Traefik already running"); return; } @@ -112,6 +111,7 @@ export const initializeTraefik = async ({ console.log("Traefik Started ✅"); } catch (error) { console.log("Traefik Not Found: Starting2 ✅", error); + throw error; } };