From bc22d05f8dcc9f338e64c795cbee75989e8dc6e9 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 14 Jul 2026 11:34:46 -0600 Subject: [PATCH] fix(compose): preserve named-volume access mode when adding suffix The randomize/isolated-deployment volume transform split mount strings on ':' and kept only the first two segments, so an access mode like :ro, :z or :Z was silently dropped and read-only mounts became read-write. Keep the full path+mode remainder when rebuilding the mount string. Fixes #4818 --- .../compose/volume/volume-services.test.ts | 33 +++++++++++++++++++ .../server/src/utils/docker/compose/volume.ts | 9 +++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/__test__/compose/volume/volume-services.test.ts b/apps/dokploy/__test__/compose/volume/volume-services.test.ts index a42ab5fa9..8213404bb 100644 --- a/apps/dokploy/__test__/compose/volume/volume-services.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume-services.test.ts @@ -42,6 +42,39 @@ test("Add suffix to volumes declared directly in services", () => { ); }); +const composeFileAccessMode = ` +version: "3.8" + +services: + web: + image: nginx:alpine + volumes: + - web_config:/etc/nginx/conf.d:ro + - certs/sub:/etc/certs:Z +`; + +test("Add suffix to volumes preserves access mode (:ro, :z, :Z)", () => { + const composeData = parse(composeFileAccessMode) as ComposeSpecification; + + const suffix = generateRandomHash(); + + if (!composeData.services) { + return; + } + + const updatedComposeData = addSuffixToVolumesInServices( + composeData.services, + suffix, + ); + + expect(updatedComposeData.web?.volumes).toContain( + `web_config-${suffix}:/etc/nginx/conf.d:ro`, + ); + expect(updatedComposeData.web?.volumes).toContain( + `certs-${suffix}/sub:/etc/certs:Z`, + ); +}); + const composeFileTypeVolume = ` version: "3.8" diff --git a/packages/server/src/utils/docker/compose/volume.ts b/packages/server/src/utils/docker/compose/volume.ts index be4c7b206..6708758cb 100644 --- a/packages/server/src/utils/docker/compose/volume.ts +++ b/packages/server/src/utils/docker/compose/volume.ts @@ -26,11 +26,14 @@ export const addSuffixToVolumesInServices = ( if (_.has(newServiceConfig, "volumes")) { newServiceConfig.volumes = _.map(newServiceConfig.volumes, (volume) => { if (_.isString(volume)) { - const [volumeName, path] = volume.split(":"); + // remainder is the container path plus optional access mode (:ro, :z, :Z) + const [volumeName, ...pathAndMode] = volume.split(":"); + const remainder = pathAndMode.join(":"); // skip bind mounts and variables (e.g. $PWD) if ( !volumeName || + !remainder || volumeName.startsWith(".") || volumeName.startsWith("/") || volumeName.startsWith("$") @@ -43,10 +46,10 @@ export const addSuffixToVolumesInServices = ( if (parts.length > 1) { const baseName = parts[0]; const rest = parts.slice(1).join("/"); - return `${baseName}-${suffix}/${rest}:${path}`; + return `${baseName}-${suffix}/${rest}:${remainder}`; } - return `${volumeName}-${suffix}:${path}`; + return `${volumeName}-${suffix}:${remainder}`; } if (_.isObject(volume) && volume.type === "volume" && volume.source) { return {