diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php
index 178876b89..9196f9fb8 100644
--- a/bootstrap/helpers/constants.php
+++ b/bootstrap/helpers/constants.php
@@ -48,6 +48,8 @@ const DATABASE_DOCKER_IMAGES = [
'influxdb',
'clickhouse/clickhouse-server',
'timescaledb/timescaledb',
+ 'timescaledb', // Matches timescale/timescaledb
+ 'timescaledb-ha', // Matches timescale/timescaledb-ha
'pgvector/pgvector',
];
const SPECIFIC_SERVICES = [
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index f6d69ef60..759d345b0 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -770,10 +770,26 @@ function isDatabaseImage(?string $image = null, ?array $serviceConfig = null)
}
$imageName = $image->before(':');
- // First check if it's a known database image
+ // Extract base image name (ignore registry prefix)
+ // Examples:
+ // docker.io/library/postgres -> postgres
+ // ghcr.io/postgrest/postgrest -> postgrest
+ // postgres -> postgres
+ // postgrest/postgrest -> postgrest
+ $baseImageName = $imageName;
+ if (str($imageName)->contains('/')) {
+ $baseImageName = str($imageName)->afterLast('/');
+ }
+
+ // Check if base image name exactly matches a known database image
$isKnownDatabase = false;
foreach (DATABASE_DOCKER_IMAGES as $database_docker_image) {
- if (str($imageName)->contains($database_docker_image)) {
+ // Extract base name from database pattern for comparison
+ $databaseBaseName = str($database_docker_image)->contains('/')
+ ? str($database_docker_image)->afterLast('/')
+ : $database_docker_image;
+
+ if ($baseImageName == $databaseBaseName) {
$isKnownDatabase = true;
break;
}
diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php
index 9b81e4bec..7379ca706 100644
--- a/resources/views/livewire/project/service/configuration.blade.php
+++ b/resources/views/livewire/project/service/configuration.blade.php
@@ -37,6 +37,16 @@
+ @if($applications->isEmpty() && $databases->isEmpty())
+
+ No services defined in this Docker Compose file.
+
+ @elseif($applications->isEmpty())
+
+ No applications with domains defined. Only database services are available.
+
+ @endif
+
@foreach ($applications as $application)
str(
diff --git a/tests/Unit/PostgRESTDetectionTest.php b/tests/Unit/PostgRESTDetectionTest.php
new file mode 100644
index 000000000..edf3b203f
--- /dev/null
+++ b/tests/Unit/PostgRESTDetectionTest.php
@@ -0,0 +1,73 @@
+toBeFalse();
+});
+
+test('postgrest image with version is detected as application', function () {
+ $result = isDatabaseImage('postgrest/postgrest:v12.0.2');
+ expect($result)->toBeFalse();
+});
+
+test('postgrest with registry prefix is detected as application', function () {
+ $result = isDatabaseImage('ghcr.io/postgrest/postgrest:latest');
+ expect($result)->toBeFalse();
+});
+
+test('regular postgres image is still detected as database', function () {
+ $result = isDatabaseImage('postgres:15');
+ expect($result)->toBeTrue();
+});
+
+test('postgres with registry prefix is detected as database', function () {
+ $result = isDatabaseImage('docker.io/library/postgres:15');
+ expect($result)->toBeTrue();
+});
+
+test('postgres image with service config is detected correctly', function () {
+ $serviceConfig = [
+ 'image' => 'postgres:15',
+ 'environment' => [
+ 'POSTGRES_PASSWORD=secret',
+ ],
+ ];
+
+ $result = isDatabaseImage('postgres:15', $serviceConfig);
+ expect($result)->toBeTrue();
+});
+
+test('postgrest without service config is still detected as application', function () {
+ $result = isDatabaseImage('postgrest/postgrest', null);
+ expect($result)->toBeFalse();
+});
+
+test('supabase postgres-meta is detected as application', function () {
+ $result = isDatabaseImage('supabase/postgres-meta:latest');
+ expect($result)->toBeFalse();
+});
+
+test('mysql image is detected as database', function () {
+ $result = isDatabaseImage('mysql:8.0');
+ expect($result)->toBeTrue();
+});
+
+test('redis image is detected as database', function () {
+ $result = isDatabaseImage('redis:7');
+ expect($result)->toBeTrue();
+});
+
+test('timescale timescaledb is detected as database', function () {
+ $result = isDatabaseImage('timescale/timescaledb:latest');
+ expect($result)->toBeTrue();
+});
+
+test('mariadb is detected as database', function () {
+ $result = isDatabaseImage('mariadb:10.11');
+ expect($result)->toBeTrue();
+});
+
+test('mongodb is detected as database', function () {
+ $result = isDatabaseImage('mongo:7');
+ expect($result)->toBeTrue();
+});