mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-18 11:12:56 +00: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
160 lines
3.5 KiB
TypeScript
160 lines
3.5 KiB
TypeScript
import type { InferResultType } from "@dokploy/server/types/with";
|
|
import type { CreateServiceOptions, PortConfig } from "dockerode";
|
|
import {
|
|
calculateResources,
|
|
generateBindMounts,
|
|
generateConfigContainer,
|
|
generateFileMounts,
|
|
generateVolumeMounts,
|
|
prepareEnvironmentVariables,
|
|
} from "../docker/utils";
|
|
import { getRemoteDocker } from "../servers/remote-docker";
|
|
|
|
export type LibsqlNested = InferResultType<
|
|
"libsql",
|
|
{
|
|
mounts: true;
|
|
environment: { with: { project: true } };
|
|
}
|
|
>;
|
|
export const buildLibsql = async (libsql: LibsqlNested) => {
|
|
const {
|
|
appName,
|
|
env,
|
|
externalPort,
|
|
externalGRPCPort,
|
|
externalAdminPort,
|
|
memoryLimit,
|
|
memoryReservation,
|
|
databaseUser,
|
|
databasePassword,
|
|
sqldNode,
|
|
sqldPrimaryUrl,
|
|
cpuLimit,
|
|
cpuReservation,
|
|
command,
|
|
mounts,
|
|
enableNamespaces,
|
|
} = libsql;
|
|
|
|
const basicAuth = Buffer.from(
|
|
`${databaseUser}:${databasePassword}`,
|
|
"utf-8",
|
|
).toString("base64");
|
|
|
|
const defaultLibsqlEnv = `SQLD_NODE="${sqldNode}"\nSQLD_HTTP_AUTH="basic:${basicAuth}"${
|
|
env ? `\n${env}` : ""
|
|
}${sqldNode === "replica" ? `\nSQLD_PRIMARY_URL="${sqldPrimaryUrl}"` : ""}`;
|
|
|
|
const {
|
|
HealthCheck,
|
|
RestartPolicy,
|
|
Placement,
|
|
Labels,
|
|
Mode,
|
|
RollbackConfig,
|
|
UpdateConfig,
|
|
Networks,
|
|
} = generateConfigContainer(libsql);
|
|
const resources = calculateResources({
|
|
memoryLimit,
|
|
memoryReservation,
|
|
cpuLimit,
|
|
cpuReservation,
|
|
});
|
|
const envVariables = prepareEnvironmentVariables(
|
|
defaultLibsqlEnv,
|
|
libsql.environment.project.env,
|
|
libsql.environment.env,
|
|
);
|
|
const volumesMount = generateVolumeMounts(mounts);
|
|
const bindsMount = generateBindMounts(mounts);
|
|
const filesMount = generateFileMounts(appName, libsql);
|
|
|
|
const docker = await getRemoteDocker(libsql.serverId);
|
|
|
|
let finalCommand =
|
|
command ??
|
|
"sqld --db-path iku.db --http-listen-addr 0.0.0.0:8080 --grpc-listen-addr 0.0.0.0:5001 --admin-listen-addr 0.0.0.0:5000";
|
|
if (enableNamespaces) {
|
|
finalCommand += " --enable-namespaces";
|
|
}
|
|
|
|
const settings: CreateServiceOptions = {
|
|
Name: appName,
|
|
TaskTemplate: {
|
|
ContainerSpec: {
|
|
HealthCheck,
|
|
Image: "ghcr.io/tursodatabase/libsql-server:v0.24.32",
|
|
Env: envVariables,
|
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
|
...(finalCommand
|
|
? {
|
|
Command: ["/bin/sh"],
|
|
Args: ["-c", finalCommand],
|
|
}
|
|
: {}),
|
|
Labels,
|
|
},
|
|
Networks,
|
|
RestartPolicy,
|
|
Placement,
|
|
Resources: {
|
|
...resources,
|
|
},
|
|
},
|
|
Mode,
|
|
RollbackConfig,
|
|
EndpointSpec: {
|
|
Mode: "dnsrr",
|
|
Ports: [
|
|
...(externalPort
|
|
? [
|
|
{
|
|
Protocol: "tcp",
|
|
TargetPort: 8080,
|
|
PublishedPort: externalPort,
|
|
PublishMode: "host",
|
|
} as PortConfig,
|
|
]
|
|
: []),
|
|
...(externalGRPCPort
|
|
? [
|
|
{
|
|
Protocol: "tcp",
|
|
TargetPort: 5001,
|
|
PublishedPort: externalGRPCPort,
|
|
PublishMode: "host",
|
|
} as PortConfig,
|
|
]
|
|
: []),
|
|
...(externalAdminPort
|
|
? [
|
|
{
|
|
Protocol: "tcp",
|
|
TargetPort: 5000,
|
|
PublishedPort: externalAdminPort,
|
|
PublishMode: "host",
|
|
} as PortConfig,
|
|
]
|
|
: []),
|
|
],
|
|
},
|
|
UpdateConfig: libsql.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,
|
|
});
|
|
} catch {
|
|
await docker.createService(settings);
|
|
}
|
|
};
|