mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-19 14:15:21 +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
192 lines
4.1 KiB
TypeScript
192 lines
4.1 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 MongoNested = InferResultType<
|
|
"mongo",
|
|
{ mounts: true; environment: { with: { project: true } } }
|
|
>;
|
|
|
|
export const buildMongo = async (mongo: MongoNested) => {
|
|
const {
|
|
appName,
|
|
env,
|
|
externalPort,
|
|
dockerImage,
|
|
memoryLimit,
|
|
memoryReservation,
|
|
cpuLimit,
|
|
cpuReservation,
|
|
databaseUser,
|
|
databasePassword,
|
|
command,
|
|
args,
|
|
mounts,
|
|
replicaSets,
|
|
} = mongo;
|
|
|
|
const startupScript = `
|
|
#!/bin/bash
|
|
${
|
|
replicaSets
|
|
? `
|
|
mongod --port 27017 --replSet rs0 --bind_ip_all &
|
|
MONGOD_PID=$!
|
|
|
|
# Wait for MongoDB to be ready
|
|
while ! mongosh --eval "db.adminCommand('ping')" > /dev/null 2>&1; do
|
|
sleep 2
|
|
done
|
|
|
|
# Check if replica set is already initialized
|
|
REPLICA_STATUS=$(mongosh --quiet --eval "rs.status().ok || 0")
|
|
|
|
if [ "$REPLICA_STATUS" != "1" ]; then
|
|
echo "Initializing replica set..."
|
|
mongosh --eval '
|
|
rs.initiate({
|
|
_id: "rs0",
|
|
members: [{ _id: 0, host: "${appName}:27017", priority: 1 }]
|
|
});
|
|
|
|
// Wait for the replica set to initialize
|
|
while (!rs.isMaster().ismaster) {
|
|
sleep(1000);
|
|
}
|
|
|
|
// Create root user after replica set is initialized and we are primary
|
|
db.getSiblingDB("admin").createUser({
|
|
user: "${databaseUser}",
|
|
pwd: "${databasePassword}",
|
|
roles: ["root"]
|
|
});
|
|
'
|
|
|
|
else
|
|
echo "Replica set already initialized."
|
|
fi
|
|
`
|
|
: ""
|
|
}
|
|
|
|
${command ?? "wait $MONGOD_PID"}`;
|
|
|
|
const defaultMongoEnv = `MONGO_INITDB_ROOT_USERNAME="${databaseUser}"\nMONGO_INITDB_ROOT_PASSWORD="${databasePassword}"${replicaSets ? "\nMONGO_INITDB_DATABASE=admin" : ""}${
|
|
env ? `\n${env}` : ""
|
|
}`;
|
|
|
|
const {
|
|
HealthCheck,
|
|
RestartPolicy,
|
|
Placement,
|
|
Labels,
|
|
Mode,
|
|
RollbackConfig,
|
|
UpdateConfig,
|
|
Networks,
|
|
StopGracePeriod,
|
|
EndpointSpec,
|
|
Ulimits,
|
|
} = generateConfigContainer(mongo);
|
|
|
|
const resources = calculateResources({
|
|
memoryLimit,
|
|
memoryReservation,
|
|
cpuLimit,
|
|
cpuReservation,
|
|
});
|
|
|
|
const envVariables = prepareEnvironmentVariables(
|
|
defaultMongoEnv,
|
|
mongo.environment.project.env,
|
|
mongo.environment.env,
|
|
);
|
|
const volumesMount = generateVolumeMounts(mounts);
|
|
const bindsMount = generateBindMounts(mounts);
|
|
const filesMount = generateFileMounts(appName, mongo);
|
|
|
|
const docker = await getRemoteDocker(mongo.serverId);
|
|
|
|
const settings: CreateServiceOptions = {
|
|
Name: appName,
|
|
TaskTemplate: {
|
|
ContainerSpec: {
|
|
HealthCheck,
|
|
Image: dockerImage,
|
|
Env: envVariables,
|
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
|
...(StopGracePeriod !== null &&
|
|
StopGracePeriod !== undefined && { StopGracePeriod }),
|
|
...(replicaSets
|
|
? {
|
|
Command: ["/bin/bash"],
|
|
Args: ["-c", startupScript],
|
|
}
|
|
: {}),
|
|
...(command &&
|
|
!replicaSets && {
|
|
Command: command.split(" "),
|
|
}),
|
|
...(args &&
|
|
args.length > 0 &&
|
|
!replicaSets && {
|
|
Args: args,
|
|
}),
|
|
...(Ulimits && { Ulimits }),
|
|
Labels,
|
|
},
|
|
Networks,
|
|
RestartPolicy,
|
|
Placement,
|
|
Resources: {
|
|
...resources,
|
|
},
|
|
},
|
|
Mode,
|
|
RollbackConfig,
|
|
EndpointSpec: EndpointSpec
|
|
? EndpointSpec
|
|
: {
|
|
Mode: "dnsrr" as const,
|
|
Ports: externalPort
|
|
? [
|
|
{
|
|
Protocol: "tcp" as const,
|
|
TargetPort: 27017,
|
|
PublishedPort: externalPort,
|
|
PublishMode: "host" as const,
|
|
},
|
|
]
|
|
: [],
|
|
},
|
|
UpdateConfig: mongo.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);
|
|
}
|
|
};
|