diff --git a/extensions/container-image/container-image-docker/deployment/pom.xml b/extensions/container-image/container-image-docker/deployment/pom.xml
index 32e5e5f7394c5..0985cbc99c3b0 100644
--- a/extensions/container-image/container-image-docker/deployment/pom.xml
+++ b/extensions/container-image/container-image-docker/deployment/pom.xml
@@ -22,6 +22,11 @@
io.quarkus
quarkus-container-image-deployment
+
+ org.assertj
+ assertj-core
+ test
+
@@ -41,4 +46,4 @@
-
\ No newline at end of file
+
diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerFileBaseInformationProvider.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerFileBaseInformationProvider.java
new file mode 100644
index 0000000000000..262cffd8c8a95
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerFileBaseInformationProvider.java
@@ -0,0 +1,47 @@
+package io.quarkus.container.image.docker.deployment;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
+
+interface DockerFileBaseInformationProvider {
+
+ Optional determine(Path dockerFile);
+
+ static DockerFileBaseInformationProvider impl() {
+ return new DockerFileBaseInformationProvider() {
+
+ private final List delegates = List.of(new UbiMinimalBaseProvider(),
+ new RedHatOpenJDKRuntimeBaseProvider());
+
+ @Override
+ public Optional determine(Path dockerFile) {
+ for (DockerFileBaseInformationProvider delegate : delegates) {
+ Optional result = delegate.determine(dockerFile);
+ if (result.isPresent()) {
+ return result;
+ }
+ }
+ return Optional.empty();
+ }
+ };
+ }
+
+ class DockerFileBaseInformation {
+ private final int javaVersion;
+ private final String baseImage;
+
+ public DockerFileBaseInformation(String baseImage, int javaVersion) {
+ this.javaVersion = javaVersion;
+ this.baseImage = baseImage;
+ }
+
+ public int getJavaVersion() {
+ return javaVersion;
+ }
+
+ public String getBaseImage() {
+ return baseImage;
+ }
+ }
+}
diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java
index 0b1e06203ea31..0f8d083c3b24c 100644
--- a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java
+++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java
@@ -34,6 +34,7 @@
import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.deployment.pkg.builditem.AppCDSResultBuildItem;
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
+import io.quarkus.deployment.pkg.builditem.CompiledJavaVersionBuildItem;
import io.quarkus.deployment.pkg.builditem.JarBuildItem;
import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
@@ -63,6 +64,7 @@ public void dockerBuildFromJar(DockerConfig dockerConfig,
ContainerImageConfig containerImageConfig,
OutputTargetBuildItem out,
ContainerImageInfoBuildItem containerImageInfo,
+ CompiledJavaVersionBuildItem compiledJavaVersion,
Optional buildRequest,
Optional pushRequest,
@SuppressWarnings("unused") Optional appCDSResult, // ensure docker build will be performed after AppCDS creation
@@ -84,6 +86,19 @@ public void dockerBuildFromJar(DockerConfig dockerConfig,
throw new RuntimeException("Unable to build docker image. Please check your docker installation");
}
+ var dockerfilePaths = getDockerfilePaths(dockerConfig, false, packageConfig, out);
+ var dockerFileBaseInformationProvider = DockerFileBaseInformationProvider.impl();
+ var dockerFileBaseInformation = dockerFileBaseInformationProvider.determine(dockerfilePaths.getDockerfilePath());
+
+ if ((compiledJavaVersion.getJavaVersion().isJava17OrHigher() == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE)
+ && dockerFileBaseInformation.isPresent() && (dockerFileBaseInformation.get().getJavaVersion() < 17)) {
+ throw new IllegalStateException(
+ String.format(
+ "The project is built with Java 17 or higher, but the selected Dockerfile (%s) is using a lower Java version in the base image (%s). Please ensure you are using the proper base image in the Dockerfile.",
+ dockerfilePaths.getDockerfilePath().toAbsolutePath(),
+ dockerFileBaseInformation.get().getBaseImage()));
+ }
+
log.info("Building docker image for jar.");
ImageIdReader reader = new ImageIdReader();
diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProvider.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProvider.java
new file mode 100644
index 0000000000000..1a955893486e1
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProvider.java
@@ -0,0 +1,42 @@
+package io.quarkus.container.image.docker.deployment;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/**
+ * Can extract information from Dockerfile that uses {@code registry.access.redhat.com/ubi8/openjdk-$d-runtime:$d.$d} as the
+ * base image
+ */
+class RedHatOpenJDKRuntimeBaseProvider
+ implements DockerFileBaseInformationProvider {
+
+ @Override
+ public Optional determine(Path dockerFile) {
+ try (Stream lines = Files.lines(dockerFile)) {
+ Optional fromOpt = lines.filter(l -> l.startsWith("FROM")).findFirst();
+ if (fromOpt.isPresent()) {
+ String fromLine = fromOpt.get();
+ String baseImage = fromLine.substring(4).trim();
+ Pattern pattern = Pattern.compile(".*ubi8/openjdk-(\\w+)-runtime.*");
+ Matcher matcher = pattern.matcher(baseImage);
+ if (matcher.find()) {
+ String match = matcher.group(1);
+ try {
+ return Optional.of(new DockerFileBaseInformationProvider.DockerFileBaseInformation(baseImage,
+ Integer.parseInt(match)));
+ } catch (NumberFormatException ignored) {
+
+ }
+ }
+ }
+ } catch (IOException ignored) {
+
+ }
+ return Optional.empty();
+ }
+}
diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProvider.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProvider.java
new file mode 100644
index 0000000000000..1ad6adc24f6a7
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProvider.java
@@ -0,0 +1,59 @@
+package io.quarkus.container.image.docker.deployment;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/**
+ * Can extract information from Dockerfile that uses {@code registry.access.redhat.com/ubi8/ubi-minimal:$d.$d} as the
+ * base image
+ */
+class UbiMinimalBaseProvider
+ implements DockerFileBaseInformationProvider {
+
+ public static final String UBI_MINIMAL_PREFIX = "registry.access.redhat.com/ubi8/ubi-minimal";
+
+ @Override
+ public Optional determine(Path dockerFile) {
+ AtomicInteger state = new AtomicInteger(0); //0: 'FROM' not yet encountered, 1: matching 'FROM' found, 2: ARG JAVA_PACKAGE found, 3: non matching 'FROM' found, 4: exception occurred
+ AtomicReference baseImage = new AtomicReference<>(null);
+ AtomicInteger javaVersion = new AtomicInteger(0);
+ try (Stream lines = Files.lines(dockerFile)) {
+ lines.takeWhile(s -> state.get() < 2).forEach(s -> {
+ if (s.startsWith("FROM")) {
+ String image = s.substring(4).trim();
+ if (image.startsWith(UBI_MINIMAL_PREFIX)) {
+ baseImage.set(image);
+ state.set(1);
+ } else {
+ state.set(3);
+ }
+ } else if (s.startsWith("ARG JAVA_PACKAGE")) {
+ Pattern pattern = Pattern.compile("ARG JAVA_PACKAGE=java-(\\w+)-openjdk-headless");
+ Matcher matcher = pattern.matcher(s);
+ if (matcher.find()) {
+ String match = matcher.group(1);
+ try {
+ javaVersion.set(Integer.parseInt(match));
+ state.set(2);
+ } catch (NumberFormatException ignored) {
+ state.set(4);
+ }
+ }
+ }
+ });
+ } catch (IOException ignored) {
+ state.set(4);
+ }
+ if (state.get() == 2) {
+ return Optional.of(new DockerFileBaseInformation(baseImage.get(), javaVersion.get()));
+ }
+ return Optional.empty();
+ }
+}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
new file mode 100644
index 0000000000000..c8f058b7a0412
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
@@ -0,0 +1,41 @@
+package io.quarkus.container.image.docker.deployment;
+
+import static io.quarkus.container.image.docker.deployment.TestUtil.getPath;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+
+class RedHatOpenJDKRuntimeBaseProviderTest {
+
+ private final DockerFileBaseInformationProvider sut = new RedHatOpenJDKRuntimeBaseProvider();
+
+ @Test
+ void testImageWithJava11() {
+ Path path = getPath("openjdk-11-runtime");
+ var result = sut.determine(path);
+ assertThat(result).hasValueSatisfying(v -> {
+ assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-11-runtime:1.10");
+ assertThat(v.getJavaVersion()).isEqualTo(11);
+ });
+ }
+
+ @Test
+ void testImageWithJava17() {
+ Path path = getPath("openjdk-17-runtime");
+ var result = sut.determine(path);
+ assertThat(result).hasValueSatisfying(v -> {
+ assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime");
+ assertThat(v.getJavaVersion()).isEqualTo(17);
+ });
+ }
+
+ @Test
+ void testUnhandled() {
+ Path path = getPath("ubi-java11");
+ var result = sut.determine(path);
+ assertThat(result).isEmpty();
+ }
+
+}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/TestUtil.java b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/TestUtil.java
new file mode 100644
index 0000000000000..ad45ce736c77a
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/TestUtil.java
@@ -0,0 +1,19 @@
+package io.quarkus.container.image.docker.deployment;
+
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+final class TestUtil {
+
+ private TestUtil() {
+ }
+
+ static Path getPath(String filename) {
+ try {
+ return Paths.get(Thread.currentThread().getContextClassLoader().getResource(filename).toURI());
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProviderTest.java b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProviderTest.java
new file mode 100644
index 0000000000000..bd263fe871e24
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProviderTest.java
@@ -0,0 +1,41 @@
+package io.quarkus.container.image.docker.deployment;
+
+import static io.quarkus.container.image.docker.deployment.TestUtil.getPath;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+
+class UbiMinimalBaseProviderTest {
+
+ private final DockerFileBaseInformationProvider sut = new UbiMinimalBaseProvider();
+
+ @Test
+ void testImageWithJava11() {
+ Path path = getPath("ubi-java11");
+ var result = sut.determine(path);
+ assertThat(result).hasValueSatisfying(v -> {
+ assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal:8.3");
+ assertThat(v.getJavaVersion()).isEqualTo(11);
+ });
+ }
+
+ @Test
+ void testImageWithJava17() {
+ Path path = getPath("ubi-java17");
+ var result = sut.determine(path);
+ assertThat(result).hasValueSatisfying(v -> {
+ assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal");
+ assertThat(v.getJavaVersion()).isEqualTo(17);
+ });
+ }
+
+ @Test
+ void testUnhandled() {
+ Path path = getPath("openjdk-11-runtime");
+ var result = sut.determine(path);
+ assertThat(result).isEmpty();
+ }
+
+}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime
new file mode 100644
index 0000000000000..5e97afd82d60e
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime
@@ -0,0 +1,17 @@
+FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.10
+
+ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
+
+# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
+ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+
+ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
new file mode 100644
index 0000000000000..7593800a82311
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
@@ -0,0 +1,18 @@
+# Use Java 17 base image
+FROM registry.access.redhat.com/ubi8/openjdk-17-runtime
+
+ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
+
+# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
+ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+
+ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java11 b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java11
new file mode 100644
index 0000000000000..963a2bcf88249
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java11
@@ -0,0 +1,31 @@
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
+
+ARG JAVA_PACKAGE=java-11-openjdk-headless
+ARG RUN_JAVA_VERSION=1.3.8
+ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
+# Install java and the run-java script
+# Also set up permissions for user `1001`
+RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
+ && microdnf update \
+ && microdnf clean all \
+ && mkdir /deployments \
+ && chown 1001 /deployments \
+ && chmod "g+rwX" /deployments \
+ && chown 1001:root /deployments \
+ && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
+ && chown 1001 /deployments/run-java.sh \
+ && chmod 540 /deployments/run-java.sh \
+ && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security
+
+# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
+ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=1001 target/quarkus-app/*.jar /deployments/
+COPY --chown=1001 target/quarkus-app/app/ /deployments/app/
+COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 1001
+
+ENTRYPOINT [ "/deployments/run-java.sh" ]
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17 b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17
new file mode 100644
index 0000000000000..5ae6e1e2f3ac4
--- /dev/null
+++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17
@@ -0,0 +1,31 @@
+FROM registry.access.redhat.com/ubi8/ubi-minimal
+
+ARG JAVA_PACKAGE=java-17-openjdk-headless
+ARG RUN_JAVA_VERSION=1.3.8
+ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
+# Install java and the run-java script
+# Also set up permissions for user `1001`
+RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
+ && microdnf update \
+ && microdnf clean all \
+ && mkdir /deployments \
+ && chown 1001 /deployments \
+ && chmod "g+rwX" /deployments \
+ && chown 1001:root /deployments \
+ && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
+ && chown 1001 /deployments/run-java.sh \
+ && chmod 540 /deployments/run-java.sh \
+ && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security
+
+# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
+ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=1001 target/quarkus-app/*.jar /deployments/
+COPY --chown=1001 target/quarkus-app/app/ /deployments/app/
+COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 1001
+
+ENTRYPOINT [ "/deployments/run-java.sh" ]