mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add experimental swarm settings
This commit is contained in:
@@ -10,7 +10,14 @@ import { projects } from "./project";
|
||||
import { security } from "./security";
|
||||
import { applicationStatus } from "./shared";
|
||||
import { ports } from "./port";
|
||||
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
json,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
text,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { generateAppName } from "./utils";
|
||||
import { registry } from "./registry";
|
||||
|
||||
@@ -23,6 +30,58 @@ export const buildType = pgEnum("buildType", [
|
||||
"nixpacks",
|
||||
]);
|
||||
|
||||
interface HealthCheckSwarm {
|
||||
Test?: string[] | undefined;
|
||||
Interval?: number | undefined;
|
||||
Timeout?: number | undefined;
|
||||
StartPeriod?: number | undefined;
|
||||
Retries?: number | undefined;
|
||||
}
|
||||
|
||||
interface RestartPolicySwarm {
|
||||
Condition?: string | undefined;
|
||||
Delay?: number | undefined;
|
||||
MaxAttempts?: number | undefined;
|
||||
Window?: number | undefined;
|
||||
}
|
||||
|
||||
interface PlacementSwarm {
|
||||
Constraints?: string[] | undefined;
|
||||
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
|
||||
MaxReplicas?: number | undefined;
|
||||
Platforms?:
|
||||
| Array<{
|
||||
Architecture: string;
|
||||
OS: string;
|
||||
}>
|
||||
| undefined;
|
||||
}
|
||||
|
||||
interface UpdateConfigSwarm {
|
||||
Parallelism: number;
|
||||
Delay?: number | undefined;
|
||||
FailureAction?: string | undefined;
|
||||
Monitor?: number | undefined;
|
||||
MaxFailureRatio?: number | undefined;
|
||||
Order: string;
|
||||
}
|
||||
|
||||
interface ServiceModeSwarm {
|
||||
Replicated?: { Replicas?: number | undefined } | undefined;
|
||||
Global?: {} | undefined;
|
||||
ReplicatedJob?:
|
||||
| {
|
||||
MaxConcurrent?: number | undefined;
|
||||
TotalCompletions?: number | undefined;
|
||||
}
|
||||
| undefined;
|
||||
GlobalJob?: {} | undefined;
|
||||
}
|
||||
|
||||
interface LabelsSwarm {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
export const applications = pgTable("application", {
|
||||
applicationId: text("applicationId")
|
||||
.notNull()
|
||||
@@ -61,6 +120,15 @@ export const applications = pgTable("application", {
|
||||
customGitBuildPath: text("customGitBuildPath"),
|
||||
customGitSSHKey: text("customGitSSHKey"),
|
||||
dockerfile: text("dockerfile"),
|
||||
// Docker swarm json
|
||||
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||
//
|
||||
replicas: integer("replicas").default(1).notNull(),
|
||||
applicationStatus: applicationStatus("applicationStatus")
|
||||
.notNull()
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { CreateServiceOptions } from "dockerode";
|
||||
import {
|
||||
calculateResources,
|
||||
generateBindMounts,
|
||||
generateConfigContainer,
|
||||
generateFileMounts,
|
||||
generateVolumeMounts,
|
||||
prepareEnvironmentVariables,
|
||||
@@ -72,7 +73,6 @@ export const mechanizeDockerContainer = async (
|
||||
cpuReservation,
|
||||
command,
|
||||
ports,
|
||||
replicas,
|
||||
} = application;
|
||||
|
||||
const resources = calculateResources({
|
||||
@@ -83,13 +83,29 @@ export const mechanizeDockerContainer = async (
|
||||
});
|
||||
|
||||
const volumesMount = generateVolumeMounts(mounts);
|
||||
|
||||
const {
|
||||
HealthCheck,
|
||||
RestartPolicy,
|
||||
Placement,
|
||||
Labels,
|
||||
Mode,
|
||||
RollbackConfig,
|
||||
UpdateConfig,
|
||||
} = generateConfigContainer(application);
|
||||
|
||||
const bindsMount = generateBindMounts(mounts);
|
||||
const filesMount = generateFileMounts(appName, mounts);
|
||||
const envVariables = prepareEnvironmentVariables(env);
|
||||
|
||||
const registry = application.registry;
|
||||
|
||||
let image = sourceType === "docker" ? dockerImage! : `${appName}:latest`;
|
||||
console.log(Labels);
|
||||
|
||||
let image =
|
||||
sourceType === "docker"
|
||||
? dockerImage || "ERROR-NO-IMAGE-PROVIDED"
|
||||
: `${appName}:latest`;
|
||||
|
||||
if (registry) {
|
||||
image = `${registry.registryUrl}/${appName}`;
|
||||
@@ -108,6 +124,7 @@ export const mechanizeDockerContainer = async (
|
||||
Name: appName,
|
||||
TaskTemplate: {
|
||||
ContainerSpec: {
|
||||
HealthCheck,
|
||||
Image: image,
|
||||
Env: envVariables,
|
||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||
@@ -117,20 +134,18 @@ export const mechanizeDockerContainer = async (
|
||||
Args: ["-c", command],
|
||||
}
|
||||
: {}),
|
||||
Labels,
|
||||
},
|
||||
Networks: [{ Target: "dokploy-network" }],
|
||||
RestartPolicy: {
|
||||
Condition: "on-failure",
|
||||
},
|
||||
RestartPolicy,
|
||||
Placement,
|
||||
|
||||
Resources: {
|
||||
...resources,
|
||||
},
|
||||
},
|
||||
Mode: {
|
||||
Replicated: {
|
||||
Replicas: replicas,
|
||||
},
|
||||
},
|
||||
Mode,
|
||||
RollbackConfig,
|
||||
EndpointSpec: {
|
||||
Ports: ports.map((port) => ({
|
||||
Protocol: port.protocol,
|
||||
@@ -138,10 +153,7 @@ export const mechanizeDockerContainer = async (
|
||||
PublishedPort: port.publishedPort,
|
||||
})),
|
||||
},
|
||||
UpdateConfig: {
|
||||
Parallelism: 1,
|
||||
Order: "start-first",
|
||||
},
|
||||
UpdateConfig,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -156,7 +168,7 @@ export const mechanizeDockerContainer = async (
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await docker.createService(settings);
|
||||
}
|
||||
// await cleanUpUnusedImages();
|
||||
};
|
||||
|
||||
@@ -122,10 +122,10 @@ export const cleanUpInactiveContainers = async () => {
|
||||
|
||||
for (const container of inactiveContainers) {
|
||||
await docker.getContainer(container.Id).remove({ force: true });
|
||||
console.log(`Contenedor eliminado: ${container.Id}`);
|
||||
console.log(`Cleaning up inactive container: ${container.Id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error al limpiar contenedores inactivos:", error);
|
||||
console.error("Error cleaning up inactive containers:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -199,6 +199,88 @@ export const calculateResources = ({
|
||||
};
|
||||
};
|
||||
|
||||
export const generateConfigContainer = (application: ApplicationNested) => {
|
||||
const { replicas, mounts } = application;
|
||||
|
||||
const healthCheckSwarm = JSON.parse(
|
||||
(application.healthCheckSwarm as string) || "{}",
|
||||
);
|
||||
const restartPolicySwarm = JSON.parse(
|
||||
(application.restartPolicySwarm as string) || "{}",
|
||||
);
|
||||
const placementSwarm = JSON.parse(
|
||||
(application.placementSwarm as string) || "{}",
|
||||
);
|
||||
|
||||
const updateConfigSwarm = JSON.parse(
|
||||
(application.updateConfigSwarm as unknown as string) || "{}",
|
||||
);
|
||||
|
||||
const rollbackConfigSwarm = JSON.parse(
|
||||
(application.rollbackConfigSwarm as unknown as string) || "{}",
|
||||
);
|
||||
const modeSwarm = JSON.parse((application.modeSwarm as string) || "{}");
|
||||
|
||||
const labelsSwarm = JSON.parse(
|
||||
(application.labelsSwarm as unknown as string) || "{}",
|
||||
);
|
||||
|
||||
const haveMounts = mounts.length > 0;
|
||||
|
||||
return {
|
||||
...(healthCheckSwarm && {
|
||||
HealthCheck: healthCheckSwarm,
|
||||
}),
|
||||
...(restartPolicySwarm
|
||||
? {
|
||||
RestartPolicy: restartPolicySwarm,
|
||||
}
|
||||
: {
|
||||
// if no restartPolicySwarm provided use default
|
||||
RestartPolicy: {
|
||||
Condition: "on-failure",
|
||||
},
|
||||
}),
|
||||
...(placementSwarm
|
||||
? {
|
||||
Placement: placementSwarm,
|
||||
}
|
||||
: {
|
||||
// if app have mounts keep manager as constraint
|
||||
Placement: {
|
||||
Constraints: haveMounts ? ["node.role==manager"] : [],
|
||||
},
|
||||
}),
|
||||
...(labelsSwarm && {
|
||||
Labels: labelsSwarm,
|
||||
}),
|
||||
...(modeSwarm
|
||||
? {
|
||||
Mode: modeSwarm,
|
||||
}
|
||||
: {
|
||||
// use replicas value if no modeSwarm provided
|
||||
Mode: {
|
||||
Replicated: {
|
||||
Replicas: replicas,
|
||||
},
|
||||
},
|
||||
}),
|
||||
...(rollbackConfigSwarm && {
|
||||
RollbackConfig: rollbackConfigSwarm,
|
||||
}),
|
||||
...(updateConfigSwarm
|
||||
? { UpdateConfig: updateConfigSwarm }
|
||||
: {
|
||||
// default config if no updateConfigSwarm provided
|
||||
UpdateConfig: {
|
||||
Parallelism: 1,
|
||||
Order: "start-first",
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => {
|
||||
if (!mounts || mounts.length === 0) {
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user