bump: Traefik v3.5.0

This commit is contained in:
Vyacheslav Scherbinin
2025-08-13 08:23:30 +07:00
parent 9763dce045
commit 3864c50deb
2 changed files with 362 additions and 385 deletions

View File

@@ -1,31 +1,28 @@
import { execAsync } from "@dokploy/server";
import { setupDirectories } from "@dokploy/server/setup/config-paths";
import { initializePostgres } from "@dokploy/server/setup/postgres-setup";
import { initializeRedis } from "@dokploy/server/setup/redis-setup";
import { execAsync } from '@dokploy/server';
import { setupDirectories } from '@dokploy/server/setup/config-paths';
import { initializePostgres } from '@dokploy/server/setup/postgres-setup';
import { initializeRedis } from '@dokploy/server/setup/redis-setup';
import { initializeNetwork, initializeSwarm } from '@dokploy/server/setup/setup';
import {
initializeNetwork,
initializeSwarm,
} from "@dokploy/server/setup/setup";
import {
createDefaultMiddlewares,
createDefaultServerTraefikConfig,
createDefaultTraefikConfig,
initializeStandaloneTraefik,
} from "@dokploy/server/setup/traefik-setup";
createDefaultMiddlewares,
createDefaultServerTraefikConfig,
createDefaultTraefikConfig,
initializeStandaloneTraefik,
} from '@dokploy/server/setup/traefik-setup';
(async () => {
try {
setupDirectories();
createDefaultMiddlewares();
await initializeSwarm();
await initializeNetwork();
createDefaultTraefikConfig();
createDefaultServerTraefikConfig();
await execAsync("docker pull traefik:v3.1.2");
await initializeStandaloneTraefik();
await initializeRedis();
await initializePostgres();
} catch (e) {
console.error("Error in dokploy setup", e);
}
try {
setupDirectories();
createDefaultMiddlewares();
await initializeSwarm();
await initializeNetwork();
createDefaultTraefikConfig();
createDefaultServerTraefikConfig();
await execAsync('docker pull traefik:v3.5.0');
await initializeStandaloneTraefik();
await initializeRedis();
await initializePostgres();
} catch (e) {
console.error('Error in dokploy setup', e);
}
})();

View File

@@ -1,402 +1,382 @@
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import type { ContainerCreateOptions, CreateServiceOptions } from "dockerode";
import { dump } from "js-yaml";
import { paths } from "../constants";
import { getRemoteDocker } from "../utils/servers/remote-docker";
import type { FileConfig } from "../utils/traefik/file-types";
import type { MainTraefikConfig } from "../utils/traefik/types";
import { chmodSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import type { ContainerCreateOptions, CreateServiceOptions } from 'dockerode';
import { dump } from 'js-yaml';
import { paths } from '../constants';
import { getRemoteDocker } from '../utils/servers/remote-docker';
import type { FileConfig } from '../utils/traefik/file-types';
import type { MainTraefikConfig } from '../utils/traefik/types';
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";
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.5.0';
export interface TraefikOptions {
env?: string[];
serverId?: string;
additionalPorts?: {
targetPort: number;
publishedPort: number;
protocol?: string;
}[];
env?: string[];
serverId?: string;
additionalPorts?: {
targetPort: number;
publishedPort: number;
protocol?: string;
}[];
}
export const initializeStandaloneTraefik = async ({
env,
serverId,
additionalPorts = [],
}: TraefikOptions = {}) => {
const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const imageName = `traefik:v${TRAEFIK_VERSION}`;
const containerName = "dokploy-traefik";
export const initializeStandaloneTraefik = async ({ env, serverId, additionalPorts = [] }: TraefikOptions = {}) => {
const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const imageName = `traefik:v${TRAEFIK_VERSION}`;
const containerName = 'dokploy-traefik';
const exposedPorts: Record<string, {}> = {
[`${TRAEFIK_PORT}/tcp`]: {},
[`${TRAEFIK_SSL_PORT}/tcp`]: {},
[`${TRAEFIK_HTTP3_PORT}/udp`]: {},
};
const exposedPorts: Record<string, {}> = {
[`${TRAEFIK_PORT}/tcp`]: {},
[`${TRAEFIK_SSL_PORT}/tcp`]: {},
[`${TRAEFIK_HTTP3_PORT}/udp`]: {},
};
const portBindings: Record<string, Array<{ HostPort: string }>> = {
[`${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() },
],
};
const portBindings: Record<string, Array<{ HostPort: string }>> = {
[`${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() }],
};
const enableDashboard = additionalPorts.some(
(port) => port.targetPort === 8080,
);
const enableDashboard = additionalPorts.some((port) => port.targetPort === 8080);
if (enableDashboard) {
exposedPorts["8080/tcp"] = {};
portBindings["8080/tcp"] = [{ HostPort: "8080" }];
}
if (enableDashboard) {
exposedPorts['8080/tcp'] = {};
portBindings['8080/tcp'] = [{ HostPort: '8080' }];
}
for (const port of additionalPorts) {
const portKey = `${port.targetPort}/${port.protocol ?? "tcp"}`;
exposedPorts[portKey] = {};
portBindings[portKey] = [{ HostPort: port.publishedPort.toString() }];
}
for (const port of additionalPorts) {
const portKey = `${port.targetPort}/${port.protocol ?? '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 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 {
const container = docker.getContainer(containerName);
await container.remove({ force: true });
await new Promise((resolve) => setTimeout(resolve, 5000));
} catch {}
const docker = await getRemoteDocker(serverId);
try {
const container = docker.getContainer(containerName);
await container.remove({ force: true });
await new Promise((resolve) => setTimeout(resolve, 5000));
} catch {}
await docker.createContainer(settings);
const newContainer = docker.getContainer(containerName);
await newContainer.start();
console.log("Traefik Started ✅");
await docker.createContainer(settings);
const newContainer = docker.getContainer(containerName);
await newContainer.start();
console.log('Traefik Started ✅');
};
export const initializeTraefikService = async ({
env,
additionalPorts = [],
serverId,
}: TraefikOptions) => {
const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const imageName = `traefik:v${TRAEFIK_VERSION}`;
const appName = "dokploy-traefik";
export const initializeTraefikService = async ({ env, additionalPorts = [], serverId }: TraefikOptions) => {
const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const imageName = `traefik:v${TRAEFIK_VERSION}`;
const appName = 'dokploy-traefik';
const settings: CreateServiceOptions = {
Name: appName,
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",
Protocol: "tcp",
},
{
TargetPort: 443,
PublishedPort: TRAEFIK_SSL_PORT,
PublishMode: "host",
Protocol: "udp",
},
{
TargetPort: 80,
PublishedPort: TRAEFIK_PORT,
PublishMode: "host",
Protocol: "tcp",
},
const settings: CreateServiceOptions = {
Name: appName,
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',
Protocol: 'tcp',
},
{
TargetPort: 443,
PublishedPort: TRAEFIK_SSL_PORT,
PublishMode: 'host',
Protocol: 'udp',
},
{
TargetPort: 80,
PublishedPort: TRAEFIK_PORT,
PublishMode: 'host',
Protocol: 'tcp',
},
...additionalPorts.map((port) => ({
TargetPort: port.targetPort,
PublishedPort: port.publishedPort,
Protocol: port.protocol as "tcp" | "udp" | "sctp" | undefined,
PublishMode: "host" as const,
})),
],
},
};
const docker = await getRemoteDocker(serverId);
try {
const service = docker.getService(appName);
const inspect = await service.inspect();
...additionalPorts.map((port) => ({
TargetPort: port.targetPort,
PublishedPort: port.publishedPort,
Protocol: port.protocol as 'tcp' | 'udp' | 'sctp' | undefined,
PublishMode: 'host' as const,
})),
],
},
};
const docker = await getRemoteDocker(serverId);
try {
const service = docker.getService(appName);
const inspect = await service.inspect();
await service.update({
version: Number.parseInt(inspect.Version.Index),
...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
});
console.log("Traefik Updated ✅");
} catch {
await docker.createService(settings);
console.log("Traefik Started ✅");
}
await service.update({
version: Number.parseInt(inspect.Version.Index),
...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
});
console.log('Traefik Updated ✅');
} catch {
await docker.createService(settings);
console.log('Traefik Started ✅');
}
};
export const createDefaultServerTraefikConfig = () => {
const { DYNAMIC_TRAEFIK_PATH } = paths();
const configFilePath = path.join(DYNAMIC_TRAEFIK_PATH, "dokploy.yml");
const { DYNAMIC_TRAEFIK_PATH } = paths();
const configFilePath = path.join(DYNAMIC_TRAEFIK_PATH, 'dokploy.yml');
if (existsSync(configFilePath)) {
console.log("Default traefik config already exists");
return;
}
if (existsSync(configFilePath)) {
console.log('Default traefik config already exists');
return;
}
const appName = "dokploy";
const serviceURLDefault = `http://${appName}:${process.env.PORT || 3000}`;
const config: FileConfig = {
http: {
routers: {
[`${appName}-router-app`]: {
rule: `Host(\`${appName}.docker.localhost\`) && PathPrefix(\`/\`)`,
service: `${appName}-service-app`,
entryPoints: ["web"],
},
},
services: {
[`${appName}-service-app`]: {
loadBalancer: {
servers: [{ url: serviceURLDefault }],
passHostHeader: true,
},
},
},
},
};
const appName = 'dokploy';
const serviceURLDefault = `http://${appName}:${process.env.PORT || 3000}`;
const config: FileConfig = {
http: {
routers: {
[`${appName}-router-app`]: {
rule: `Host(\`${appName}.docker.localhost\`) && PathPrefix(\`/\`)`,
service: `${appName}-service-app`,
entryPoints: ['web'],
},
},
services: {
[`${appName}-service-app`]: {
loadBalancer: {
servers: [{ url: serviceURLDefault }],
passHostHeader: true,
},
},
},
},
};
const yamlStr = dump(config);
mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
writeFileSync(
path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`),
yamlStr,
"utf8",
);
const yamlStr = dump(config);
mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
writeFileSync(path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`), yamlStr, 'utf8');
};
export const getDefaultTraefikConfig = () => {
const configObject: MainTraefikConfig = {
global: {
sendAnonymousUsage: false,
},
providers: {
...(process.env.NODE_ENV === "development"
? {
docker: {
defaultRule:
"Host(`{{ trimPrefix `/` .Name }}.docker.localhost`)",
},
}
: {
swarm: {
exposedByDefault: false,
watch: true,
},
docker: {
exposedByDefault: false,
watch: true,
network: "dokploy-network",
},
}),
file: {
directory: "/etc/dokploy/traefik/dynamic",
watch: true,
},
},
entryPoints: {
web: {
address: `:${TRAEFIK_PORT}`,
},
websecure: {
address: `:${TRAEFIK_SSL_PORT}`,
http3: {
advertisedPort: TRAEFIK_HTTP3_PORT,
},
...(process.env.NODE_ENV === "production" && {
http: {
tls: {
certResolver: "letsencrypt",
},
},
}),
},
},
api: {
insecure: true,
},
...(process.env.NODE_ENV === "production" && {
certificatesResolvers: {
letsencrypt: {
acme: {
email: "test@localhost.com",
storage: "/etc/dokploy/traefik/dynamic/acme.json",
httpChallenge: {
entryPoint: "web",
},
},
},
},
}),
};
const configObject: MainTraefikConfig = {
global: {
sendAnonymousUsage: false,
},
providers: {
...(process.env.NODE_ENV === 'development'
? {
docker: {
defaultRule: 'Host(`{{ trimPrefix `/` .Name }}.docker.localhost`)',
},
}
: {
swarm: {
exposedByDefault: false,
watch: true,
},
docker: {
exposedByDefault: false,
watch: true,
network: 'dokploy-network',
},
}),
file: {
directory: '/etc/dokploy/traefik/dynamic',
watch: true,
},
},
entryPoints: {
web: {
address: `:${TRAEFIK_PORT}`,
},
websecure: {
address: `:${TRAEFIK_SSL_PORT}`,
http3: {
advertisedPort: TRAEFIK_HTTP3_PORT,
},
...(process.env.NODE_ENV === 'production' && {
http: {
tls: {
certResolver: 'letsencrypt',
},
},
}),
},
},
api: {
insecure: true,
},
...(process.env.NODE_ENV === 'production' && {
certificatesResolvers: {
letsencrypt: {
acme: {
email: 'test@localhost.com',
storage: '/etc/dokploy/traefik/dynamic/acme.json',
httpChallenge: {
entryPoint: 'web',
},
},
},
},
}),
};
const yamlStr = dump(configObject);
const yamlStr = dump(configObject);
return yamlStr;
return yamlStr;
};
export const getDefaultServerTraefikConfig = () => {
const configObject: MainTraefikConfig = {
providers: {
swarm: {
exposedByDefault: false,
watch: true,
},
docker: {
exposedByDefault: false,
watch: true,
network: "dokploy-network",
},
file: {
directory: "/etc/dokploy/traefik/dynamic",
watch: true,
},
},
entryPoints: {
web: {
address: `:${TRAEFIK_PORT}`,
},
websecure: {
address: `:${TRAEFIK_SSL_PORT}`,
http3: {
advertisedPort: TRAEFIK_HTTP3_PORT,
},
http: {
tls: {
certResolver: "letsencrypt",
},
},
},
},
api: {
insecure: true,
},
certificatesResolvers: {
letsencrypt: {
acme: {
email: "test@localhost.com",
storage: "/etc/dokploy/traefik/dynamic/acme.json",
httpChallenge: {
entryPoint: "web",
},
},
},
},
};
const configObject: MainTraefikConfig = {
providers: {
swarm: {
exposedByDefault: false,
watch: true,
},
docker: {
exposedByDefault: false,
watch: true,
network: 'dokploy-network',
},
file: {
directory: '/etc/dokploy/traefik/dynamic',
watch: true,
},
},
entryPoints: {
web: {
address: `:${TRAEFIK_PORT}`,
},
websecure: {
address: `:${TRAEFIK_SSL_PORT}`,
http3: {
advertisedPort: TRAEFIK_HTTP3_PORT,
},
http: {
tls: {
certResolver: 'letsencrypt',
},
},
},
},
api: {
insecure: true,
},
certificatesResolvers: {
letsencrypt: {
acme: {
email: 'test@localhost.com',
storage: '/etc/dokploy/traefik/dynamic/acme.json',
httpChallenge: {
entryPoint: 'web',
},
},
},
},
};
const yamlStr = dump(configObject);
const yamlStr = dump(configObject);
return yamlStr;
return yamlStr;
};
export const createDefaultTraefikConfig = () => {
const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths();
const mainConfig = path.join(MAIN_TRAEFIK_PATH, "traefik.yml");
const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, "acme.json");
const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths();
const mainConfig = path.join(MAIN_TRAEFIK_PATH, 'traefik.yml');
const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, 'acme.json');
if (existsSync(acmeJsonPath)) {
chmodSync(acmeJsonPath, "600");
}
if (existsSync(mainConfig)) {
console.log("Main config already exists");
return;
}
const yamlStr = getDefaultTraefikConfig();
mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true });
writeFileSync(mainConfig, yamlStr, "utf8");
if (existsSync(acmeJsonPath)) {
chmodSync(acmeJsonPath, '600');
}
if (existsSync(mainConfig)) {
console.log('Main config already exists');
return;
}
const yamlStr = getDefaultTraefikConfig();
mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true });
writeFileSync(mainConfig, yamlStr, 'utf8');
};
export const getDefaultMiddlewares = () => {
const defaultMiddlewares = {
http: {
middlewares: {
"redirect-to-https": {
redirectScheme: {
scheme: "https",
permanent: true,
},
},
},
},
};
const yamlStr = dump(defaultMiddlewares);
return yamlStr;
const defaultMiddlewares = {
http: {
middlewares: {
'redirect-to-https': {
redirectScheme: {
scheme: 'https',
permanent: true,
},
},
},
},
};
const yamlStr = dump(defaultMiddlewares);
return yamlStr;
};
export const createDefaultMiddlewares = () => {
const { DYNAMIC_TRAEFIK_PATH } = paths();
const middlewaresPath = path.join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
if (existsSync(middlewaresPath)) {
console.log("Default middlewares already exists");
return;
}
const yamlStr = getDefaultMiddlewares();
mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
writeFileSync(middlewaresPath, yamlStr, "utf8");
const { DYNAMIC_TRAEFIK_PATH } = paths();
const middlewaresPath = path.join(DYNAMIC_TRAEFIK_PATH, 'middlewares.yml');
if (existsSync(middlewaresPath)) {
console.log('Default middlewares already exists');
return;
}
const yamlStr = getDefaultMiddlewares();
mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
writeFileSync(middlewaresPath, yamlStr, 'utf8');
};