dedupe duplicate mount points when recreating a container

This commit is contained in:
jarek committed 2026-08-09 10:31:28 +02:00
1 parent d33185d1fa
commit 4e15c2677c
2 files changed
+42 -1

No files matched your search

+36
View File
@@ -1,4 +1,5 @@
type HostConfigLike = {
Tmpfs?: Record<string, string> | null;
Binds?: string[] | null;
Mounts?: Array<{ Target?: string | null }> | null;
};
@@ -9,6 +10,41 @@ type InspectMountLike = {
Destination?: string | null;
};
/** Destination path of a bind string ("src:/dst:ro" -> "/dst", "/dst" -> "/dst"). */
function bindTarget(bind: string): string {
const parts = bind.split(':');
return parts.length >= 2 ? parts[1] : parts[0];
}
/**
* Drop Config.Volumes entries whose path is already mounted, so recreate never sends a duplicate
* mount point (#1088 / #1363). The path can be occupied by Tmpfs, an existing bind, a bind we're
* ABOUT to add (getAdditionalVolumeBinds), or a volume that shows up only in inspect.Mounts (the
* shape a remote/hawser daemon returns) - the earlier fix checked Tmpfs/Binds only, so a named
* volume at an image-VOLUME path (e.g. cups /etc/cups) slipped through and collided with the bind
* re-added from inspect.Mounts. Checking all four sources in one place closes that gap.
*/
export function dedupeVolumesForRecreate(
volumes: Record<string, unknown> | undefined | null,
hostConfig: HostConfigLike,
mounts: InspectMountLike[],
additionalBinds: string[]
): Record<string, unknown> | undefined {
if (!volumes) return undefined;
const mounted = new Set<string>();
for (const p of Object.keys(hostConfig.Tmpfs || {})) mounted.add(p);
for (const b of hostConfig.Binds || []) mounted.add(bindTarget(b));
for (const b of additionalBinds) mounted.add(bindTarget(b));
for (const m of hostConfig.Mounts || []) { if (m?.Target) mounted.add(m.Target); }
for (const m of mounts || []) { if (m.Destination) mounted.add(m.Destination); }
const kept: Record<string, unknown> = {};
for (const [path, val] of Object.entries(volumes)) {
if (!mounted.has(path)) kept[path] = val;
}
return Object.keys(kept).length > 0 ? kept : undefined;
}
/** Build extra bind strings for volume mounts missing from HostConfig. */
export function getAdditionalVolumeBinds(
hostConfig: HostConfigLike,
+6 -1
View File
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { authorize } from '$lib/server/authorize';
import { getAdditionalVolumeBinds } from '$lib/server/mount-dedupe';
import { getAdditionalVolumeBinds, dedupeVolumesForRecreate } from '$lib/server/mount-dedupe';
import {
getOwnContainerId,
getHostDockerSocket,
@@ -169,6 +169,11 @@ function buildCreateConfig(inspectData: any, newImage: string): any {
delete createConfig.Hostname;
const additionalBinds = getAdditionalVolumeBinds(hostConfig, inspectData.Mounts || []);
// Drop image-VOLUME entries that collide with a bind/tmpfs/inspect mount, so create never
// sends a duplicate mount point (#1088 / #1363).
const kept = dedupeVolumesForRecreate(createConfig.Volumes, hostConfig, inspectData.Mounts || [], additionalBinds);
if (kept) createConfig.Volumes = kept;
else delete createConfig.Volumes;
if (additionalBinds.length > 0) {
createConfig.HostConfig = {
...createConfig.HostConfig,