fix: preserve named docker volumes during rebuild

This commit is contained in:
KodekaTeam committed 2026-07-30 15:56:39 +07:00
1 parent b358138f77
commit 5c81d387b6
5 files changed
+69 -48

No files matched your search

+6 -16
View File
@@ -38,6 +38,10 @@ import {
startDeploymentLockHeartbeat,
} from "../services/deployment-lock.service";
import { sanitizeDeploymentError } from "../services/deployment-error.service";
import {
formatDockerInspectMountBindings,
type DockerInspectMount as DockerInspectRuntimeMount,
} from "../services/docker-inspect-format";
const DeploySourceTypeSchema = z.enum([
"APP_INSTALLER",
@@ -972,11 +976,7 @@ type DockerInspectRuntime = {
>;
NetworkMode?: string | null;
};
Mounts?: Array<{
Source?: string;
Destination?: string;
RW?: boolean;
}>;
Mounts?: DockerInspectRuntimeMount[];
};
type DockerPortBindings = Record<
@@ -1005,16 +1005,6 @@ function formatEnvLines(env?: string[] | null): string {
return (env ?? []).filter(Boolean).join("\n");
}
function formatMountBindings(mounts?: DockerInspectRuntime["Mounts"]): string {
return (mounts ?? [])
.map((mount) => {
if (!mount.Source || !mount.Destination) return "";
return `${mount.Source}:${mount.Destination}${mount.RW === false ? ":ro" : ""}`;
})
.filter(Boolean)
.join(",");
}
function formatPortBindings(bindings?: DockerPortBindings): string {
if (!bindings) return "";
@@ -1130,7 +1120,7 @@ async function resolveContainerRuntimeConfig(container: {
image: inspect.Config?.Image?.trim() || container.image,
ports: formatPortBindings(inspect.HostConfig?.PortBindings),
env: formatEnvLines(inspect.Config?.Env),
volumes: formatMountBindings(inspect.Mounts),
volumes: formatDockerInspectMountBindings(inspect.Mounts),
network: inspect.HostConfig?.NetworkMode?.trim() || "bridge",
restartPolicy:
inspect.HostConfig?.RestartPolicy?.Name?.trim() ||
@@ -11,6 +11,10 @@ import {
releaseDeploymentLock,
startDeploymentLockHeartbeat,
} from "./deployment-lock.service";
import {
formatDockerInspectMountBindings,
type DockerInspectMount,
} from "./docker-inspect-format";
type DockerInspectRuntime = {
Config?: {
@@ -26,11 +30,7 @@ type DockerInspectRuntime = {
>;
NetworkMode?: string | null;
};
Mounts?: Array<{
Source?: string;
Destination?: string;
RW?: boolean;
}>;
Mounts?: DockerInspectMount[];
};
type DockerPortBindings = Record<
@@ -42,16 +42,6 @@ function formatEnvLines(env?: string[] | null): string {
return (env ?? []).filter(Boolean).join("\n");
}
function formatMountBindings(mounts?: DockerInspectRuntime["Mounts"]): string {
return (mounts ?? [])
.map((mount) => {
if (!mount.Source || !mount.Destination) return "";
return `${mount.Source}:${mount.Destination}${mount.RW === false ? ":ro" : ""}`;
})
.filter(Boolean)
.join(",");
}
function formatPortBindings(bindings?: DockerPortBindings): string {
if (!bindings) return "";
@@ -122,7 +112,7 @@ async function resolveRuntimeConfig(install: {
image: inspect.Config?.Image?.trim() || "",
ports: formatPortBindings(inspect.HostConfig?.PortBindings),
env: formatEnvLines(inspect.Config?.Env),
volumes: formatMountBindings(inspect.Mounts),
volumes: formatDockerInspectMountBindings(inspect.Mounts),
network: inspect.HostConfig?.NetworkMode?.trim() || "bridge",
restartPolicy:
inspect.HostConfig?.RestartPolicy?.Name?.trim() || "unless-stopped",
@@ -15,6 +15,10 @@ import {
startDeploymentLockHeartbeat,
} from "./deployment-lock.service";
import { sanitizeDeploymentError } from "./deployment-error.service";
import {
formatDockerInspectMountBindings,
type DockerInspectMount,
} from "./docker-inspect-format";
type RollbackRuntime = {
image: string;
@@ -43,11 +47,7 @@ type DockerInspectRuntime = {
>;
NetworkMode?: string | null;
};
Mounts?: Array<{
Source?: string;
Destination?: string;
RW?: boolean;
}>;
Mounts?: DockerInspectMount[];
};
type DockerPortBindings = NonNullable<
@@ -139,15 +139,6 @@ function formatPortBindings(bindings?: DockerPortBindings) {
.join(",");
}
function formatMountBindings(mounts?: DockerInspectRuntime["Mounts"]) {
return (mounts ?? [])
.flatMap((mount) => {
if (!mount.Source || !mount.Destination) return [];
return `${mount.Source}:${mount.Destination}${mount.RW === false ? ":ro" : ""}`;
})
.join(",");
}
function formatEntrypoint(value?: string[] | string | null) {
if (typeof value === "string") return value.trim() || undefined;
return value?.[0]?.trim() || undefined;
@@ -258,7 +249,7 @@ async function resolvePreviousRuntime(input: {
image: inspect.Config?.Image?.trim() || input.container.image,
ports: formatPortBindings(inspect.HostConfig?.PortBindings),
env: (inspect.Config?.Env ?? []).filter(Boolean).join("\n"),
volumes: formatMountBindings(inspect.Mounts),
volumes: formatDockerInspectMountBindings(inspect.Mounts),
network: inspect.HostConfig?.NetworkMode?.trim() || "bridge",
restartPolicy:
inspect.HostConfig?.RestartPolicy?.Name?.trim() ||
@@ -711,4 +702,3 @@ export async function rollbackContainerToDeployment(input: {
}
}
@@ -0,0 +1,29 @@
export type DockerInspectMount = {
Type?: string;
Name?: string;
Source?: string;
Destination?: string;
RW?: boolean;
};
function resolveMountSource(mount: DockerInspectMount) {
if (mount.Type === "volume") {
return mount.Name?.trim() || "";
}
return mount.Source?.trim() || "";
}
export function formatDockerInspectMountBindings(
mounts?: DockerInspectMount[] | null,
) {
return (mounts ?? [])
.flatMap((mount) => {
const source = resolveMountSource(mount);
const destination = mount.Destination?.trim();
if (!source || !destination) return [];
return `${source}:${destination}${mount.RW === false ? ":ro" : ""}`;
})
.join(",");
}
@@ -1,6 +1,7 @@
import assert from "node:assert/strict";
import test from "node:test";
import { formatDockerInspectMountBindings } from "../../src/server/services/docker-inspect-format";
import { buildDockerRunCommand } from "../../src/server/services/ssh-services/docker-containers";
const BASE_OPTS = {
@@ -57,3 +58,24 @@ test("volume validation only allows docker.sock when explicitly trusted", () =>
assert.match(command, /-v' '/);
assert.match(command, /\/var\/run\/docker\.sock:\/var\/run\/docker\.sock/);
});
test("docker inspect formatter preserves named volumes instead of host mountpoints", () => {
assert.equal(
formatDockerInspectMountBindings([
{
Type: "volume",
Name: "nakama-data",
Source: "/var/lib/docker/volumes/nakama-data/_data",
Destination: "/nakama/data",
RW: true,
},
{
Type: "bind",
Source: "/srv/doktainer/apps/site",
Destination: "/app",
RW: false,
},
]),
"nakama-data:/nakama/data,/srv/doktainer/apps/site:/app:ro",
);
});