mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-19 14:15:21 +02:00
Users deploying to Docker Swarm can now configure resource ulimits (nofile, nproc, etc.) to prevent applications from hitting system limits that cause crashes or degraded performance.
298 lines
6.7 KiB
TypeScript
298 lines
6.7 KiB
TypeScript
import type { CreateServiceOptions } from "dockerode";
|
|
import { eq } from "drizzle-orm";
|
|
import type { z } from "zod";
|
|
import { db } from "../db";
|
|
import {
|
|
type createRollbackSchema,
|
|
deployments as deploymentsSchema,
|
|
rollbacks,
|
|
} from "../db/schema";
|
|
import type { ApplicationNested } from "../utils/builders";
|
|
import { getRegistryTag } from "../utils/cluster/upload";
|
|
import {
|
|
calculateResources,
|
|
generateBindMounts,
|
|
generateConfigContainer,
|
|
generateVolumeMounts,
|
|
prepareEnvironmentVariables,
|
|
} from "../utils/docker/utils";
|
|
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
|
|
import { getRemoteDocker } from "../utils/servers/remote-docker";
|
|
import { type Application, findApplicationById } from "./application";
|
|
import { findDeploymentById } from "./deployment";
|
|
import type { Mount } from "./mount";
|
|
import type { Port } from "./port";
|
|
import type { Project } from "./project";
|
|
import type { Registry } from "./registry";
|
|
|
|
export const createRollback = async (
|
|
input: z.infer<typeof createRollbackSchema>,
|
|
) => {
|
|
return await db.transaction(async (tx) => {
|
|
const { fullContext, ...other } = input;
|
|
const rollback = await tx
|
|
.insert(rollbacks)
|
|
.values(other)
|
|
.returning()
|
|
.then((res) => res[0]);
|
|
|
|
if (!rollback) {
|
|
throw new Error("Failed to create rollback");
|
|
}
|
|
|
|
const tagImage = `${input.appName}:v${rollback.version}`;
|
|
const deployment = await findDeploymentById(rollback.deploymentId);
|
|
|
|
if (!deployment?.applicationId) {
|
|
throw new Error("Deployment not found");
|
|
}
|
|
|
|
const {
|
|
deployments: _,
|
|
bitbucket,
|
|
github,
|
|
gitlab,
|
|
gitea,
|
|
...rest
|
|
} = await findApplicationById(deployment.applicationId);
|
|
|
|
await tx
|
|
.update(rollbacks)
|
|
.set({
|
|
image: tagImage,
|
|
fullContext: rest,
|
|
})
|
|
.where(eq(rollbacks.rollbackId, rollback.rollbackId));
|
|
|
|
// Update the deployment to reference this rollback
|
|
await tx
|
|
.update(deploymentsSchema)
|
|
.set({
|
|
rollbackId: rollback.rollbackId,
|
|
})
|
|
.where(eq(deploymentsSchema.deploymentId, rollback.deploymentId));
|
|
|
|
const updatedRollback = await tx.query.rollbacks.findFirst({
|
|
where: eq(rollbacks.rollbackId, rollback.rollbackId),
|
|
});
|
|
|
|
return updatedRollback;
|
|
});
|
|
};
|
|
|
|
export const findRollbackById = async (rollbackId: string) => {
|
|
const result = await db.query.rollbacks.findFirst({
|
|
where: eq(rollbacks.rollbackId, rollbackId),
|
|
with: {
|
|
deployment: {
|
|
with: {
|
|
application: {
|
|
with: {
|
|
environment: {
|
|
with: {
|
|
project: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!result) {
|
|
throw new Error("Rollback not found");
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
const deleteRollbackImage = async (image: string, serverId?: string | null) => {
|
|
const command = `docker image rm ${image} --force`;
|
|
|
|
if (serverId) {
|
|
await execAsyncRemote(command, serverId);
|
|
} else {
|
|
await execAsync(command);
|
|
}
|
|
};
|
|
|
|
export const removeRollbackById = async (rollbackId: string) => {
|
|
const rollback = await findRollbackById(rollbackId);
|
|
|
|
if (!rollback) {
|
|
throw new Error("Rollback not found");
|
|
}
|
|
|
|
if (rollback?.image) {
|
|
try {
|
|
const deployment = await findDeploymentById(rollback.deploymentId);
|
|
|
|
if (!deployment?.applicationId) {
|
|
throw new Error("Deployment not found");
|
|
}
|
|
|
|
const application = await findApplicationById(deployment.applicationId);
|
|
await deleteRollbackImage(rollback.image, application.serverId);
|
|
|
|
await db
|
|
.delete(rollbacks)
|
|
.where(eq(rollbacks.rollbackId, rollbackId))
|
|
.returning()
|
|
.then((res) => res[0]);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
return rollback;
|
|
};
|
|
|
|
export const rollback = async (rollbackId: string) => {
|
|
const result = await findRollbackById(rollbackId);
|
|
|
|
const deployment = await findDeploymentById(result.deploymentId);
|
|
|
|
if (!deployment?.applicationId) {
|
|
throw new Error("Deployment not found");
|
|
}
|
|
|
|
const application = await findApplicationById(deployment.applicationId);
|
|
|
|
if (!result.fullContext) {
|
|
throw new Error("Rollback context not found");
|
|
}
|
|
// Use the full context for rollback
|
|
await rollbackApplication(
|
|
application.appName,
|
|
result.image || "",
|
|
application.serverId,
|
|
result.fullContext,
|
|
);
|
|
};
|
|
|
|
const rollbackApplication = async (
|
|
appName: string,
|
|
image: string,
|
|
serverId?: string | null,
|
|
fullContext?: Application & {
|
|
environment: {
|
|
project: Project;
|
|
};
|
|
mounts: Mount[];
|
|
ports: Port[];
|
|
rollbackRegistry?: Registry;
|
|
},
|
|
) => {
|
|
if (!fullContext) {
|
|
throw new Error("Full context is required for rollback");
|
|
}
|
|
|
|
const docker = await getRemoteDocker(serverId);
|
|
|
|
// Use the same configuration as mechanizeDockerContainer
|
|
const {
|
|
env,
|
|
mounts,
|
|
cpuLimit,
|
|
memoryLimit,
|
|
memoryReservation,
|
|
cpuReservation,
|
|
command,
|
|
ports,
|
|
} = fullContext;
|
|
|
|
const resources = calculateResources({
|
|
memoryLimit,
|
|
memoryReservation,
|
|
cpuLimit,
|
|
cpuReservation,
|
|
});
|
|
|
|
const volumesMount = generateVolumeMounts(mounts);
|
|
|
|
const {
|
|
HealthCheck,
|
|
RestartPolicy,
|
|
Placement,
|
|
Labels,
|
|
Mode,
|
|
RollbackConfig,
|
|
UpdateConfig,
|
|
Networks,
|
|
Ulimits,
|
|
} = generateConfigContainer(fullContext as ApplicationNested);
|
|
|
|
const bindsMount = generateBindMounts(mounts);
|
|
const envVariables = prepareEnvironmentVariables(
|
|
env,
|
|
fullContext.environment.project.env,
|
|
);
|
|
|
|
// Build the full registry image path if rollbackRegistry is available
|
|
// e.g., "appName:v5" -> "siumauricio/appName:v5" or "registry.com/prefix/appName:v5"
|
|
let rollbackImage = image;
|
|
if (fullContext.rollbackRegistry) {
|
|
rollbackImage = getRegistryTag(fullContext.rollbackRegistry, image);
|
|
}
|
|
|
|
const settings: CreateServiceOptions = {
|
|
authconfig: {
|
|
password: fullContext.rollbackRegistry?.password || "",
|
|
username: fullContext.rollbackRegistry?.username || "",
|
|
serveraddress: fullContext.rollbackRegistry?.registryUrl || "",
|
|
},
|
|
Name: appName,
|
|
TaskTemplate: {
|
|
ContainerSpec: {
|
|
HealthCheck,
|
|
Image: rollbackImage,
|
|
Env: envVariables,
|
|
Mounts: [...volumesMount, ...bindsMount],
|
|
...(command
|
|
? {
|
|
Command: ["/bin/sh"],
|
|
Args: ["-c", command],
|
|
}
|
|
: {}),
|
|
...(Ulimits && { Ulimits }),
|
|
Labels,
|
|
},
|
|
Networks,
|
|
RestartPolicy,
|
|
Placement,
|
|
Resources: {
|
|
...resources,
|
|
},
|
|
},
|
|
Mode,
|
|
RollbackConfig,
|
|
EndpointSpec: {
|
|
Ports: ports.map((port) => ({
|
|
PublishMode: port.publishMode,
|
|
Protocol: port.protocol,
|
|
TargetPort: port.targetPort,
|
|
PublishedPort: port.publishedPort,
|
|
})),
|
|
},
|
|
UpdateConfig,
|
|
};
|
|
|
|
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,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
await docker.createService(settings);
|
|
}
|
|
};
|