diff --git a/build.gradle b/build.gradle
index 532d932ef..2615b335d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -52,6 +52,12 @@ subprojects {
options.encoding = 'UTF-8'
}
+ // Configure test tasks for all subprojects
+ tasks.withType( Test ).configureEach {
+ // Set the project root for finding Docker files - available to all modules
+ systemProperty 'hibernate.reactive.project.root', rootProject.projectDir.absolutePath
+ }
+
if ( !gradle.ext.javaToolchainEnabled ) {
sourceCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion )
targetCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion )
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c1fa1d0b3..99b4a30f2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,7 +7,7 @@ jbossLoggingAnnotationVersion = "3.0.4.Final"
jbossLoggingVersion = "3.6.1.Final"
junitVersion = "5.11.3"
log4jVersion = "2.20.0"
-testcontainersVersion = "1.21.0"
+testcontainersVersion = "1.21.3"
vertxSqlClientVersion = "5.0.0"
vertxWebVersion= "5.0.0"
vertxWebClientVersion = "5.0.0"
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java
index bf46f835a..31bcf2380 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java
@@ -27,7 +27,13 @@
import jakarta.persistence.metamodel.EntityType;
import static java.util.concurrent.TimeUnit.MINUTES;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
@Timeout(value = 10, timeUnit = MINUTES)
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java
index e1a0928d1..172ace2bc 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java
@@ -12,7 +12,7 @@
import org.testcontainers.containers.CockroachContainer;
import org.testcontainers.containers.Container;
-import static org.hibernate.reactive.containers.DockerImage.imageName;
+import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
class CockroachDBDatabase extends PostgreSQLDatabase {
@@ -25,7 +25,7 @@ class CockroachDBDatabase extends PostgreSQLDatabase {
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
*/
- public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v24.3.13" ) )
+ public static final CockroachContainer cockroachDb = new CockroachContainer( fromDockerfile( "cockroachdb" ) )
// Username, password and database are not supported by test container at the moment
// Testcontainers will use a database named 'postgres' and the 'root' user
.withReuse( true );
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java
index dfeeaf15c..c2b60e3d6 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java
@@ -28,7 +28,7 @@
import org.testcontainers.containers.Db2Container;
-import static org.hibernate.reactive.containers.DockerImage.imageName;
+import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
class DB2Database implements TestableDatabase {
@@ -87,7 +87,7 @@ class DB2Database implements TestableDatabase {
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
*/
- static final Db2Container db2 = new Db2Container( imageName( "icr.io", "db2_community/db2", "12.1.0.0" ) )
+ static final Db2Container db2 = new Db2Container( fromDockerfile( "db2" ) )
.withUsername( DatabaseConfiguration.USERNAME )
.withPassword( DatabaseConfiguration.PASSWORD )
.withDatabaseName( DatabaseConfiguration.DB_NAME )
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java
index 1b2f3f6a7..e8f40f34d 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java
@@ -5,10 +5,17 @@
*/
package org.hibernate.reactive.containers;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
import org.testcontainers.utility.DockerImageName;
+
/**
- * A utility class with methods to generate {@link DockerImageName} for testcontainers.
+ * A utility class with methods to generate a {@link DockerImageName} for Testcontainers.
*
* Testcontainers might not work if the image required is available in multiple different registries (for example when
* using podman instead of docker).
@@ -17,10 +24,28 @@
*/
public final class DockerImage {
- public static final String DEFAULT_REGISTRY = "docker.io";
+ /**
+ * The absolute path of the project root that we have set in Gradle.
+ */
+ private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" );
+
+ /**
+ * The path to the directory containing all the Dockerfile files
+ */
+ private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" );
- public static DockerImageName imageName(String image, String version) {
- return imageName( DEFAULT_REGISTRY, image, version );
+ /**
+ * Extract the image name and version from the first FROM instruction in the Dockerfile.
+ * Note that everything else is ignored.
+ */
+ public static DockerImageName fromDockerfile(String databaseName) {
+ try {
+ final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() );
+ return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() );
+ }
+ catch (IOException e) {
+ throw new RuntimeException( e );
+ }
}
public static DockerImageName imageName(String registry, String image, String version) {
@@ -28,4 +53,74 @@ public static DockerImageName imageName(String registry, String image, String ve
.parse( registry + "/" + image + ":" + version )
.asCompatibleSubstituteFor( image );
}
+
+ private static class ImageInformation {
+ private final String registry;
+ private final String image;
+ private final String version;
+
+ public ImageInformation(String fullImageInfo) {
+ // FullImageInfo pattern: /:
+ // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13"
+ final int registryEndPos = fullImageInfo.indexOf( '/' );
+ final int imageEndPos = fullImageInfo.lastIndexOf( ':' );
+ this.registry = fullImageInfo.substring( 0, registryEndPos );
+ this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos );
+ this.version = fullImageInfo.substring( imageEndPos + 1 );
+ }
+
+ public String getRegistry() {
+ return registry;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ return registry + "/" + image + ":" + version;
+ }
+ }
+
+ private static Path dockerFilePath(String database) {
+ // Get project root from system property set by Gradle, with fallback
+ return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" );
+ }
+
+ private static ImageInformation readFromInstruction(String database) throws IOException {
+ return readFromInstruction( dockerFilePath( database ) );
+ }
+
+ /**
+ * Read a Dockerfile and extract the first FROM instruction.
+ *
+ * @param dockerfilePath path to the Dockerfile
+ * @return the first FROM instruction found, or empty if none found
+ * @throws IOException if the file cannot be read
+ */
+ private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException {
+ if ( !Files.exists( dockerfilePath ) ) {
+ throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath );
+ }
+
+ List lines = Files.readAllLines( dockerfilePath );
+ for ( String line : lines ) {
+ // Skip comments and empty lines
+ String trimmedLine = line.trim();
+ if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) {
+ continue;
+ }
+
+ if ( trimmedLine.startsWith( "FROM " ) ) {
+ return new ImageInformation( trimmedLine.substring( "FROM ".length() ) );
+ }
+ }
+
+ throw new IOException( " Missing FROM instruction in " + dockerfilePath );
+ }
}
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java
index aeb1b8fb0..eaab2e8fb 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java
@@ -28,7 +28,7 @@
import org.testcontainers.containers.MSSQLServerContainer;
-import static org.hibernate.reactive.containers.DockerImage.imageName;
+import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
/**
* The JDBC driver syntax is:
@@ -96,7 +96,7 @@ class MSSQLServerDatabase implements TestableDatabase {
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
*/
- public static final MSSQLServerContainer> mssqlserver = new MSSQLServerContainer<>( imageName( "mcr.microsoft.com", "mssql/server", "2022-latest" ) )
+ public static final MSSQLServerContainer> mssqlserver = new MSSQLServerContainer<>( fromDockerfile( "sqlserver" ) )
.acceptLicense()
.withPassword( PASSWORD )
.withReuse( true );
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java
index 16a5f97ff..14d60c264 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java
@@ -13,7 +13,7 @@
import org.testcontainers.containers.MariaDBContainer;
-import static org.hibernate.reactive.containers.DockerImage.imageName;
+import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
class MariaDatabase extends MySQLDatabase {
@@ -36,7 +36,7 @@ class MariaDatabase extends MySQLDatabase {
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
*/
- public static final MariaDBContainer> maria = new MariaDBContainer<>( imageName( "mariadb", "11.7.2" ) )
+ public static final MariaDBContainer> maria = new MariaDBContainer<>( fromDockerfile( "maria" ) )
.withUsername( DatabaseConfiguration.USERNAME )
.withPassword( DatabaseConfiguration.PASSWORD )
.withDatabaseName( DatabaseConfiguration.DB_NAME )
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java
index e0aa9ef3a..f5cd8f7c7 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java
@@ -5,7 +5,7 @@
*/
package org.hibernate.reactive.containers;
-import static org.hibernate.reactive.containers.DockerImage.imageName;
+import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -87,7 +87,7 @@ class MySQLDatabase implements TestableDatabase {
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
*/
- public static final MySQLContainer> mysql = new MySQLContainer<>( imageName( "mysql", "9.2.0") )
+ public static final MySQLContainer> mysql = new MySQLContainer<>( fromDockerfile( "mysql" ) )
.withUsername( DatabaseConfiguration.USERNAME )
.withPassword( DatabaseConfiguration.PASSWORD )
.withDatabaseName( DatabaseConfiguration.DB_NAME )
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java
index d57f9103a..bbde95e6c 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java
@@ -29,7 +29,7 @@
import org.testcontainers.containers.OracleContainer;
-import static org.hibernate.reactive.containers.DockerImage.imageName;
+import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
/**
* Connection string for Oracle thin should be something like:
@@ -88,9 +88,7 @@ class OracleDatabase implements TestableDatabase {
}
}
- public static final OracleContainer oracle = new OracleContainer(
- imageName( "gvenzl/oracle-free", "23-slim-faststart" )
- .asCompatibleSubstituteFor( "gvenzl/oracle-xe" ) )
+ public static final OracleContainer oracle = new OracleContainer( fromDockerfile( "oracle" ).asCompatibleSubstituteFor( "gvenzl/oracle-xe" ) )
.withUsername( DatabaseConfiguration.USERNAME )
.withPassword( DatabaseConfiguration.PASSWORD )
.withDatabaseName( DatabaseConfiguration.DB_NAME )
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java
index 56ef4f878..f6a974ba2 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java
@@ -5,8 +5,6 @@
*/
package org.hibernate.reactive.containers;
-import static org.hibernate.reactive.containers.DockerImage.imageName;
-
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -30,9 +28,11 @@
import org.testcontainers.containers.PostgreSQLContainer;
+import static org.hibernate.reactive.containers.DockerImage.fromDockerfile;
+
class PostgreSQLDatabase implements TestableDatabase {
- public static PostgreSQLDatabase INSTANCE = new PostgreSQLDatabase();
+ public static final PostgreSQLDatabase INSTANCE = new PostgreSQLDatabase();
private static Map, String> expectedDBTypeForClass = new HashMap<>();
@@ -87,7 +87,7 @@ class PostgreSQLDatabase implements TestableDatabase {
* TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located
* at `$HOME/.testcontainers.properties` (create the file if it does not exist).
*/
- public static final PostgreSQLContainer> postgresql = new PostgreSQLContainer<>( imageName( "postgres", "17.5" ) )
+ public static final PostgreSQLContainer> postgresql = new PostgreSQLContainer<>( fromDockerfile( "postgresql" ) )
.withUsername( DatabaseConfiguration.USERNAME )
.withPassword( DatabaseConfiguration.PASSWORD )
.withDatabaseName( DatabaseConfiguration.DB_NAME )
diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java
index 70251e20e..261fa380d 100644
--- a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java
+++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java
@@ -51,9 +51,7 @@ public abstract class BaseReactiveIT {
// These properties are in DatabaseConfiguration in core
public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" );
- public static final DockerImageName IMAGE_NAME = DockerImageName
- .parse( "docker.io/postgres:17.5" )
- .asCompatibleSubstituteFor( "postgres" );
+ public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" );
public static final String USERNAME = "hreact";
public static final String PASSWORD = "hreact";
diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java
new file mode 100644
index 000000000..b5621249d
--- /dev/null
+++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java
@@ -0,0 +1,125 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.it;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * A utility class with methods to generate a {@link DockerImageName} for Testcontainers.
+ *
+ * Testcontainers might not work if the image required is available in multiple different registries (for example when
+ * using podman instead of docker).
+ * These methods make sure to pick a registry as default.
+ *
+ */
+public final class DockerImage {
+
+ /**
+ * The absolute path of the project root that we have set in Gradle.
+ */
+ private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" );
+
+ /**
+ * The path to the directory containing all the Dockerfile files
+ */
+ private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" );
+
+ /**
+ * Extract the image name and version from the first FROM instruction in the Dockerfile.
+ * Note that everything else is ignored.
+ */
+ public static DockerImageName fromDockerfile(String databaseName) {
+ try {
+ final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() );
+ return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() );
+ }
+ catch (IOException e) {
+ throw new RuntimeException( e );
+ }
+ }
+
+ public static DockerImageName imageName(String registry, String image, String version) {
+ return DockerImageName
+ .parse( registry + "/" + image + ":" + version )
+ .asCompatibleSubstituteFor( image );
+ }
+
+ private static class ImageInformation {
+ private final String registry;
+ private final String image;
+ private final String version;
+
+ public ImageInformation(String fullImageInfo) {
+ // FullImageInfo pattern: /:
+ // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13"
+ final int registryEndPos = fullImageInfo.indexOf( '/' );
+ final int imageEndPos = fullImageInfo.lastIndexOf( ':' );
+ this.registry = fullImageInfo.substring( 0, registryEndPos );
+ this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos );
+ this.version = fullImageInfo.substring( imageEndPos + 1 );
+ }
+
+ public String getRegistry() {
+ return registry;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ return registry + "/" + image + ":" + version;
+ }
+ }
+
+ private static Path dockerFilePath(String database) {
+ // Get project root from system property set by Gradle, with fallback
+ return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" );
+ }
+
+ private static ImageInformation readFromInstruction(String database) throws IOException {
+ return readFromInstruction( dockerFilePath( database ) );
+ }
+
+ /**
+ * Read a Dockerfile and extract the first FROM instruction.
+ *
+ * @param dockerfilePath path to the Dockerfile
+ * @return the first FROM instruction found, or empty if none found
+ * @throws IOException if the file cannot be read
+ */
+ private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException {
+ if ( !Files.exists( dockerfilePath ) ) {
+ throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath );
+ }
+
+ List lines = Files.readAllLines( dockerfilePath );
+ for ( String line : lines ) {
+ // Skip comments and empty lines
+ String trimmedLine = line.trim();
+ if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) {
+ continue;
+ }
+
+ if ( trimmedLine.startsWith( "FROM " ) ) {
+ return new ImageInformation( trimmedLine.substring( "FROM ".length() ) );
+ }
+ }
+
+ throw new IOException( " Missing FROM instruction in " + dockerfilePath );
+ }
+}
diff --git a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java
index f65358ce3..cb94e0547 100644
--- a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java
+++ b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java
@@ -51,9 +51,7 @@ public abstract class BaseReactiveIT {
// These properties are in DatabaseConfiguration in core
public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" );
- public static final DockerImageName IMAGE_NAME = DockerImageName
- .parse( "docker.io/postgres:17.5" )
- .asCompatibleSubstituteFor( "postgres" );
+ public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" );
public static final String USERNAME = "hreact";
public static final String PASSWORD = "hreact";
diff --git a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java
new file mode 100644
index 000000000..7770da76c
--- /dev/null
+++ b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java
@@ -0,0 +1,125 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.it.quarkus.qe.database;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * A utility class with methods to generate a {@link DockerImageName} for Testcontainers.
+ *
+ * Testcontainers might not work if the image required is available in multiple different registries (for example when
+ * using podman instead of docker).
+ * These methods make sure to pick a registry as default.
+ *
+ */
+public final class DockerImage {
+
+ /**
+ * The absolute path of the project root that we have set in Gradle.
+ */
+ private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" );
+
+ /**
+ * The path to the directory containing all the Dockerfile files
+ */
+ private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" );
+
+ /**
+ * Extract the image name and version from the first FROM instruction in the Dockerfile.
+ * Note that everything else is ignored.
+ */
+ public static DockerImageName fromDockerfile(String databaseName) {
+ try {
+ final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() );
+ return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() );
+ }
+ catch (IOException e) {
+ throw new RuntimeException( e );
+ }
+ }
+
+ public static DockerImageName imageName(String registry, String image, String version) {
+ return DockerImageName
+ .parse( registry + "/" + image + ":" + version )
+ .asCompatibleSubstituteFor( image );
+ }
+
+ private static class ImageInformation {
+ private final String registry;
+ private final String image;
+ private final String version;
+
+ public ImageInformation(String fullImageInfo) {
+ // FullImageInfo pattern: /:
+ // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13"
+ final int registryEndPos = fullImageInfo.indexOf( '/' );
+ final int imageEndPos = fullImageInfo.lastIndexOf( ':' );
+ this.registry = fullImageInfo.substring( 0, registryEndPos );
+ this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos );
+ this.version = fullImageInfo.substring( imageEndPos + 1 );
+ }
+
+ public String getRegistry() {
+ return registry;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ return registry + "/" + image + ":" + version;
+ }
+ }
+
+ private static Path dockerFilePath(String database) {
+ // Get project root from system property set by Gradle, with fallback
+ return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" );
+ }
+
+ private static ImageInformation readFromInstruction(String database) throws IOException {
+ return readFromInstruction( dockerFilePath( database ) );
+ }
+
+ /**
+ * Read a Dockerfile and extract the first FROM instruction.
+ *
+ * @param dockerfilePath path to the Dockerfile
+ * @return the first FROM instruction found, or empty if none found
+ * @throws IOException if the file cannot be read
+ */
+ private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException {
+ if ( !Files.exists( dockerfilePath ) ) {
+ throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath );
+ }
+
+ List lines = Files.readAllLines( dockerfilePath );
+ for ( String line : lines ) {
+ // Skip comments and empty lines
+ String trimmedLine = line.trim();
+ if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) {
+ continue;
+ }
+
+ if ( trimmedLine.startsWith( "FROM " ) ) {
+ return new ImageInformation( trimmedLine.substring( "FROM ".length() ) );
+ }
+ }
+
+ throw new IOException( " Missing FROM instruction in " + dockerfilePath );
+ }
+}
diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java
new file mode 100644
index 000000000..10026d020
--- /dev/null
+++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java
@@ -0,0 +1,125 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.it.verticle;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * A utility class with methods to generate a {@link DockerImageName} for Testcontainers.
+ *
+ * Testcontainers might not work if the image required is available in multiple different registries (for example when
+ * using podman instead of docker).
+ * These methods make sure to pick a registry as default.
+ *
+ */
+public final class DockerImage {
+
+ /**
+ * The absolute path of the project root that we have set in Gradle.
+ */
+ private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" );
+
+ /**
+ * The path to the directory containing all the Dockerfile files
+ */
+ private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" );
+
+ /**
+ * Extract the image name and version from the first FROM instruction in the Dockerfile.
+ * Note that everything else is ignored.
+ */
+ public static DockerImageName fromDockerfile(String databaseName) {
+ try {
+ final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() );
+ return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() );
+ }
+ catch (IOException e) {
+ throw new RuntimeException( e );
+ }
+ }
+
+ public static DockerImageName imageName(String registry, String image, String version) {
+ return DockerImageName
+ .parse( registry + "/" + image + ":" + version )
+ .asCompatibleSubstituteFor( image );
+ }
+
+ private static class ImageInformation {
+ private final String registry;
+ private final String image;
+ private final String version;
+
+ public ImageInformation(String fullImageInfo) {
+ // FullImageInfo pattern: /:
+ // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13"
+ final int registryEndPos = fullImageInfo.indexOf( '/' );
+ final int imageEndPos = fullImageInfo.lastIndexOf( ':' );
+ this.registry = fullImageInfo.substring( 0, registryEndPos );
+ this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos );
+ this.version = fullImageInfo.substring( imageEndPos + 1 );
+ }
+
+ public String getRegistry() {
+ return registry;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ return registry + "/" + image + ":" + version;
+ }
+ }
+
+ private static Path dockerFilePath(String database) {
+ // Get project root from system property set by Gradle, with fallback
+ return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" );
+ }
+
+ private static ImageInformation readFromInstruction(String database) throws IOException {
+ return readFromInstruction( dockerFilePath( database ) );
+ }
+
+ /**
+ * Read a Dockerfile and extract the first FROM instruction.
+ *
+ * @param dockerfilePath path to the Dockerfile
+ * @return the first FROM instruction found, or empty if none found
+ * @throws IOException if the file cannot be read
+ */
+ private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException {
+ if ( !Files.exists( dockerfilePath ) ) {
+ throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath );
+ }
+
+ List lines = Files.readAllLines( dockerfilePath );
+ for ( String line : lines ) {
+ // Skip comments and empty lines
+ String trimmedLine = line.trim();
+ if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) {
+ continue;
+ }
+
+ if ( trimmedLine.startsWith( "FROM " ) ) {
+ return new ImageInformation( trimmedLine.substring( "FROM ".length() ) );
+ }
+ }
+
+ throw new IOException( " Missing FROM instruction in " + dockerfilePath );
+ }
+}
diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java
index 990fef754..f11254c6d 100644
--- a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java
+++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java
@@ -21,6 +21,7 @@
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.utility.DockerImageName;
import static java.lang.invoke.MethodHandles.lookup;
import static org.hibernate.reactive.logging.impl.LoggerFactory.make;
@@ -36,7 +37,8 @@ public class VertxServer {
// These properties are in DatabaseConfiguration in core
public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" );
- public static final String IMAGE_NAME = "postgres:17.5";
+ public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" );
+
public static final String USERNAME = "hreact";
public static final String PASSWORD = "hreact";
public static final String DB_NAME = "hreact";
diff --git a/tooling/docker/README.md b/tooling/docker/README.md
new file mode 100644
index 000000000..4de2451e0
--- /dev/null
+++ b/tooling/docker/README.md
@@ -0,0 +1,6 @@
+Our test suite will only read the first FROM instruction from each Dockerfile to extract the base image and the version
+of the container to run. It will ignore everything else.
+
+The reason we have these files is that we want to automate the upgrade of the containers using dependabot.
+
+See the class `DockerImage`.
diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile
new file mode 100644
index 000000000..a4710ad5b
--- /dev/null
+++ b/tooling/docker/cockroachdb.Dockerfile
@@ -0,0 +1,3 @@
+# CockroachDB
+# See https://hub.docker.com/r/cockroachdb/cockroach
+FROM docker.io/cockroachdb/cockroach:v24.3.13
diff --git a/tooling/docker/db2.Dockerfile b/tooling/docker/db2.Dockerfile
new file mode 100644
index 000000000..1ccb34906
--- /dev/null
+++ b/tooling/docker/db2.Dockerfile
@@ -0,0 +1,3 @@
+# IBM DB2
+# See https://hub.docker.com/r/ibmcom/db2
+FROM icr.io/db2_community/db2:12.1.0.0
diff --git a/tooling/docker/maria.Dockerfile b/tooling/docker/maria.Dockerfile
new file mode 100644
index 000000000..4ccb397c8
--- /dev/null
+++ b/tooling/docker/maria.Dockerfile
@@ -0,0 +1,3 @@
+# MariaDB
+# See https://hub.docker.com/_/mariadb
+FROM docker.io/mariadb:11.7.2
diff --git a/tooling/docker/mysql.Dockerfile b/tooling/docker/mysql.Dockerfile
new file mode 100644
index 000000000..966350a87
--- /dev/null
+++ b/tooling/docker/mysql.Dockerfile
@@ -0,0 +1,3 @@
+# MySQL
+# See https://hub.docker.com/_/mysql
+FROM docker.io/mysql:9.2.0
diff --git a/tooling/docker/oracle.Dockerfile b/tooling/docker/oracle.Dockerfile
new file mode 100644
index 000000000..7dfc6447d
--- /dev/null
+++ b/tooling/docker/oracle.Dockerfile
@@ -0,0 +1,3 @@
+# Oracle Database Free
+# See https://hub.docker.com/r/gvenzl/oracle-free
+FROM docker.io/gvenzl/oracle-free:23-slim-faststart
diff --git a/tooling/docker/postgresql.Dockerfile b/tooling/docker/postgresql.Dockerfile
new file mode 100644
index 000000000..fb36f48e8
--- /dev/null
+++ b/tooling/docker/postgresql.Dockerfile
@@ -0,0 +1,3 @@
+# PostgreSQL
+# See https://hub.docker.com/_/postgres
+FROM docker.io/postgres:17.5
diff --git a/tooling/docker/sqlserver.Dockerfile b/tooling/docker/sqlserver.Dockerfile
new file mode 100644
index 000000000..b4fb34f2f
--- /dev/null
+++ b/tooling/docker/sqlserver.Dockerfile
@@ -0,0 +1,3 @@
+# Microsoft SQL Server
+# See https://hub.docker.com/_/microsoft-mssql-server
+FROM mcr.microsoft.com/mssql/server:2022-latest
diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute
index d0ebda569..498892f67 100755
--- a/tooling/jbang/CockroachDBReactiveTest.java.qute
+++ b/tooling/jbang/CockroachDBReactiveTest.java.qute
@@ -10,7 +10,7 @@
//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final}
//DEPS org.assertj:assertj-core:3.27.3
//DEPS junit:junit:4.13.2
-//DEPS org.testcontainers:cockroachdb:1.21.0
+//DEPS org.testcontainers:cockroachdb:1.21.3
//DEPS org.slf4j:slf4j-simple:2.0.7
//// Testcontainer needs the JDBC drivers to start the container
diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute
index d9396c13f..d0ee70229 100755
--- a/tooling/jbang/Db2ReactiveTest.java.qute
+++ b/tooling/jbang/Db2ReactiveTest.java.qute
@@ -10,7 +10,7 @@
//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final}
//DEPS org.assertj:assertj-core:3.27.3
//DEPS junit:junit:4.13.2
-//DEPS org.testcontainers:db2:1.21.0
+//DEPS org.testcontainers:db2:1.21.3
//DEPS org.slf4j:slf4j-simple:2.0.7
import jakarta.persistence.Entity;
diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute
index d3f304147..b0129f086 100755
--- a/tooling/jbang/MariaDBReactiveTest.java.qute
+++ b/tooling/jbang/MariaDBReactiveTest.java.qute
@@ -10,7 +10,7 @@
//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final}
//DEPS org.assertj:assertj-core:3.27.3
//DEPS junit:junit:4.13.2
-//DEPS org.testcontainers:mariadb:1.21.0
+//DEPS org.testcontainers:mariadb:1.21.3
//DEPS org.slf4j:slf4j-simple:2.0.7
//// Testcontainer needs the JDBC drivers to start the container
diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute
index 67925f99a..34865636f 100755
--- a/tooling/jbang/MySQLReactiveTest.java.qute
+++ b/tooling/jbang/MySQLReactiveTest.java.qute
@@ -10,7 +10,7 @@
//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final}
//DEPS org.assertj:assertj-core:3.27.3
//DEPS junit:junit:4.13.2
-//DEPS org.testcontainers:mysql:1.21.0
+//DEPS org.testcontainers:mysql:1.21.3
//DEPS org.slf4j:slf4j-simple:2.0.7
//// Testcontainer needs the JDBC drivers to start the container
diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute
index 0993b9227..b5c6c8cbd 100755
--- a/tooling/jbang/PostgreSQLReactiveTest.java.qute
+++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute
@@ -10,7 +10,7 @@
//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final}
//DEPS org.assertj:assertj-core:3.27.3
//DEPS junit:junit:4.13.2
-//DEPS org.testcontainers:postgresql:1.21.0
+//DEPS org.testcontainers:postgresql:1.21.3
//DEPS org.slf4j:slf4j-simple:2.0.7
//DESCRIPTION Allow authentication to PostgreSQL using SCRAM:
//DEPS com.ongres.scram:client:2.1
diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java
index 28656a504..8db2eedc9 100755
--- a/tooling/jbang/ReactiveTest.java
+++ b/tooling/jbang/ReactiveTest.java
@@ -13,11 +13,11 @@
//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:4.0.0.Final}
//DEPS org.assertj:assertj-core:3.27.3
//DEPS junit:junit:4.13.2
-//DEPS org.testcontainers:postgresql:1.21.0
-//DEPS org.testcontainers:mysql:1.21.0
-//DEPS org.testcontainers:db2:1.21.0
-//DEPS org.testcontainers:mariadb:1.21.0
-//DEPS org.testcontainers:cockroachdb:1.21.0
+//DEPS org.testcontainers:postgresql:1.21.3
+//DEPS org.testcontainers:mysql:1.21.3
+//DEPS org.testcontainers:db2:1.21.3
+//DEPS org.testcontainers:mariadb:1.21.3
+//DEPS org.testcontainers:cockroachdb:1.21.3
//
//// Testcontainer needs the JDBC drivers to start the containers
//// Hibernate Reactive doesn't use them