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
This commit is contained in:
Mauricio Siu
2025-03-02 03:14:54 -06:00
parent 3f45eb467b
commit 7f8f6ac64c
4 changed files with 91 additions and 90 deletions

View File

@@ -112,15 +112,6 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
{haveTraefikDashboardPortEnabled ? "Disable" : "Enable"} Dashboard {haveTraefikDashboardPortEnabled ? "Disable" : "Enable"} Dashboard
</span> </span>
</DropdownMenuItem> </DropdownMenuItem>
{/*
<DockerTerminalModal appName="dokploy-traefik">
<DropdownMenuItem
className="w-full cursor-pointer space-x-3"
onSelect={(e) => e.preventDefault()}
>
<span>Enter the terminal</span>
</DropdownMenuItem>
</DockerTerminalModal> */}
<ManageTraefikPorts serverId={serverId}> <ManageTraefikPorts serverId={serverId}>
<DropdownMenuItem <DropdownMenuItem
onSelect={(e) => e.preventDefault()} onSelect={(e) => e.preventDefault()}

View File

@@ -42,10 +42,6 @@ import {
recreateDirectory, recreateDirectory,
sendDockerCleanupNotifications, sendDockerCleanupNotifications,
spawnAsync, spawnAsync,
startService,
startServiceRemote,
stopService,
stopServiceRemote,
updateLetsEncryptEmail, updateLetsEncryptEmail,
updateServerById, updateServerById,
updateServerTraefik, updateServerTraefik,
@@ -86,11 +82,9 @@ export const settingsRouter = createTRPCRouter({
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
try { try {
if (input?.serverId) { if (input?.serverId) {
await stopServiceRemote(input.serverId, "dokploy-traefik"); await execAsync("docker restart dokploy-traefik");
await startServiceRemote(input.serverId, "dokploy-traefik");
} else if (!IS_CLOUD) { } else if (!IS_CLOUD) {
await stopService("dokploy-traefik"); await execAsync("docker restart dokploy-traefik");
await startService("dokploy-traefik");
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -104,6 +98,7 @@ export const settingsRouter = createTRPCRouter({
await initializeTraefik({ await initializeTraefik({
enableDashboard: input.enableDashboard, enableDashboard: input.enableDashboard,
serverId: input.serverId, serverId: input.serverId,
force: true,
}); });
return true; return true;
}), }),
@@ -511,16 +506,18 @@ export const settingsRouter = createTRPCRouter({
.input(apiServerSchema) .input(apiServerSchema)
.query(async ({ input }) => { .query(async ({ input }) => {
const command = 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) { if (input?.serverId) {
const result = await execAsyncRemote(input.serverId, command); const execResult = await execAsyncRemote(input.serverId, command);
return result.stdout.trim(); result = execResult.stdout;
} } else {
if (!IS_CLOUD) { const execResult = await execAsync(command);
const result = await execAsync(command); result = execResult.stdout;
return result.stdout.trim();
} }
const envVars = JSON.parse(result.trim());
return envVars.join("\n");
}), }),
writeTraefikEnv: adminProcedure writeTraefikEnv: adminProcedure
@@ -530,6 +527,7 @@ export const settingsRouter = createTRPCRouter({
await initializeTraefik({ await initializeTraefik({
env: envs, env: envs,
serverId: input.serverId, serverId: input.serverId,
force: true,
}); });
return true; return true;
@@ -537,27 +535,22 @@ export const settingsRouter = createTRPCRouter({
haveTraefikDashboardPortEnabled: adminProcedure haveTraefikDashboardPortEnabled: adminProcedure
.input(apiServerSchema) .input(apiServerSchema)
.query(async ({ input }) => { .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 = ""; let stdout = "";
if (input?.serverId) { if (input?.serverId) {
const result = await execAsyncRemote(input.serverId, command); const result = await execAsyncRemote(input.serverId, command);
stdout = result.stdout; stdout = result.stdout;
} else if (!IS_CLOUD) { } else if (!IS_CLOUD) {
const result = await execAsync( const result = await execAsync(command);
"docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik",
);
stdout = result.stdout; stdout = result.stdout;
} }
const parsed: any[] = JSON.parse(stdout.trim()); const ports = JSON.parse(stdout.trim());
for (const port of parsed) { return Object.entries(ports).some(([containerPort, bindings]) => {
if (port.PublishedPort === 8080) { const [port] = containerPort.split("/");
return true; return port === "8080" && bindings && (bindings as any[]).length > 0;
} });
}
return false;
}), }),
readStatsLogs: adminProcedure readStatsLogs: adminProcedure
@@ -767,6 +760,7 @@ export const settingsRouter = createTRPCRouter({
await initializeTraefik({ await initializeTraefik({
serverId: input.serverId, serverId: input.serverId,
additionalPorts: input.additionalPorts, additionalPorts: input.additionalPorts,
force: true,
}); });
return true; return true;
} catch (error) { } catch (error) {
@@ -783,7 +777,7 @@ export const settingsRouter = createTRPCRouter({
getTraefikPorts: adminProcedure getTraefikPorts: adminProcedure
.input(apiServerSchema) .input(apiServerSchema)
.query(async ({ input }) => { .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 { try {
let stdout = ""; let stdout = "";
@@ -795,21 +789,38 @@ export const settingsRouter = createTRPCRouter({
stdout = result.stdout; stdout = result.stdout;
} }
const ports: { const portsMap = JSON.parse(stdout.trim());
Protocol: string; const additionalPorts: Array<{
TargetPort: number; targetPort: number;
PublishedPort: number; publishedPort: number;
PublishMode: string; publishMode: "host" | "ingress";
}[] = JSON.parse(stdout.trim()); }> = [];
// Filter out the default ports (80, 443, and optionally 8080) // Convert the Docker container port format to our expected format
const additionalPorts = ports for (const [containerPort, bindings] of Object.entries(portsMap)) {
.filter((port) => ![80, 443, 8080].includes(port.PublishedPort)) if (!bindings) continue;
.map((port) => ({
targetPort: port.TargetPort, const [port = ""] = containerPort.split("/");
publishedPort: port.PublishedPort, if (!port) continue;
publishMode: port.PublishMode.toLowerCase() as "host" | "ingress",
})); 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; return additionalPorts;
} catch (error) { } catch (error) {

View File

@@ -539,22 +539,21 @@ export const installRClone = () => `
export const createTraefikInstance = () => { export const createTraefikInstance = () => {
const command = ` const command = `
# Check if dokpyloy-traefik exists # 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 ✅" echo "Traefik already exists ✅"
else else
# Create the dokploy-traefik service # Create the dokploy-traefik container
TRAEFIK_VERSION=${TRAEFIK_VERSION} TRAEFIK_VERSION=${TRAEFIK_VERSION}
docker service create \ docker run -d \
--name dokploy-traefik \ --name dokploy-traefik \
--replicas 1 \
--constraint 'node.role==manager' \
--network dokploy-network \ --network dokploy-network \
--mount type=bind,src=/etc/dokploy/traefik/traefik.yml,dst=/etc/traefik/traefik.yml \ --restart unless-stopped \
--mount type=bind,src=/etc/dokploy/traefik/dynamic,dst=/etc/dokploy/traefik/dynamic \ -v /etc/dokploy/traefik/traefik.yml:/etc/traefik/traefik.yml \
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \ -v /etc/dokploy/traefik/dynamic:/etc/dokploy/traefik/dynamic \
-v /var/run/docker.sock:/var/run/docker.sock \
--label traefik.enable=true \ --label traefik.enable=true \
--publish mode=host,target=${TRAEFIK_SSL_PORT},published=${TRAEFIK_SSL_PORT} \ -p ${TRAEFIK_SSL_PORT}:${TRAEFIK_SSL_PORT} \
--publish mode=host,target=${TRAEFIK_PORT},published=${TRAEFIK_PORT} \ -p ${TRAEFIK_PORT}:${TRAEFIK_PORT} \
traefik:v$TRAEFIK_VERSION traefik:v$TRAEFIK_VERSION
echo "Traefik version $TRAEFIK_VERSION installed ✅" echo "Traefik version $TRAEFIK_VERSION installed ✅"
fi fi

View File

@@ -23,6 +23,7 @@ interface TraefikOptions {
publishedPort: number; publishedPort: number;
publishMode?: "ingress" | "host"; publishMode?: "ingress" | "host";
}[]; }[];
force?: boolean;
} }
export const initializeTraefik = async ({ export const initializeTraefik = async ({
@@ -30,10 +31,33 @@ export const initializeTraefik = async ({
env, env,
serverId, serverId,
additionalPorts = [], additionalPorts = [],
force = false,
}: TraefikOptions = {}) => { }: TraefikOptions = {}) => {
const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId); const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const imageName = `traefik:v${TRAEFIK_VERSION}`; const imageName = `traefik:v${TRAEFIK_VERSION}`;
const containerName = "dokploy-traefik"; const containerName = "dokploy-traefik";
const exposedPorts: Record<string, {}> = {
[`${TRAEFIK_PORT}/tcp`]: {},
[`${TRAEFIK_SSL_PORT}/tcp`]: {},
};
const portBindings: Record<string, Array<{ HostPort: string }>> = {
[`${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 = { const settings: ContainerCreateOptions = {
name: containerName, name: containerName,
Image: imageName, Image: imageName,
@@ -42,6 +66,7 @@ export const initializeTraefik = async ({
"dokploy-network": {}, "dokploy-network": {},
}, },
}, },
ExposedPorts: exposedPorts,
HostConfig: { HostConfig: {
RestartPolicy: { RestartPolicy: {
Name: "always", Name: "always",
@@ -51,37 +76,11 @@ export const initializeTraefik = async ({
`${DYNAMIC_TRAEFIK_PATH}:/etc/dokploy/traefik/dynamic`, `${DYNAMIC_TRAEFIK_PATH}:/etc/dokploy/traefik/dynamic`,
"/var/run/docker.sock:/var/run/docker.sock", "/var/run/docker.sock:/var/run/docker.sock",
], ],
PortBindings: { PortBindings: 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(),
},
],
};
}),
},
}, },
Env: env, Env: env,
}; };
const docker = await getRemoteDocker(serverId); const docker = await getRemoteDocker(serverId);
try { try {
if (serverId) { if (serverId) {
@@ -93,7 +92,7 @@ export const initializeTraefik = async ({
const container = docker.getContainer(containerName); const container = docker.getContainer(containerName);
try { try {
const inspect = await container.inspect(); const inspect = await container.inspect();
if (inspect.State.Status === "running") { if (inspect.State.Status === "running" && !force) {
console.log("Traefik already running"); console.log("Traefik already running");
return; return;
} }
@@ -112,6 +111,7 @@ export const initializeTraefik = async ({
console.log("Traefik Started ✅"); console.log("Traefik Started ✅");
} catch (error) { } catch (error) {
console.log("Traefik Not Found: Starting2 ✅", error); console.log("Traefik Not Found: Starting2 ✅", error);
throw error;
} }
}; };