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 0968931d7..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
@@ -67,7 +67,11 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
>
{t("settings.server.webServer.reload")}
-
+
e.preventDefault()}
className="cursor-pointer"
@@ -108,15 +112,6 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
{haveTraefikDashboardPortEnabled ? "Disable" : "Enable"} Dashboard
- {/*
-
- e.preventDefault()}
- >
- Enter the terminal
-
- */}
e.preventDefault()}
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 bedecf517..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,13 +36,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/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index ac05d0fc9..461d3e1f2 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -41,10 +41,6 @@ import {
recreateDirectory,
sendDockerCleanupNotifications,
spawnAsync,
- startService,
- startServiceRemote,
- stopService,
- stopServiceRemote,
updateLetsEncryptEmail,
updateServerById,
updateServerTraefik,
@@ -88,11 +84,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);
@@ -106,6 +100,7 @@ export const settingsRouter = createTRPCRouter({
await initializeTraefik({
enableDashboard: input.enableDashboard,
serverId: input.serverId,
+ force: true,
});
return true;
}),
@@ -513,16 +508,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
@@ -532,6 +529,7 @@ export const settingsRouter = createTRPCRouter({
await initializeTraefik({
env: envs,
serverId: input.serverId,
+ force: true,
});
return true;
@@ -539,27 +537,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
@@ -772,6 +765,7 @@ export const settingsRouter = createTRPCRouter({
await initializeTraefik({
serverId: input.serverId,
additionalPorts: input.additionalPorts,
+ force: true,
});
return true;
} catch (error) {
@@ -788,7 +782,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 = "";
@@ -800,21 +794,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/services/docker.ts b/packages/server/src/services/docker.ts
index d2f4de53b..69ae446a9 100644
--- a/packages/server/src/services/docker.ts
+++ b/packages/server/src/services/docker.ts
@@ -287,13 +287,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;
diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts
index 4f2335b2e..e129dce10 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";
@@ -542,22 +543,28 @@ export const installRClone = () => `
export const createTraefikInstance = () => {
const command = `
# Check if dokpyloy-traefik exists
- if docker service ls | 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 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 \
- --label traefik.enable=true \
- --publish mode=host,target=${TRAEFIK_SSL_PORT},published=${TRAEFIK_SSL_PORT} \
- --publish mode=host,target=${TRAEFIK_PORT},published=${TRAEFIK_PORT} \
+ --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 \
+ -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 e8d019424..7f2b707d0 100644
--- a/packages/server/src/setup/traefik-setup.ts
+++ b/packages/server/src/setup/traefik-setup.ts
@@ -1,9 +1,8 @@
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";
import { getRemoteDocker } from "../utils/servers/remote-docker";
import type { FileConfig } from "../utils/traefik/file-types";
import type { MainTraefikConfig } from "../utils/traefik/types";
@@ -12,6 +11,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 {
@@ -23,6 +24,7 @@ interface TraefikOptions {
publishedPort: number;
publishMode?: "ingress" | "host";
}[];
+ force?: boolean;
}
export const initializeTraefik = async ({
@@ -30,113 +32,86 @@ 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 settings: CreateServiceOptions = {
- Name: containerName,
- TaskTemplate: {
- ContainerSpec: {
- Image: imageName,
- Env: env,
- Mounts: [
- {
- 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",
- },
- ],
- },
- 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
- ? [
- {
- TargetPort: 8080,
- PublishedPort: 8080,
- PublishMode: "host" as const,
- },
- ]
- : []),
- ...additionalPorts.map((port) => ({
- TargetPort: port.targetPort,
- PublishedPort: port.publishedPort,
- PublishMode: port.publishMode || ("host" as const),
- })),
- ],
- },
+
+ 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) {
+ 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,
+ NetworkingConfig: {
+ EndpointsConfig: {
+ "dokploy-network": {},
+ },
+ },
+ ExposedPorts: exposedPorts,
+ 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: portBindings,
+ },
+ Env: env,
+ };
+
const docker = await getRemoteDocker(serverId);
try {
- if (serverId) {
- await pullRemoteImage(imageName, serverId);
- } else {
- await pullImage(imageName);
- }
-
- const service = docker.getService(containerName);
- const inspect = await service.inspect();
-
- const existingEnv = inspect.Spec.TaskTemplate.ContainerSpec.Env || [];
- const updatedEnv = !env ? existingEnv : env;
-
- const updatedSettings = {
- ...settings,
- TaskTemplate: {
- ...settings.TaskTemplate,
- ContainerSpec: {
- ...(settings?.TaskTemplate as ContainerTaskSpec).ContainerSpec,
- Env: updatedEnv,
- },
- },
- };
- await service.update({
- version: Number.parseInt(inspect.Version.Index),
- ...updatedSettings,
- });
-
- console.log("Traefik Started ✅");
- } catch (_) {
try {
- await docker.createService(settings);
- } catch (error: any) {
- if (error?.statusCode !== 409) {
- throw error;
+ 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 {
+ const inspect = await container.inspect();
+ if (inspect.State.Status === "running" && !force) {
+ console.log("Traefik already running");
+ return;
}
- console.log("Traefik service already exists, continuing...");
+
+ await container.remove({ force: true });
+ } catch (error) {
+ console.log(error);
}
- console.log("Traefik Not Found: Starting ✅");
+
+ await docker.createContainer(settings);
+ const newContainer = docker.getContainer(containerName);
+ await newContainer.start();
+ } catch (error) {
+ console.log(error);
}
};
@@ -212,6 +187,9 @@ export const getDefaultTraefikConfig = () => {
},
websecure: {
address: `:${TRAEFIK_SSL_PORT}`,
+ http3: {
+ advertisedPort: TRAEFIK_HTTP3_PORT,
+ },
...(process.env.NODE_ENV === "production" && {
http: {
tls: {
@@ -267,6 +245,9 @@ export const getDefaultServerTraefikConfig = () => {
},
websecure: {
address: `:${TRAEFIK_SSL_PORT}`,
+ http3: {
+ advertisedPort: TRAEFIK_HTTP3_PORT,
+ },
http: {
tls: {
certResolver: "letsencrypt",