{"record":{"id":"e211fd5c991293c6","repo":"coollabsio/coolify","slug":"the-following-file-is-a-directory-on-the-server-b","errorCode":null,"errorMessage":"The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.","messagePattern":"The following file is a directory on the server, but you are trying to mark it as a file\\. <br><br>Please delete the directory on the server or mark it as directory\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"app/Models/LocalFileVolume.php","lineNumber":266,"sourceCode":"        $escapedPath = escapeshellarg($path);\n\n        $isFile = instant_remote_process([\"test -f {$escapedPath} && echo OK || echo NOK\"], $server);\n        $isDir = instant_remote_process([\"test -d {$escapedPath} && echo OK || echo NOK\"], $server);\n        if ($isFile === 'OK' && $this->is_directory) {\n            if ($this->remoteFileExceedsLimit($escapedPath, $server)) {\n                $this->content = self::TOO_LARGE_PLACEHOLDER;\n            } else {\n                $this->content = instant_remote_process([\"cat {$escapedPath}\"], $server, false);\n            }\n            $this->is_directory = false;\n            $this->save();\n            FileStorageChanged::dispatch(data_get($server, 'team_id'));\n            throw new \\Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');\n        } elseif ($isDir === 'OK' && ! $this->is_directory) {\n            if ($path === '/' || $path === '.' || $path === '..' || $path === '' || str($path)->isEmpty() || is_null($path)) {\n                $this->is_directory = true;\n                $this->save();\n                throw new \\Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');\n            }\n            instant_remote_process([\n                \"rm -fr {$escapedPath}\",\n                \"touch {$escapedPath}\",\n            ], $server, false);\n            FileStorageChanged::dispatch(data_get($server, 'team_id'));\n        }\n        if ($isDir === 'NOK' && ! $this->is_directory) {\n            $chmod = data_get($this, 'chmod');\n            $chown = data_get($this, 'chown');\n            if ($content) {\n                $content = base64_encode($content);\n                $commands->push(\"echo '$content' | base64 -d | tee {$escapedPath} > /dev/null\");\n            } else {\n                $commands->push(\"touch {$escapedPath}\");\n            }\n            $commands->push(\"chmod +x {$escapedPath}\");\n            if ($chown) {","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/coollabsio/coolify/blob/70b9acc42467278373e00de77abb40684e25b395/app/Models/LocalFileVolume.php#L248-L284","documentation":"Thrown by LocalFileVolume::saveStorageOnServer() (app/Models/LocalFileVolume.php:266) when the model is marked is_directory=false but the path on the server is a directory, and the resolved path is one of the dangerous roots '/', '.', '..', or empty. Coolify refuses the destructive fix-up it would normally do (rm -fr + touch) for these roots, flips is_directory to true, saves, and throws to inform the operator. For non-root directory paths the code instead silently replaces the directory with an empty file — this error only fires for the root-path cases.","triggerScenarios":"A file storage with is_directory=false whose fs_path resolves to '/', '.', '..', '' or a path string that trims to empty — e.g. fs_path set to '.' (relative to workdir) while the workdir itself is the target. The test -d check succeeds, the root-path guard matches, the model is corrected to is_directory=true, then the exception is raised.","commonSituations":"Entering '.', '/', or blank in the storage path field; relative fs_path values like './' that resolve to the container workdir; importing service templates whose storage paths were left as '.'.","solutions":["Set an explicit non-empty fs_path (e.g. '/data/app.db' or './config'), never '/', '.', '..' or blank.","If a directory is actually intended, mark the storage as a directory so the model and server state agree.","Refresh and re-save after the throw — the model has already been auto-corrected to is_directory=true for this path."],"exampleFix":"// before\n$fileVolume->fs_path = '.';\n$fileVolume->is_directory = false;\n$fileVolume->saveStorageOnServer(); // throws: '.' resolves to a directory\n\n// after\n$fileVolume->fs_path = './data';\n$fileVolume->saveStorageOnServer();","handlingStrategy":"validation","validationCode":"// Reject unsafe paths before saving a file-type storage\n$fsPath = trim((string) $fileVolume->fs_path);\nif (in_array($fsPath, ['/', '.', '..', ''], true) || $fileVolume->fs_path === null) {\n    throw new \\InvalidArgumentException('fs_path must be an explicit, non-root path.');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $fileVolume->saveStorageOnServer();\n} catch (\\Exception $e) {\n    if (str_contains($e->getMessage(), 'you are trying to mark it as a file')) {\n        // fs_path resolved to a directory root; the model was flipped to is_directory=true\n        $fileVolume->refresh();\n        return 'Set an explicit file path instead of a directory root.';\n    }\n    throw $e;\n}","preventionTips":["Never use '/', '.', '..' or empty strings as fs_path; always give an explicit path segment.","Mark storages as directories when the target is (or will be) a directory so the destructive rm -fr branch is never needed."],"tags":["coolify","file-storage","path-validation","remote-execution","state-mismatch"],"backgroundTag":"filesystem-type-mismatch","analyzedSha":"70b9acc42467278373e00de77abb40684e25b395","analyzedAt":"2026-08-17T01:41:01.313Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}