coollabsio/coolify · error · RuntimeException

The S3 storage used by an existing backup is unavailable.

Error message

The S3 storage used by an existing backup is unavailable.

What it means

Thrown while deleting a volume backup schedule: some executions have s3_uploaded=true and s3_storage_deleted=false, but executions->first()->s3 is null — the S3 storage configuration (S3Storage model) they reference was deleted from Coolify. The action aborts so remote objects in the bucket are not left untracked.

Source

Thrown at app/Actions/Shared/DeleteScheduledVolumeBackup.php:57

            if ($localFilenames !== []) {
                $server ??= $backup->server();
                if (! $server) {
                    throw new \RuntimeException('The server is unavailable, so local backup archives cannot be deleted.');
                }

                deleteBackupsLocally($localFilenames, $server, throwError: true);
            }

            $s3Executions = $backup->executions()
                ->with('s3')
                ->where('s3_uploaded', true)
                ->where('s3_storage_deleted', false)
                ->get();

            foreach ($s3Executions->groupBy('s3_storage_id') as $executions) {
                $s3 = $executions->first()->s3;
                if (! $s3) {
                    throw new \RuntimeException('The S3 storage used by an existing backup is unavailable.');
                }

                $filenames = $executions->pluck('filename')->filter()->all();
                if ($filenames !== []) {
                    deleteBackupsS3($filenames, $s3);
                }
            }

            $backup->delete();
        } finally {
            $lock->release();
        }
    }
}

View on GitHub (pinned to 70b9acc424)

Solutions

  1. Recreate an S3 storage entry with the same credentials/bucket so the action can delete the remote objects, then retry the schedule deletion.
  2. If the bucket is being abandoned, empty the relevant prefix manually in your provider console, then set s3_storage_deleted=true on those executions (or remove the execution rows) and delete the schedule.
  3. Going forward, delete backup schedules that target an S3 storage before deleting the storage itself.

Example fix

// before: deleting the schedule throws because the S3 storage is gone
DeleteScheduledVolumeBackup::run($backup);

// after: explicitly acknowledge the orphaned remote objects first
foreach ($backup->executions()->where('s3_uploaded', true)->where('s3_storage_deleted', false)->get() as $execution) {
    if (is_null($execution->s3)) {
        // objects already removed by hand (or bucket decommissioned)
        $execution->update(['s3_storage_deleted' => true]);
    }
}
DeleteScheduledVolumeBackup::run($backup);
Defensive patterns

Strategy: validation

Validate before calling

// Fail fast if any S3-uploaded execution lost its storage config
$orphanedS3 = $backup->executions()
    ->with('s3')
    ->where('s3_uploaded', true)
    ->where('s3_storage_deleted', false)
    ->get()
    ->filter(fn ($e) => is_null($e->s3))
    ->isNotEmpty();

if ($orphanedS3) {
    // recreate the S3 storage or acknowledge manual bucket cleanup first
}

Type guard

function s3ExecutionsResolvable(\App\Models\ScheduledVolumeBackup $backup): bool
{
    return $backup->executions()
        ->with('s3')
        ->where('s3_uploaded', true)
        ->where('s3_storage_deleted', false)
        ->get()
        ->every(fn ($e) => ! is_null($e->s3));
}

Try / catch

try {
    DeleteScheduledVolumeBackup::run($backup);
} catch (\RuntimeException $e) {
    if (str_contains($e->getMessage(), 'S3 storage')) {
        report('Schedule '.$backup->id.' references a deleted S3 storage; recreate it or clean the bucket manually.');
        return;
    }
    throw $e;
}

Prevention

When it happens

Trigger: Deleting an S3 storage entry in Settings -> S3 Storage before deleting the backup schedules/executions that uploaded to it; groupBy('s3_storage_id') finds executions whose s3 relation no longer exists.

Common situations: Rotating S3 providers: user removes the old storage config assuming backups are independent; cleanup scripts delete storages but not dependent schedules; team membership changes hide the storage while executions still point at its id.

Related errors


AI-assisted analysis of coollabsio/coolify@70b9acc424 (2026-08-17). Data as JSON: /api/errors/a8321892dae3c331. Report an issue: GitHub.