mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Docker Swarm's default start-first update order causes new database containers to fail with 'DBPathInUse' because two containers compete for the same data volume simultaneously. Docker then rolls back the update, silently reverting any env var or config changes. Using stop-first ensures the old container is stopped before the new one starts, preventing volume lock conflicts across all database types. Fixes #4550
140 lines
3.0 KiB
TypeScript
140 lines
3.0 KiB
TypeScript
import type { InferResultType } from "@dokploy/server/types/with";
|
|
import type { CreateServiceOptions } from "dockerode";
|
|
import {
|
|
calculateResources,
|
|
generateBindMounts,
|
|
generateConfigContainer,
|
|
generateFileMounts,
|
|
generateVolumeMounts,
|
|
prepareEnvironmentVariables,
|
|
} from "../docker/utils";
|
|
import { getRemoteDocker } from "../servers/remote-docker";
|
|
|
|
export type RedisNested = InferResultType<
|
|
"redis",
|
|
{ mounts: true; environment: { with: { project: true } } }
|
|
>;
|
|
export const buildRedis = async (redis: RedisNested) => {
|
|
const {
|
|
appName,
|
|
env,
|
|
externalPort,
|
|
dockerImage,
|
|
memoryLimit,
|
|
memoryReservation,
|
|
databasePassword,
|
|
cpuLimit,
|
|
cpuReservation,
|
|
command,
|
|
args,
|
|
mounts,
|
|
} = redis;
|
|
|
|
const defaultRedisEnv = `REDIS_PASSWORD="${databasePassword}"${
|
|
env ? `\n${env}` : ""
|
|
}`;
|
|
|
|
const {
|
|
HealthCheck,
|
|
RestartPolicy,
|
|
Placement,
|
|
Labels,
|
|
Mode,
|
|
RollbackConfig,
|
|
UpdateConfig,
|
|
Networks,
|
|
StopGracePeriod,
|
|
EndpointSpec,
|
|
Ulimits,
|
|
} = generateConfigContainer(redis);
|
|
const resources = calculateResources({
|
|
memoryLimit,
|
|
memoryReservation,
|
|
cpuLimit,
|
|
cpuReservation,
|
|
});
|
|
const envVariables = prepareEnvironmentVariables(
|
|
defaultRedisEnv,
|
|
redis.environment.project.env,
|
|
redis.environment.env,
|
|
);
|
|
const volumesMount = generateVolumeMounts(mounts);
|
|
const bindsMount = generateBindMounts(mounts);
|
|
const filesMount = generateFileMounts(appName, redis);
|
|
|
|
const docker = await getRemoteDocker(redis.serverId);
|
|
|
|
const settings: CreateServiceOptions = {
|
|
Name: appName,
|
|
TaskTemplate: {
|
|
ContainerSpec: {
|
|
HealthCheck,
|
|
Image: dockerImage,
|
|
Env: envVariables,
|
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
|
...(StopGracePeriod !== null &&
|
|
StopGracePeriod !== undefined && { StopGracePeriod }),
|
|
...(command || args
|
|
? {
|
|
...(command && {
|
|
Command: command.split(" "),
|
|
}),
|
|
...(args &&
|
|
args.length > 0 && {
|
|
Args: args,
|
|
}),
|
|
}
|
|
: {
|
|
Command: ["/bin/sh"],
|
|
Args: ["-c", `redis-server --requirepass ${databasePassword}`],
|
|
}),
|
|
...(Ulimits && { Ulimits }),
|
|
Labels,
|
|
},
|
|
Networks,
|
|
RestartPolicy,
|
|
Placement,
|
|
Resources: {
|
|
...resources,
|
|
},
|
|
},
|
|
Mode,
|
|
RollbackConfig,
|
|
EndpointSpec: EndpointSpec
|
|
? EndpointSpec
|
|
: {
|
|
Mode: "dnsrr" as const,
|
|
Ports: externalPort
|
|
? [
|
|
{
|
|
Protocol: "tcp" as const,
|
|
TargetPort: 6379,
|
|
PublishedPort: externalPort,
|
|
PublishMode: "host" as const,
|
|
},
|
|
]
|
|
: [],
|
|
},
|
|
UpdateConfig: redis.updateConfigSwarm ?? {
|
|
Parallelism: 1,
|
|
Order: "stop-first" as const,
|
|
FailureAction: "rollback" as const,
|
|
},
|
|
};
|
|
|
|
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 {
|
|
await docker.createService(settings);
|
|
}
|
|
};
|