coollabsio/coolify · error · RuntimeException
The backup target is not a directory or persistent volume.
Error message
The backup target is not a directory or persistent volume.
What it means
Thrown by ScheduledVolumeBackup::sourcePath() (app/Models/ScheduledVolumeBackup.php:171). When computing the path to back up, the routine accepts only two shapes: a LocalPersistentVolume (uses host_path or name) or a LocalFileVolume with is_directory=true (a directory backup). If the morph target backupable is anything else — a LocalFileVolume flagged as a file, an unrelated morph type, or a dangling morph — the RuntimeException fires. This usually means the storage's directory flag was flipped or the target was mutated after the schedule was created.
Source
Thrown at app/Models/ScheduledVolumeBackup.php:171
public function targetName(): string
{
return match (true) {
$this->backupable instanceof LocalFileVolume => $this->backupable->fs_path,
$this->backupable instanceof LocalPersistentVolume => $this->backupable->name,
default => 'Unknown storage',
};
}
public function sourcePath(): string
{
$target = $this->backupable;
if ($target instanceof LocalPersistentVolume) {
return filled($target->host_path) ? $target->host_path : $target->name;
}
if (! $target instanceof LocalFileVolume || ! $target->is_directory) {
throw new \RuntimeException('The backup target is not a directory or persistent volume.');
}
$path = str($target->fs_path);
if ($path->startsWith('.')) {
$resource = $this->targetResource();
if (! $resource || ! method_exists($resource, 'workdir')) {
throw new \RuntimeException('The directory backup workdir is unavailable.');
}
return $resource->workdir().$path->after('.')->toString();
}
return $path->toString();
}
}
View on GitHub (pinned to 70b9acc424)
Solutions
- Mark the target storage as a directory again (is_directory=true in the storage settings) if a directory backup is intended.
- If the storage genuinely became a file mount, delete the stale ScheduledVolumeBackup — it can never run against a file target.
- Guard schedule creation: only offer backup schedules for volumes and directory storages (the scopeForApplication/scopeForService query shapes show the allowed combinations).
Example fix
// before
$backup = ScheduledVolumeBackup::find($uuid);
$path = $backup->sourcePath(); // throws: target is a file storage, not a directory
// after — verify the morph target shape before computing the path
$target = $backup->backupable;
if (! $target instanceof \App\Models\LocalPersistentVolume
&& ! ($target instanceof \App\Models\LocalFileVolume && $target->is_directory)) {
$backup->delete(); // stale schedule
return;
}
$path = $backup->sourcePath(); Defensive patterns
Strategy: type-guard
Validate before calling
// Before creating a schedule, confirm the target is a volume or a directory storage
$ok = $target instanceof \App\Models\LocalPersistentVolume
|| ($target instanceof \App\Models\LocalFileVolume && $target->is_directory && ! $target->is_host_file);
if (! $ok) {
abort(422, 'Backups are only supported for persistent volumes and directory storages.');
} Type guard
function isBackupableDirectoryOrVolume(object $target): bool
{
if ($target instanceof \App\Models\LocalPersistentVolume) {
return true;
}
return $target instanceof \App\Models\LocalFileVolume && $target->is_directory;
} Try / catch
try {
$path = $backup->sourcePath();
} catch (\RuntimeException $e) {
if (str_contains($e->getMessage(), 'not a directory or persistent volume')) {
// target was flipped to a file or is a dangling morph: delete the stale schedule
$backup->delete();
return;
}
throw $e;
} Prevention
- Only expose backup scheduling for LocalPersistentVolume and is_directory LocalFileVolume targets (mirror scopeForApplication/scopeForService).
- When changing a storage's type from directory to file, delete its backup schedules in the same action.
- Remember the saveStorageOnServer() self-heal can flip is_directory=false behind your back (errors 304/305) — re-check schedules after that fires.
When it happens
Trigger: Running a ScheduledVolumeBackup whose backupable is a LocalFileVolume with is_directory=false (someone switched the storage from directory to file), or whose backupable_type/backupable_id pair no longer resolves to a valid volume/directory target (dangling morph after partial cleanup).
Common situations: The self-heal path in LocalFileVolume::saveStorageOnServer() flipping is_directory to false (errors 304/305) on a storage that had a backup schedule; editing service templates that change storage types; manual DB edits to local_file_volumes rows.
Related errors
- The directory backup workdir is unavailable.
- The following file is a file on the server, but you are tryi
- The following file is a directory on the server, but you are
- Failed to store SSH key: {message}
- Failed to open lock file for SSH key: {$lockFile}
AI-assisted analysis of coollabsio/coolify@70b9acc424 (2026-08-17).
Data as JSON: /api/errors/18fd100444e86290.
Report an issue: GitHub.