Skip to content

fix(compose): materialize file mounts and purge stale directories before deployment (#5292) - #5447

Open
fliptrigga13 wants to merge 1 commit into
Dokploy:canaryfrom
fliptrigga13:fix/issue-5292-compose-file-mounts-materialize
Open

fliptrigga13 wants to merge 1 commit into
Dokploy:canaryfrom
fliptrigga13:fix/issue-5292-compose-file-mounts-materialize

Conversation

@fliptrigga13

@fliptrigga13 fliptrigga13 commented Sep 13, 2026

Copy link
Copy Markdown

Summary of Changes

Closes #5292

Context & Problem

In Compose projects, services reference file mounts via ../files/<mountPath>:<container target> (e.g. Postgres docker-entrypoint-initdb.d/init.sql or custom config files). As reported in #5292:

  1. getBuildComposeCommand detected mounts (Detected: X mounts 📂 in the log box) but never materialized them to disk before running cd code && docker compose up.
  2. When Docker Compose starts with a bind mount pointing to a non-existent host path, Docker automatically creates an empty directory on the host at that path. As a result, file-type mounts ended up as empty directories inside containers, breaking services silently (e.g., config files unparsed, database entrypoint scripts ignored).
  3. If an empty or leading-slash path was provided, createFile could write directly to /etc/dokploy/compose/<appName>/files, corrupting the root directory into a plain file.
  4. Once an empty directory was created on the host, subsequent attempts by createFile or getCreateFileCommand to write the real file failed or crashed with EISDIR: illegal operation on a directory.

Solution

  1. Materialize Compose Mounts on Deployment:

    • Added getCreateMountsCommand in packages/server/src/utils/builders/compose.ts and integrated it into getBuildComposeCommand.
    • Ensures /etc/dokploy/compose/<appName>/files is created, creates nested parent directories, deletes any stale empty directories left behind by Docker (if [ -d "$fullPath" ]; then rm -rf "$fullPath"; fi), and writes the base64-decoded content before docker compose up executes.
  2. Sanitization & Stale Directory Cleanup in docker/utils.ts:

    • In createFile and getCreateFileCommand:
      • Cleaned filePath to strip leading slashes and whitespace, preventing directory escapes and preventing corruption of the base files folder.
      • Checked if the target path already exists as a directory (created by Docker), and removed it before writing the file with writeFileSync or shell commands.
  3. Automated Testing:

    • Added unit test suite in apps/dokploy/__test__/compose/compose-file-mounts.test.ts.
    • 7/7 tests passing (100% pass rate).

RetriggerConfidence Score: 2/5

This PR is not safe to merge until file paths are contained beneath their application roots and file mounts are handled correctly for multi-node Swarm deployments.

Summary

  • Adds host-side file mount creation to the Compose deployment command.
  • Removes directory-shaped stale mount targets before writing files.
  • Applies equivalent cleanup to local and generated remote file-write helpers.
  • Adds regression tests, although they currently exercise test-only copies rather than production code.
  • Leaves path containment and multi-node Swarm materialization unresolved.

Reviews (1) · Last reviewed commit: "fix(compose): materialize file mounts an..."

let commands = `mkdir -p ${quote([filesPath])};\n`;

for (const mount of fileMounts) {
const cleanPath = (mount.filePath || "").trim().replace(/^[/\\]+/, "");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Mount paths escape containment

cleanPath removes leading separators but still accepts .. components. An authenticated caller with mount-management permission can therefore escape the application's files directory. The deployment command can write controlled content to the escaped host path, while the related cleanup in createFile and getCreateFileCommand can recursively delete an escaped directory before replacing it with a file. Validate that the resolved path remains beneath filesPath or outputPath before creating, deleting, or writing anything.

How this was verified: Mount paths reach these helpers without traversal validation, and the generated operations run on the deployment host with paths normalized from attacker-controlled .. components.

Knowledge Base Used:

const exportEnvCommand = getExportEnvCommand(compose);

const newCompose = await writeDomainsToCompose(compose, domains);
const mountsCommand = getCreateMountsCommand(compose);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Swarm workers miss mount files

Mount materialization runs only on the host executing the deployment command, but it is enabled for both Docker Compose and Swarm stack modes. In a multi-node Swarm, a service using one of these bind-mounted files can be scheduled on a worker where the source file was never created, causing the task to fail its bind mount or use an incorrect host path. Stack deployments need to distribute files to eligible nodes, constrain placement to the prepared node, or reject file mounts that cannot be satisfied across the cluster.

Knowledge Base Used:

Comment on lines +14 to +18
export const generateCreateMountsCommand = (
_appName: string,
baseFilesPath: string,
mounts: Mount[],
) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Tests bypass production code

The regression suite reimplements mount command generation and sanitization instead of importing the exported production function. The copies also use different quoting and path-building helpers, so these tests can keep passing when getCreateMountsCommand, createFile, or getCreateFileCommand regresses. Exercise the production exports directly so the tests protect the code used during deployment.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compose file-type mounts materialize as empty directories instead of files

1 participant