fix: preserve named docker volumes during rebuild
This commit is contained in:
1 parent
b358138f77
commit
5c81d387b6
5 files changed
+69
-48
No files matched your search
@@ -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",
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user