From 203da1a8fe25a5c2ab66738cf07a0c7e3b768427 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Tue, 25 Feb 2025 22:51:02 -0600 Subject: [PATCH 1/9] refactor(traefik): migrate from Docker Swarm service to standalone container --- packages/server/src/setup/traefik-setup.ts | 143 +++++++++------------ 1 file changed, 59 insertions(+), 84 deletions(-) diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts index 21caa5cf2..5a904eb89 100644 --- a/packages/server/src/setup/traefik-setup.ts +++ b/packages/server/src/setup/traefik-setup.ts @@ -1,6 +1,6 @@ import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs"; import path from "node:path"; -import type { ContainerTaskSpec, CreateServiceOptions } from "dockerode"; +import type { ContainerCreateOptions } from "dockerode"; import { dump } from "js-yaml"; import { paths } from "../constants"; import { pullImage, pullRemoteImage } from "../utils/docker/utils"; @@ -34,68 +34,53 @@ export const initializeTraefik = async ({ const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId); const imageName = `traefik:v${TRAEFIK_VERSION}`; const containerName = "dokploy-traefik"; - const settings: CreateServiceOptions = { - Name: containerName, - TaskTemplate: { - ContainerSpec: { - Image: imageName, - Env: env, - Mounts: [ + const settings: ContainerCreateOptions = { + name: containerName, + Image: imageName, + NetworkingConfig: { + EndpointsConfig: { + "dokploy-network": {}, + }, + }, + HostConfig: { + RestartPolicy: { + Name: "always", + }, + Binds: [ + `${MAIN_TRAEFIK_PATH}/traefik.yml:/etc/traefik/traefik.yml`, + `${DYNAMIC_TRAEFIK_PATH}:/etc/dokploy/traefik/dynamic`, + "/var/run/docker.sock:/var/run/docker.sock", + ], + PortBindings: { + [`${TRAEFIK_SSL_PORT}/tcp`]: [ { - Type: "bind", - Source: `${MAIN_TRAEFIK_PATH}/traefik.yml`, - Target: "/etc/traefik/traefik.yml", - }, - { - Type: "bind", - Source: DYNAMIC_TRAEFIK_PATH, - Target: "/etc/dokploy/traefik/dynamic", - }, - { - Type: "bind", - Source: "/var/run/docker.sock", - Target: "/var/run/docker.sock", + HostPort: TRAEFIK_SSL_PORT.toString(), }, ], - }, - Networks: [{ Target: "dokploy-network" }], - Placement: { - Constraints: ["node.role==manager"], - }, - }, - Mode: { - Replicated: { - Replicas: 1, - }, - }, - EndpointSpec: { - Ports: [ - { - TargetPort: 443, - PublishedPort: TRAEFIK_SSL_PORT, - PublishMode: "host", - }, - { - TargetPort: 80, - PublishedPort: TRAEFIK_PORT, - PublishMode: "host", - }, - ...(enableDashboard - ? [ + [`${TRAEFIK_PORT}/tcp`]: [ + { + HostPort: TRAEFIK_PORT.toString(), + }, + ], + ...(enableDashboard && { + [`${8080}/tcp`]: [ + { + HostPort: "8080", + }, + ], + }), + ...additionalPorts.map((port) => { + return { + [`${port.targetPort}/tcp`]: [ { - TargetPort: 8080, - PublishedPort: 8080, - PublishMode: "host" as const, + HostPort: port.publishedPort.toString(), }, - ] - : []), - ...additionalPorts.map((port) => ({ - TargetPort: port.targetPort, - PublishedPort: port.publishedPort, - PublishMode: port.publishMode || ("host" as const), - })), - ], + ], + }; + }), + }, }, + Env: env, }; const docker = await getRemoteDocker(serverId); try { @@ -105,38 +90,28 @@ export const initializeTraefik = async ({ await pullImage(imageName); } - const service = docker.getService(containerName); - const inspect = await service.inspect(); + const container = docker.getContainer(containerName); + try { + const inspect = await container.inspect(); + if (inspect.State.Status === "running") { + console.log("Traefik already running"); + return; + } - const existingEnv = inspect.Spec.TaskTemplate.ContainerSpec.Env || []; - const updatedEnv = !env ? existingEnv : env; + await container.remove({ force: true }); + console.log("Removed existing container"); + } catch (error) { + console.log("Traefik Not Found: Starting1 ✅"); + console.log(error); + } - const updatedSettings = { - ...settings, - TaskTemplate: { - ...settings.TaskTemplate, - ContainerSpec: { - ...(settings?.TaskTemplate as ContainerTaskSpec).ContainerSpec, - Env: updatedEnv, - }, - }, - }; - await service.update({ - version: Number.parseInt(inspect.Version.Index), - ...updatedSettings, - }); + await docker.createContainer(settings); + const newContainer = docker.getContainer(containerName); + await newContainer.start(); console.log("Traefik Started ✅"); } catch (error) { - try { - await docker.createService(settings); - } catch (error: any) { - if (error?.statusCode !== 409) { - throw error; - } - console.log("Traefik service already exists, continuing..."); - } - console.log("Traefik Not Found: Starting ✅"); + console.log("Traefik Not Found: Starting2 ✅", error); } }; From 29c1e4691ea9ed41b01c20ba8981224ea147ba72 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Tue, 25 Feb 2025 23:04:19 -0600 Subject: [PATCH 2/9] feat(docker): add support for standalone container log retrieval --- .../settings/servers/actions/show-traefik-actions.tsx | 6 +++++- .../dashboard/settings/web-server/show-modal-logs.tsx | 9 ++++++++- apps/dokploy/server/api/routers/docker.ts | 7 ++++++- packages/server/src/services/docker.ts | 8 +++++++- 4 files changed, 26 insertions(+), 4 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 17a6ae757..f9b799476 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 @@ -79,7 +79,11 @@ export const ShowTraefikActions = ({ serverId }: Props) => { > {t("settings.server.webServer.reload")} - + e.preventDefault()} className="cursor-pointer" diff --git a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx index 12e7b6704..f6d17f9b2 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx @@ -37,13 +37,20 @@ interface Props { appName: string; children?: React.ReactNode; serverId?: string; + type: "standalone" | "swarm"; } -export const ShowModalLogs = ({ appName, children, serverId }: Props) => { +export const ShowModalLogs = ({ + appName, + children, + serverId, + type = "swarm", +}: Props) => { const { data, isLoading } = api.docker.getContainersByAppLabel.useQuery( { appName, serverId, + type, }, { enabled: !!appName, diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts index f6972e16b..3de59058c 100644 --- a/apps/dokploy/server/api/routers/docker.ts +++ b/apps/dokploy/server/api/routers/docker.ts @@ -65,10 +65,15 @@ export const dockerRouter = createTRPCRouter({ z.object({ appName: z.string().min(1), serverId: z.string().optional(), + type: z.enum(["standalone", "swarm"]), }), ) .query(async ({ input }) => { - return await getContainersByAppLabel(input.appName, input.serverId); + return await getContainersByAppLabel( + input.appName, + input.type, + input.serverId, + ); }), getStackContainersByAppName: protectedProcedure diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index 597c03fa1..fbb51192a 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -281,13 +281,19 @@ export const getServiceContainersByAppName = async ( export const getContainersByAppLabel = async ( appName: string, + type: "standalone" | "swarm", serverId?: string, ) => { try { let stdout = ""; let stderr = ""; - const command = `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`; + const command = + type === "swarm" + ? `docker ps --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}}'`; if (serverId) { const result = await execAsyncRemote(serverId, command); stdout = result.stdout; 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 3/9] 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 bd168edcd..b43686bde 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 fc1255fcf..03711fe15 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 dc760407e..0e2a18117 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 5a904eb89..73ebaa757 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; } }; From 2ae14c65cfa8d832db115b8fde3de15ec5e81d88 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Mar 2025 03:24:29 -0600 Subject: [PATCH 4/9] refactor(web-server): make type prop optional in ShowModalLogs component - Update type prop to be optional in the Props interface - Improve component flexibility by allowing undefined type --- .../dashboard/settings/web-server/show-modal-logs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx index 677db9060..43b1838de 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx @@ -36,7 +36,7 @@ interface Props { appName: string; children?: React.ReactNode; serverId?: string; - type: "standalone" | "swarm"; + type?: "standalone" | "swarm"; } export const ShowModalLogs = ({ From d2e053635558ca7b9b914a2c01b8d3d5a91f97a8 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Mar 2025 04:31:06 -0600 Subject: [PATCH 5/9] feat(traefik): add HTTP/3 support with UDP port configuration - Introduce TRAEFIK_HTTP3_PORT environment variable - Configure UDP port binding for HTTP/3 - Enable HTTP/3 with advertisedPort in Traefik websecure configuration --- packages/server/src/setup/traefik-setup.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts index 73ebaa757..c2e8a6159 100644 --- a/packages/server/src/setup/traefik-setup.ts +++ b/packages/server/src/setup/traefik-setup.ts @@ -12,6 +12,8 @@ export const TRAEFIK_SSL_PORT = Number.parseInt(process.env.TRAEFIK_SSL_PORT!, 10) || 443; export const TRAEFIK_PORT = Number.parseInt(process.env.TRAEFIK_PORT!, 10) || 80; +export const TRAEFIK_HTTP3_PORT = + Number.parseInt(process.env.TRAEFIK_HTTP3_PORT!, 10) || 443; export const TRAEFIK_VERSION = process.env.TRAEFIK_VERSION || "3.1.2"; interface TraefikOptions { @@ -40,11 +42,15 @@ export const initializeTraefik = async ({ const exposedPorts: Record = { [`${TRAEFIK_PORT}/tcp`]: {}, [`${TRAEFIK_SSL_PORT}/tcp`]: {}, + [`${TRAEFIK_HTTP3_PORT}/udp`]: {}, }; const portBindings: Record> = { [`${TRAEFIK_PORT}/tcp`]: [{ HostPort: TRAEFIK_PORT.toString() }], [`${TRAEFIK_SSL_PORT}/tcp`]: [{ HostPort: TRAEFIK_SSL_PORT.toString() }], + [`${TRAEFIK_HTTP3_PORT}/udp`]: [ + { HostPort: TRAEFIK_HTTP3_PORT.toString() }, + ], }; if (enableDashboard) { @@ -187,6 +193,9 @@ export const getDefaultTraefikConfig = () => { }, websecure: { address: `:${TRAEFIK_SSL_PORT}`, + http3: { + advertisedPort: TRAEFIK_HTTP3_PORT, + }, ...(process.env.NODE_ENV === "production" && { http: { tls: { From bf04dfa757ca6474b1d5381c30b41e9a06cd8ed0 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Mar 2025 04:35:58 -0600 Subject: [PATCH 6/9] feat(traefik): add HTTP/3 support with UDP port configuration - Introduce TRAEFIK_HTTP3_PORT environment variable - Configure UDP port binding for HTTP/3 - Enable HTTP/3 with advertisedPort in Traefik websecure configuration --- packages/server/src/setup/server-setup.ts | 3 ++- packages/server/src/setup/traefik-setup.ts | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts index 0e2a18117..fde7a1a8b 100644 --- a/packages/server/src/setup/server-setup.ts +++ b/packages/server/src/setup/server-setup.ts @@ -9,6 +9,7 @@ import { TRAEFIK_PORT, TRAEFIK_SSL_PORT, TRAEFIK_VERSION, + TRAEFIK_HTTP3_PORT, getDefaultMiddlewares, getDefaultServerTraefikConfig, } from "@dokploy/server/setup/traefik-setup"; @@ -551,9 +552,9 @@ export const createTraefikInstance = () => { -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 \ -p ${TRAEFIK_SSL_PORT}:${TRAEFIK_SSL_PORT} \ -p ${TRAEFIK_PORT}:${TRAEFIK_PORT} \ + -p ${TRAEFIK_HTTP3_PORT}:${TRAEFIK_HTTP3_PORT}/udp \ 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 c2e8a6159..1778d319f 100644 --- a/packages/server/src/setup/traefik-setup.ts +++ b/packages/server/src/setup/traefik-setup.ts @@ -251,6 +251,9 @@ export const getDefaultServerTraefikConfig = () => { }, websecure: { address: `:${TRAEFIK_SSL_PORT}`, + http3: { + advertisedPort: TRAEFIK_HTTP3_PORT, + }, http: { tls: { certResolver: "letsencrypt", From 8063673a7c9c59c16ce9ff14a29579c04b10ebfa Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Mar 2025 05:17:42 -0600 Subject: [PATCH 7/9] refactor(traefik): streamline container removal and service management - Remove dokploy-service before Traefik container initialization - Simplify error handling and logging during container setup - Add support for remote server service removal --- packages/server/src/setup/traefik-setup.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts index 1778d319f..7f124234b 100644 --- a/packages/server/src/setup/traefik-setup.ts +++ b/packages/server/src/setup/traefik-setup.ts @@ -7,6 +7,7 @@ import { pullImage, pullRemoteImage } from "../utils/docker/utils"; import { getRemoteDocker } from "../utils/servers/remote-docker"; import type { FileConfig } from "../utils/traefik/file-types"; import type { MainTraefikConfig } from "../utils/traefik/types"; +import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; export const TRAEFIK_SSL_PORT = Number.parseInt(process.env.TRAEFIK_SSL_PORT!, 10) || 443; @@ -95,6 +96,14 @@ export const initializeTraefik = async ({ await pullImage(imageName); } + // remove dokploy-service if it exists + const command = "docker service rm dokploy-service > /dev/null 2>&1"; + if (serverId) { + await execAsyncRemote(command, serverId); + } else { + await execAsync(command); + } + const container = docker.getContainer(containerName); try { const inspect = await container.inspect(); @@ -104,19 +113,14 @@ export const initializeTraefik = async ({ } await container.remove({ force: true }); - console.log("Removed existing container"); } catch (error) { - console.log("Traefik Not Found: Starting1 ✅"); console.log(error); } await docker.createContainer(settings); const newContainer = docker.getContainer(containerName); await newContainer.start(); - - console.log("Traefik Started ✅"); } catch (error) { - console.log("Traefik Not Found: Starting2 ✅", error); throw error; } }; From 988e5cb23eaf44f7b3fb6cdb462d77640e637c76 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 9 Mar 2025 01:14:45 -0600 Subject: [PATCH 8/9] fix(traefik): improve error handling in container startup Log Traefik container startup errors instead of throwing, preventing potential deployment interruptions --- packages/server/src/setup/traefik-setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts index 7f124234b..c76a1f80e 100644 --- a/packages/server/src/setup/traefik-setup.ts +++ b/packages/server/src/setup/traefik-setup.ts @@ -121,7 +121,7 @@ export const initializeTraefik = async ({ const newContainer = docker.getContainer(containerName); await newContainer.start(); } catch (error) { - throw error; + console.log(error); } }; From b7f63fdad438839a42263374492b00692873ad4c Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 9 Mar 2025 02:32:02 -0600 Subject: [PATCH 9/9] refactor(traefik): improve migration and removal of Traefik services - Update Traefik service detection and removal logic in server and traefik setup - Use Docker service and container inspection methods for more robust service management - Add graceful migration and removal of existing Traefik services - Simplify image pulling and service removal process --- packages/server/src/setup/server-setup.ts | 9 ++++++++- packages/server/src/setup/traefik-setup.ts | 20 +++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts index 24cf76b9a..e129dce10 100644 --- a/packages/server/src/setup/server-setup.ts +++ b/packages/server/src/setup/server-setup.ts @@ -543,7 +543,14 @@ export const installRClone = () => ` export const createTraefikInstance = () => { const command = ` # Check if dokpyloy-traefik exists - if docker ps -a --format '{{.Names}}' | grep -q '^dokploy-traefik$'; then + if docker service inspect dokploy-traefik > /dev/null 2>&1; then + echo "Migrating Traefik to Standalone..." + docker service rm dokploy-traefik + sleep 7 + echo "Traefik migrated to Standalone ✅" + fi + + if docker inspect dokploy-traefik > /dev/null 2>&1; then echo "Traefik already exists ✅" else # Create the dokploy-traefik container diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts index c76a1f80e..7f2b707d0 100644 --- a/packages/server/src/setup/traefik-setup.ts +++ b/packages/server/src/setup/traefik-setup.ts @@ -3,11 +3,9 @@ import path from "node:path"; import type { ContainerCreateOptions } from "dockerode"; import { dump } from "js-yaml"; import { paths } from "../constants"; -import { pullImage, pullRemoteImage } from "../utils/docker/utils"; import { getRemoteDocker } from "../utils/servers/remote-docker"; import type { FileConfig } from "../utils/traefik/file-types"; import type { MainTraefikConfig } from "../utils/traefik/types"; -import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; export const TRAEFIK_SSL_PORT = Number.parseInt(process.env.TRAEFIK_SSL_PORT!, 10) || 443; @@ -90,19 +88,11 @@ export const initializeTraefik = async ({ const docker = await getRemoteDocker(serverId); try { - if (serverId) { - await pullRemoteImage(imageName, serverId); - } else { - await pullImage(imageName); - } - - // remove dokploy-service if it exists - const command = "docker service rm dokploy-service > /dev/null 2>&1"; - if (serverId) { - await execAsyncRemote(command, serverId); - } else { - await execAsync(command); - } + try { + const service = docker.getService("dokploy-traefik"); + await service?.remove({ force: true }); + await new Promise((resolve) => setTimeout(resolve, 5000)); + } catch (_) {} const container = docker.getContainer(containerName); try {