From bbcadbd480437d6578b0c2db08c402d89afa19f3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 24 Oct 2026 18:14:27 +0200 Subject: [PATCH] fix(backups): identify the instance database by id, not by name A user database named coolify-db was backed up as the instance database, which targeted the wrong container. It was also hidden from the server database list and from status updates. Co-Authored-By: Claude Opus 5.5 --- app/Jobs/DatabaseBackupJob.php | 2 +- app/Jobs/PushServerUpdateJob.php | 2 +- app/Models/Server.php | 2 +- app/Models/StandalonePostgresql.php | 8 ++ .../DatabaseBackupInstanceDatabaseTest.php | 73 +++++++++++++++++++ 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 tests/Feature/DatabaseBackupInstanceDatabaseTest.php diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 15b9ff4bba..26337ebe70 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -317,7 +317,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue return; } $this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name; - if ($this->database->name === 'coolify-db') { + if ($this->database instanceof StandalonePostgresql && $this->database->isInstanceDatabase()) { $databasesToBackup = ['coolify']; $this->directory_name = $this->container_name = 'coolify-db'; $ip = Str::slug($this->server->ip); diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index 0107e4bc1d..9a9fd8a56c 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -551,7 +551,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue, Silenced ->select($databaseColumns) ->where(fn ($query) => $this->scopeDestination($query, $standaloneDockerIds, $swarmDockerIds)) ->get(); - })->filter(fn ($database) => data_get($database, 'name') !== 'coolify-db')->values(); + })->reject(fn ($database) => $database instanceof StandalonePostgresql && $database->isInstanceDatabase())->values(); } private function serverDestinationIds(): array diff --git a/app/Models/Server.php b/app/Models/Server.php index 145edd55cd..d5ffa27e76 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1893,7 +1893,7 @@ $siteAddress { ->concat($dragonflies) ->concat($clickhouses) ->concat($sqlites) - ->filter(fn ($item) => data_get($item, 'name') !== 'coolify-db'); + ->reject(fn ($item) => $item instanceof StandalonePostgresql && $item->isInstanceDatabase()); } public function applications() diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 7829ab0d0e..542358fab6 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -320,6 +320,14 @@ class StandalonePostgresql extends BaseModel return 'standalone-postgresql'; } + /** + * Coolify's own database is the instance sentinel row (id 0). Users can give any database the name "coolify-db". + */ + public function isInstanceDatabase(): bool + { + return $this->id === 0; + } + protected function internalDbUrl(): Attribute { return new Attribute( diff --git a/tests/Feature/DatabaseBackupInstanceDatabaseTest.php b/tests/Feature/DatabaseBackupInstanceDatabaseTest.php new file mode 100644 index 0000000000..acad0bbf28 --- /dev/null +++ b/tests/Feature/DatabaseBackupInstanceDatabaseTest.php @@ -0,0 +1,73 @@ + InstanceSettings::firstOrCreate(['id' => 0])); +}); + +it('backs up a user database named coolify-db as an ordinary database', function () { + $team = Team::factory()->create(); + $server = Server::factory()->create([ + 'team_id' => $team->id, + 'private_key_id' => PrivateKey::factory()->create(['team_id' => $team->id])->id, + ]); + $server->settings()->update(['is_reachable' => true, 'is_usable' => true]); + $destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail(); + $environment = Environment::factory()->create(['project_id' => Project::factory()->create(['team_id' => $team->id])->id]); + $database = create_standalone_postgresql($environment->id, $destination, [ + 'name' => 'coolify-db', + 'postgres_db' => 'appdb', + 'status' => 'running:healthy', + ]); + $backup = new ScheduledDatabaseBackup; + $backup->forceFill([ + 'enabled' => true, + 'save_s3' => false, + 'frequency' => 'daily', + 'database_id' => $database->id, + 'database_type' => $database->getMorphClass(), + 'team_id' => $team->id, + ])->save(); + $commands = collect(); + Process::fake(function ($process) use ($commands) { + $command = is_array($process->command) ? implode(' ', $process->command) : $process->command; + $commands->push($command); + + return Process::result(output: str_contains($command, 'du -b') ? '128' : ''); + }); + + (new DatabaseBackupJob($backup->fresh()))->handle(); + + $execution = ScheduledDatabaseBackupExecution::query()->sole(); + $dumpCommand = $commands->first(fn (string $command) => str_contains($command, 'pg_dump')); + expect($database->isInstanceDatabase())->toBeFalse() + ->and($execution->database_name)->toBe('appdb') + ->and($execution->filename)->toContain('/databases/') + ->and($execution->filename)->not->toContain('/coolify/coolify-db-') + ->and($dumpCommand)->toContain($database->uuid) + ->and($dumpCommand)->not->toContain('exec coolify-db '); + expect($server->databases()->pluck('id'))->toContain($database->id); +});