diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 92fb6fad2129b..828e4396c2459 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -2654,6 +2654,16 @@
quarkus-container-image-docker-deployment
${project.version}
+
+ io.quarkus
+ quarkus-container-image-podman
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-container-image-podman-deployment
+ ${project.version}
+
io.quarkus
quarkus-container-image-jib
@@ -2679,6 +2689,16 @@
quarkus-container-image-deployment
${project.version}
+
+ io.quarkus
+ quarkus-container-image-docker-common
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-container-image-docker-common-deployment
+ ${project.version}
+
io.quarkus
quarkus-container-image
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java
index 1a0ca32b00b75..0260ddb493d89 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java
@@ -101,6 +101,7 @@ public interface Capability {
String METRICS = QUARKUS_PREFIX + ".metrics";
String CONTAINER_IMAGE_JIB = QUARKUS_PREFIX + ".container.image.jib";
String CONTAINER_IMAGE_DOCKER = QUARKUS_PREFIX + ".container.image.docker";
+ String CONTAINER_IMAGE_PODMAN = QUARKUS_PREFIX + ".container.image.podman";
String CONTAINER_IMAGE_OPENSHIFT = QUARKUS_PREFIX + ".container.image.openshift";
String CONTAINER_IMAGE_BUILDPACK = QUARKUS_PREFIX + ".container.image.buildpack";
String HIBERNATE_ORM = QUARKUS_PREFIX + ".hibernate.orm";
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/IsContainerRuntimeWorking.java b/core/deployment/src/main/java/io/quarkus/deployment/IsContainerRuntimeWorking.java
new file mode 100644
index 0000000000000..4194d9d1a5628
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/IsContainerRuntimeWorking.java
@@ -0,0 +1,168 @@
+package io.quarkus.deployment;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.deployment.console.StartupLogCompressor;
+
+public abstract class IsContainerRuntimeWorking implements BooleanSupplier {
+ private static final Logger LOGGER = Logger.getLogger(IsContainerRuntimeWorking.class);
+ private static final int DOCKER_HOST_CHECK_TIMEOUT = 1000;
+
+ private final List strategies;
+
+ protected IsContainerRuntimeWorking(List strategies) {
+ this.strategies = strategies;
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ for (Strategy strategy : strategies) {
+ LOGGER.debugf("Checking container runtime Environment using strategy %s", strategy.getClass().getName());
+ Result result = strategy.get();
+
+ if (result == Result.AVAILABLE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected interface Strategy extends Supplier {
+
+ }
+
+ /**
+ * Delegates the check to testcontainers (if the latter is on the classpath)
+ */
+ protected static class TestContainersStrategy implements Strategy {
+ private final boolean silent;
+
+ protected TestContainersStrategy(boolean silent) {
+ this.silent = silent;
+ }
+
+ @Override
+ public Result get() {
+ // Testcontainers uses the Unreliables library to test if docker is started
+ // this runs in threads that start with 'ducttape'
+ StartupLogCompressor compressor = new StartupLogCompressor("Checking Docker Environment", Optional.empty(), null,
+ (s) -> s.getName().startsWith("ducttape"));
+ try {
+ Class> dockerClientFactoryClass = Thread.currentThread().getContextClassLoader()
+ .loadClass("org.testcontainers.DockerClientFactory");
+ Object dockerClientFactoryInstance = dockerClientFactoryClass.getMethod("instance").invoke(null);
+
+ Class> configurationClass = Thread.currentThread().getContextClassLoader()
+ .loadClass("org.testcontainers.utility.TestcontainersConfiguration");
+ Object configurationInstance = configurationClass.getMethod("getInstance").invoke(null);
+ String oldReusePropertyValue = (String) configurationClass
+ .getMethod("getEnvVarOrUserProperty", String.class, String.class)
+ .invoke(configurationInstance, "testcontainers.reuse.enable", "false"); // use the default provided in TestcontainersConfiguration#environmentSupportsReuse
+ Method updateUserConfigMethod = configurationClass.getMethod("updateUserConfig", String.class, String.class);
+ // this will ensure that testcontainers does not start ryuk - see https://github.com/quarkusio/quarkus/issues/25852 for why this is important
+ updateUserConfigMethod.invoke(configurationInstance, "testcontainers.reuse.enable", "true");
+
+ // ensure that Testcontainers doesn't take previous failures into account
+ Class> dockerClientProviderStrategyClass = Thread.currentThread().getContextClassLoader()
+ .loadClass("org.testcontainers.dockerclient.DockerClientProviderStrategy");
+ Field failFastAlwaysField = dockerClientProviderStrategyClass.getDeclaredField("FAIL_FAST_ALWAYS");
+ failFastAlwaysField.setAccessible(true);
+ AtomicBoolean failFastAlways = (AtomicBoolean) failFastAlwaysField.get(null);
+ failFastAlways.set(false);
+
+ boolean isAvailable = (boolean) dockerClientFactoryClass.getMethod("isDockerAvailable")
+ .invoke(dockerClientFactoryInstance);
+ if (!isAvailable) {
+ compressor.closeAndDumpCaptured();
+ }
+
+ // restore the previous value
+ updateUserConfigMethod.invoke(configurationInstance, "testcontainers.reuse.enable", oldReusePropertyValue);
+ return isAvailable ? Result.AVAILABLE : Result.UNAVAILABLE;
+ } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException
+ | NoSuchFieldException e) {
+ if (!silent) {
+ compressor.closeAndDumpCaptured();
+ LOGGER.debug("Unable to use Testcontainers to determine if Docker is working", e);
+ }
+ return Result.UNKNOWN;
+ } finally {
+ compressor.close();
+ }
+ }
+ }
+
+ /**
+ * Detection using a remote host socket
+ * We don't want to pull in the docker API here, so we just see if the DOCKER_HOST is set
+ * and if we can connect to it.
+ * We can't actually verify it is docker listening on the other end.
+ * Furthermore, this does not support Unix Sockets
+ */
+ protected static class DockerHostStrategy implements Strategy {
+ private static final String UNIX_SCHEME = "unix";
+
+ @Override
+ public Result get() {
+ String dockerHost = System.getenv("DOCKER_HOST");
+
+ if (dockerHost == null) {
+ return Result.UNKNOWN;
+ }
+
+ try {
+ URI dockerHostUri = new URI(dockerHost);
+
+ if (UNIX_SCHEME.equals(dockerHostUri.getScheme())) {
+ // Java 11 does not support connecting to Unix sockets so for now let's use a naive approach
+ Path dockerSocketPath = Path.of(dockerHostUri.getPath());
+
+ if (Files.isWritable(dockerSocketPath)) {
+ return Result.AVAILABLE;
+ } else {
+ LOGGER.warnf(
+ "Unix socket defined in DOCKER_HOST %s is not writable, make sure Docker is running on the specified host",
+ dockerHost);
+ }
+ } else {
+ try (Socket s = new Socket()) {
+ s.connect(new InetSocketAddress(dockerHostUri.getHost(), dockerHostUri.getPort()),
+ DOCKER_HOST_CHECK_TIMEOUT);
+ return Result.AVAILABLE;
+ } catch (IOException e) {
+ LOGGER.warnf(
+ "Unable to connect to DOCKER_HOST URI %s, make sure Docker is running on the specified host",
+ dockerHost);
+ }
+ }
+ } catch (URISyntaxException | IllegalArgumentException e) {
+ LOGGER.warnf("Unable to parse DOCKER_HOST URI %s, it will be ignored for working Docker detection",
+ dockerHost);
+ }
+
+ return Result.UNKNOWN;
+ }
+ }
+
+ protected enum Result {
+ AVAILABLE,
+ UNAVAILABLE,
+ UNKNOWN
+ }
+}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java b/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java
index e739ca5a7c51f..1efd20d2da382 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java
@@ -3,175 +3,20 @@
import static io.quarkus.deployment.util.ContainerRuntimeUtil.ContainerRuntime.UNAVAILABLE;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.BooleanSupplier;
-import java.util.function.Supplier;
-import org.jboss.logging.Logger;
-
-import io.quarkus.deployment.console.StartupLogCompressor;
import io.quarkus.deployment.util.ContainerRuntimeUtil;
-public class IsDockerWorking implements BooleanSupplier {
-
- private static final Logger LOGGER = Logger.getLogger(IsDockerWorking.class.getName());
- public static final int DOCKER_HOST_CHECK_TIMEOUT = 1000;
-
- private final List strategies;
-
+public class IsDockerWorking extends IsContainerRuntimeWorking {
public IsDockerWorking() {
this(false);
}
public IsDockerWorking(boolean silent) {
- this.strategies = List.of(new TestContainersStrategy(silent), new DockerHostStrategy(), new DockerBinaryStrategy());
- }
-
- @Override
- public boolean getAsBoolean() {
- for (Strategy strategy : strategies) {
- LOGGER.debugf("Checking Docker Environment using strategy %s", strategy.getClass().getName());
- Result result = strategy.get();
- if (result == Result.AVAILABLE) {
- return true;
- }
- }
- return false;
- }
-
- private interface Strategy extends Supplier {
-
- }
-
- /**
- * Delegates the check to testcontainers (if the latter is on the classpath)
- */
- private static class TestContainersStrategy implements Strategy {
-
- private final boolean silent;
-
- private TestContainersStrategy(boolean silent) {
- this.silent = silent;
- }
-
- @Override
- public Result get() {
- // Testcontainers uses the Unreliables library to test if docker is started
- // this runs in threads that start with 'ducttape'
- StartupLogCompressor compressor = new StartupLogCompressor("Checking Docker Environment", Optional.empty(), null,
- (s) -> s.getName().startsWith("ducttape"));
- try {
- Class> dockerClientFactoryClass = Thread.currentThread().getContextClassLoader()
- .loadClass("org.testcontainers.DockerClientFactory");
- Object dockerClientFactoryInstance = dockerClientFactoryClass.getMethod("instance").invoke(null);
-
- Class> configurationClass = Thread.currentThread().getContextClassLoader()
- .loadClass("org.testcontainers.utility.TestcontainersConfiguration");
- Object configurationInstance = configurationClass.getMethod("getInstance").invoke(null);
- String oldReusePropertyValue = (String) configurationClass
- .getMethod("getEnvVarOrUserProperty", String.class, String.class)
- .invoke(configurationInstance, "testcontainers.reuse.enable", "false"); // use the default provided in TestcontainersConfiguration#environmentSupportsReuse
- Method updateUserConfigMethod = configurationClass.getMethod("updateUserConfig", String.class, String.class);
- // this will ensure that testcontainers does not start ryuk - see https://github.com/quarkusio/quarkus/issues/25852 for why this is important
- updateUserConfigMethod.invoke(configurationInstance, "testcontainers.reuse.enable", "true");
-
- // ensure that Testcontainers doesn't take previous failures into account
- Class> dockerClientProviderStrategyClass = Thread.currentThread().getContextClassLoader()
- .loadClass("org.testcontainers.dockerclient.DockerClientProviderStrategy");
- Field failFastAlwaysField = dockerClientProviderStrategyClass.getDeclaredField("FAIL_FAST_ALWAYS");
- failFastAlwaysField.setAccessible(true);
- AtomicBoolean failFastAlways = (AtomicBoolean) failFastAlwaysField.get(null);
- failFastAlways.set(false);
-
- boolean isAvailable = (boolean) dockerClientFactoryClass.getMethod("isDockerAvailable")
- .invoke(dockerClientFactoryInstance);
- if (!isAvailable) {
- compressor.closeAndDumpCaptured();
- }
-
- // restore the previous value
- updateUserConfigMethod.invoke(configurationInstance, "testcontainers.reuse.enable", oldReusePropertyValue);
- return isAvailable ? Result.AVAILABLE : Result.UNAVAILABLE;
- } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException
- | NoSuchFieldException e) {
- if (!silent) {
- compressor.closeAndDumpCaptured();
- LOGGER.debug("Unable to use Testcontainers to determine if Docker is working", e);
- }
- return Result.UNKNOWN;
- } finally {
- compressor.close();
- }
- }
- }
-
- /**
- * Detection using a remote host socket
- * We don't want to pull in the docker API here, so we just see if the DOCKER_HOST is set
- * and if we can connect to it.
- * We can't actually verify it is docker listening on the other end.
- * Furthermore, this does not support Unix Sockets
- */
- private static class DockerHostStrategy implements Strategy {
-
- private static final String UNIX_SCHEME = "unix";
-
- @Override
- public Result get() {
- String dockerHost = System.getenv("DOCKER_HOST");
-
- if (dockerHost == null) {
- return Result.UNKNOWN;
- }
-
- try {
- URI dockerHostUri = new URI(dockerHost);
-
- if (UNIX_SCHEME.equals(dockerHostUri.getScheme())) {
- // Java 11 does not support connecting to Unix sockets so for now let's use a naive approach
- Path dockerSocketPath = Path.of(dockerHostUri.getPath());
-
- if (Files.isWritable(dockerSocketPath)) {
- return Result.AVAILABLE;
- } else {
- LOGGER.warnf(
- "Unix socket defined in DOCKER_HOST %s is not writable, make sure Docker is running on the specified host",
- dockerHost);
- }
- } else {
- try (Socket s = new Socket()) {
- s.connect(new InetSocketAddress(dockerHostUri.getHost(), dockerHostUri.getPort()),
- DOCKER_HOST_CHECK_TIMEOUT);
- return Result.AVAILABLE;
- } catch (IOException e) {
- LOGGER.warnf(
- "Unable to connect to DOCKER_HOST URI %s, make sure Docker is running on the specified host",
- dockerHost);
- }
- }
- } catch (URISyntaxException | IllegalArgumentException e) {
- LOGGER.warnf("Unable to parse DOCKER_HOST URI %s, it will be ignored for working Docker detection",
- dockerHost);
- }
-
- return Result.UNKNOWN;
- }
+ super(List.of(new TestContainersStrategy(silent), new DockerHostStrategy(), new DockerBinaryStrategy()));
}
private static class DockerBinaryStrategy implements Strategy {
-
@Override
public Result get() {
if (ContainerRuntimeUtil.detectContainerRuntime(false) != UNAVAILABLE) {
@@ -182,10 +27,4 @@ public Result get() {
}
}
-
- private enum Result {
- AVAILABLE,
- UNAVAILABLE,
- UNKNOWN
- }
}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/IsPodmanWorking.java b/core/deployment/src/main/java/io/quarkus/deployment/IsPodmanWorking.java
new file mode 100644
index 0000000000000..2a6fce41c656d
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/IsPodmanWorking.java
@@ -0,0 +1,27 @@
+package io.quarkus.deployment;
+
+import static io.quarkus.deployment.util.ContainerRuntimeUtil.ContainerRuntime.UNAVAILABLE;
+
+import java.util.List;
+
+import io.quarkus.deployment.util.ContainerRuntimeUtil;
+
+public class IsPodmanWorking extends IsContainerRuntimeWorking {
+ public IsPodmanWorking() {
+ this(false);
+ }
+
+ public IsPodmanWorking(boolean silent) {
+ super(List.of(
+ new TestContainersStrategy(silent),
+ new DockerHostStrategy(),
+ new PodmanBinaryStrategy()));
+ }
+
+ private static class PodmanBinaryStrategy implements Strategy {
+ @Override
+ public Result get() {
+ return (ContainerRuntimeUtil.detectContainerRuntime(false) != UNAVAILABLE) ? Result.AVAILABLE : Result.UNKNOWN;
+ }
+ }
+}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/PodmanStatusProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/PodmanStatusProcessor.java
new file mode 100644
index 0000000000000..1106a75e83835
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/PodmanStatusProcessor.java
@@ -0,0 +1,12 @@
+package io.quarkus.deployment;
+
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.LaunchModeBuildItem;
+import io.quarkus.deployment.builditem.PodmanStatusBuildItem;
+
+public class PodmanStatusProcessor {
+ @BuildStep
+ PodmanStatusBuildItem isPodmanWorking(LaunchModeBuildItem launchMode) {
+ return new PodmanStatusBuildItem(new IsPodmanWorking(launchMode.getLaunchMode().isDevOrTest()));
+ }
+}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ContainerRuntimeStatusBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ContainerRuntimeStatusBuildItem.java
new file mode 100644
index 0000000000000..54226bad5f5eb
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ContainerRuntimeStatusBuildItem.java
@@ -0,0 +1,23 @@
+package io.quarkus.deployment.builditem;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.deployment.IsContainerRuntimeWorking;
+
+public abstract class ContainerRuntimeStatusBuildItem extends SimpleBuildItem {
+ private final IsContainerRuntimeWorking isContainerRuntimeWorking;
+ private Boolean cachedStatus;
+
+ protected ContainerRuntimeStatusBuildItem(IsContainerRuntimeWorking isContainerRuntimeWorking) {
+ this.isContainerRuntimeWorking = isContainerRuntimeWorking;
+ }
+
+ public boolean isContainerRuntimeAvailable() {
+ synchronized (this) {
+ if (cachedStatus == null) {
+ cachedStatus = isContainerRuntimeWorking.getAsBoolean();
+ }
+ }
+
+ return cachedStatus;
+ }
+}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/DockerStatusBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/DockerStatusBuildItem.java
index 9309683477926..aff9a5f305a90 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/DockerStatusBuildItem.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/DockerStatusBuildItem.java
@@ -1,21 +1,17 @@
package io.quarkus.deployment.builditem;
-import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.deployment.IsDockerWorking;
-public final class DockerStatusBuildItem extends SimpleBuildItem {
-
- private final IsDockerWorking isDockerWorking;
- private Boolean cachedStatus;
-
+public final class DockerStatusBuildItem extends ContainerRuntimeStatusBuildItem {
public DockerStatusBuildItem(IsDockerWorking isDockerWorking) {
- this.isDockerWorking = isDockerWorking;
+ super(isDockerWorking);
}
- public synchronized boolean isDockerAvailable() {
- if (cachedStatus == null) {
- cachedStatus = isDockerWorking.getAsBoolean();
- }
- return cachedStatus;
+ /**
+ * @deprecated Use {@link #isContainerRuntimeAvailable()} instead
+ */
+ @Deprecated(forRemoval = true)
+ public boolean isDockerAvailable() {
+ return isContainerRuntimeAvailable();
}
}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/PodmanStatusBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/PodmanStatusBuildItem.java
new file mode 100644
index 0000000000000..be1546fc3b881
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/PodmanStatusBuildItem.java
@@ -0,0 +1,9 @@
+package io.quarkus.deployment.builditem;
+
+import io.quarkus.deployment.IsPodmanWorking;
+
+public final class PodmanStatusBuildItem extends ContainerRuntimeStatusBuildItem {
+ public PodmanStatusBuildItem(IsPodmanWorking isPodmanWorking) {
+ super(isPodmanWorking);
+ }
+}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/ContainerRuntimeUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/ContainerRuntimeUtil.java
index ff6452d4199f2..10d33810a02a0 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/util/ContainerRuntimeUtil.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/util/ContainerRuntimeUtil.java
@@ -5,6 +5,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@@ -44,13 +46,21 @@ public static ContainerRuntime detectContainerRuntime() {
return detectContainerRuntime(true);
}
+ public static ContainerRuntime detectContainerRuntime(List orderToCheckRuntimes) {
+ return detectContainerRuntime(true, orderToCheckRuntimes);
+ }
+
public static ContainerRuntime detectContainerRuntime(boolean required) {
+ return detectContainerRuntime(required, List.of(ContainerRuntime.DOCKER, ContainerRuntime.PODMAN));
+ }
+
+ public static ContainerRuntime detectContainerRuntime(boolean required, List orderToCheckRuntimes) {
ContainerRuntime containerRuntime = loadContainerRuntimeFromSystemProperty();
if (containerRuntime != null) {
return containerRuntime;
}
- final ContainerRuntime containerRuntimeEnvironment = getContainerRuntimeEnvironment();
+ final ContainerRuntime containerRuntimeEnvironment = getContainerRuntimeEnvironment(orderToCheckRuntimes);
if (containerRuntimeEnvironment == ContainerRuntime.UNAVAILABLE) {
storeContainerRuntimeInSystemProperty(ContainerRuntime.UNAVAILABLE);
@@ -70,47 +80,58 @@ public static ContainerRuntime detectContainerRuntime(boolean required) {
return containerRuntime;
}
- private static ContainerRuntime getContainerRuntimeEnvironment() {
+ private static ContainerRuntime getContainerRuntimeEnvironment(List orderToCheckRuntimes) {
// Docker version 19.03.14, build 5eb3275d40
- String dockerVersionOutput;
- boolean dockerAvailable;
+
// Check if Podman is installed
// podman version 2.1.1
- String podmanVersionOutput;
- boolean podmanAvailable;
+ var runtimesToCheck = new ArrayList<>(orderToCheckRuntimes.stream().distinct().toList());
+ runtimesToCheck.retainAll(List.of(ContainerRuntime.DOCKER, ContainerRuntime.PODMAN));
if (CONTAINER_EXECUTABLE != null) {
- if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("docker")) {
- dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER);
- dockerAvailable = dockerVersionOutput.contains("Docker version");
- if (dockerAvailable) {
- return ContainerRuntime.DOCKER;
- }
- }
- if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("podman")) {
- podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN);
- podmanAvailable = PODMAN_PATTERN.matcher(podmanVersionOutput).matches();
- if (podmanAvailable) {
- return ContainerRuntime.PODMAN;
- }
+ var runtime = runtimesToCheck.stream()
+ .filter(containerRuntime -> CONTAINER_EXECUTABLE.trim()
+ .equalsIgnoreCase(containerRuntime.getExecutableName()))
+ .findFirst()
+ .filter(r -> {
+ var versionOutput = getVersionOutputFor(r);
+
+ return switch (r) {
+ case DOCKER, DOCKER_ROOTLESS -> versionOutput.contains("Docker version");
+ case PODMAN, PODMAN_ROOTLESS -> PODMAN_PATTERN.matcher(versionOutput).matches();
+ default -> false;
+ };
+ });
+
+ if (runtime.isPresent()) {
+ return runtime.get();
+ } else {
+ log.warn("quarkus.native.container-runtime config property must be set to either podman or docker " +
+ "and the executable must be available. Ignoring it.");
}
- log.warn("quarkus.native.container-runtime config property must be set to either podman or docker " +
- "and the executable must be available. Ignoring it.");
}
- dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER);
- dockerAvailable = dockerVersionOutput.contains("Docker version");
- if (dockerAvailable) {
- // Check if "docker" is an alias to "podman"
- if (PODMAN_PATTERN.matcher(dockerVersionOutput).matches()) {
- return ContainerRuntime.PODMAN;
+ for (var runtime : runtimesToCheck) {
+ var versionOutput = getVersionOutputFor(runtime);
+
+ switch (runtime) {
+ case DOCKER:
+ case DOCKER_ROOTLESS:
+ var dockerAvailable = versionOutput.contains("Docker version");
+ if (dockerAvailable) {
+ // Check if "docker" is an alias to podman
+ return PODMAN_PATTERN.matcher(versionOutput).matches() ? ContainerRuntime.PODMAN
+ : ContainerRuntime.DOCKER;
+ }
+ break;
+
+ case PODMAN:
+ case PODMAN_ROOTLESS:
+ if (PODMAN_PATTERN.matcher(versionOutput).matches()) {
+ return ContainerRuntime.PODMAN;
+ }
+ break;
}
- return ContainerRuntime.DOCKER;
- }
- podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN);
- podmanAvailable = PODMAN_PATTERN.matcher(podmanVersionOutput).matches();
- if (podmanAvailable) {
- return ContainerRuntime.PODMAN;
}
return ContainerRuntime.UNAVAILABLE;
diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml
index a9ec968e653b5..c7e2908451a7f 100644
--- a/devtools/bom-descriptor-json/pom.xml
+++ b/devtools/bom-descriptor-json/pom.xml
@@ -382,6 +382,19 @@
+
+ io.quarkus
+ quarkus-container-image-docker-common
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-container-image-jib
@@ -408,6 +421,19 @@
+
+ io.quarkus
+ quarkus-container-image-podman
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-core
diff --git a/docs/pom.xml b/docs/pom.xml
index bc89de3357558..bec1fb8c038d4 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -398,6 +398,19 @@
+
+ io.quarkus
+ quarkus-container-image-docker-common-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-container-image-jib-deployment
@@ -424,6 +437,19 @@
+
+ io.quarkus
+ quarkus-container-image-podman-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-core-deployment
diff --git a/docs/src/main/asciidoc/container-image.adoc b/docs/src/main/asciidoc/container-image.adoc
index 2ec5a802577ca..b08fcf1ab843f 100644
--- a/docs/src/main/asciidoc/container-image.adoc
+++ b/docs/src/main/asciidoc/container-image.adoc
@@ -6,14 +6,15 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
= Container Images
include::_attributes.adoc[]
:categories: cloud
-:summary: Learn how to build and push container images with Jib, OpenShift or Docker as part of the Quarkus build.
+:summary: Learn how to build and push container images with Jib, OpenShift, Docker, or Podman as part of the Quarkus build.
:topics: devops,cloud
-:extensions: io.quarkus:quarkus-container-image-openshift,io.quarkus:quarkus-container-image-jib,io.quarkus:quarkus-container-image-docker,io.quarkus:quarkus-container-image-buildpack
+:extensions: io.quarkus:quarkus-container-image-openshift,io.quarkus:quarkus-container-image-jib,io.quarkus:quarkus-container-image-docker,io.quarkus:quarkus-container-image-podman,io.quarkus:quarkus-container-image-buildpack
Quarkus provides extensions for building (and pushing) container images. Currently, it supports:
- <<#jib,Jib>>
- <<#docker,Docker>>
+- <<#podman,Podman>>
- <<#openshift,OpenShift>>
- <<#buildpack,Buildpack>>
@@ -120,6 +121,16 @@ The `quarkus-container-image-docker` extension is capable of https://docs.docker
NOTE: `docker buildx build` ONLY supports https://docs.docker.com/engine/reference/commandline/buildx_build/#load[loading the result of a build] to `docker images` when building for a single platform. Therefore, if you specify more than one argument in the `quarkus.docker.buildx.platform` property, the resulting images will not be loaded into `docker images`. If `quarkus.docker.buildx.platform` is omitted or if only a single platform is specified, it will then be loaded into `docker images`.
+[[podman]]
+=== Podman
+
+The extension `quarkus-container-image-podman` uses https://podman.io/[Podman] and the generated `Dockerfiles` under `src/main/docker` in order to perform container builds.
+
+To use this feature, add the following extension to your project.
+
+:add-extension-extensions: container-image-podman
+include::{includes}/devtools/extension-add.adoc[]
+
[[openshift]]
=== OpenShift
@@ -204,7 +215,7 @@ NOTE: If no registry is set (using `quarkus.container-image.registry`) then `doc
It does not make sense to use multiple extension as part of the same build. When multiple container image extensions are present, an error will be raised to inform the user. The user can either remove the unneeded extensions or select one using `application.properties`.
-For example, if both `container-image-docker` and `container-image-openshift` are present and the user needs to use `container-image-docker`:
+For example, if both `container-image-docker` and `container-image-podman` are present and the user needs to use `container-image-docker`:
[source,properties]
----
@@ -255,6 +266,13 @@ In addition to the generic container image options, the `container-image-docker`
include::{generated-dir}/config/quarkus-container-image-docker.adoc[opts=optional, leveloffset=+1]
+[[PodmanOptions]]
+=== Podman Options
+
+In addition to the generic container image options, the `container-image-podman` also provides the following options:
+
+include::{generated-dir}/config/quarkus-container-image-podman.adoc[opts=optional, leveloffset=+1]
+
=== OpenShift Options
In addition to the generic container image options, the `container-image-openshift` also provides the following options:
diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc
index b9e0d99c7d2b7..25a273ea77dca 100644
--- a/docs/src/main/asciidoc/getting-started-testing.adoc
+++ b/docs/src/main/asciidoc/getting-started-testing.adoc
@@ -1261,8 +1261,8 @@ This is covered in the xref:building-native-image.adoc[Native Executable Guide].
`@QuarkusIntegrationTest` should be used to launch and test the artifact produced by the Quarkus build, and supports testing a jar (of whichever type), a native image or container image.
Put simply, this means that if the result of a Quarkus build (`mvn package` or `gradle build`) is a jar, that jar will be launched as `java -jar ...` and tests run against it.
If instead a native image was built, then the application is launched as `./application ...` and again the tests run against the running application.
-Finally, if a container image was created during the build (by including the `quarkus-container-image-jib` or `quarkus-container-image-docker` extensions and having the
-`quarkus.container-image.build=true` property configured), then a container is created and run (this requires the `docker` executable being present).
+Finally, if a container image was created during the build (by including the `quarkus-container-image-jib`, `quarkus-container-image-docker`, or `container-image-podman` extensions and having the
+`quarkus.container-image.build=true` property configured), then a container is created and run (this requires the `docker` or `podman` executable being present).
This is a black box test that supports the same set features and has the same limitations.
diff --git a/extensions/container-image/container-image-docker-common/deployment/pom.xml b/extensions/container-image/container-image-docker-common/deployment/pom.xml
new file mode 100644
index 0000000000000..6f553dc401722
--- /dev/null
+++ b/extensions/container-image/container-image-docker-common/deployment/pom.xml
@@ -0,0 +1,47 @@
+
+
+
+ quarkus-container-image-docker-common-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-container-image-docker-common-deployment
+ Quarkus - Container Image - Docker Common - Deployment
+
+
+
+ io.quarkus
+ quarkus-container-image-docker-common
+
+
+ io.quarkus
+ quarkus-container-image-deployment
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonConfig.java b/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonConfig.java
new file mode 100644
index 0000000000000..426240ae842ed
--- /dev/null
+++ b/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonConfig.java
@@ -0,0 +1,56 @@
+package io.quarkus.container.image.docker.common.deployment;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import io.quarkus.runtime.annotations.ConfigDocDefault;
+import io.quarkus.runtime.annotations.ConfigDocMapKey;
+
+public interface CommonConfig {
+ /**
+ * Path to the JVM Dockerfile.
+ * If set to an absolute path then the absolute path will be used, otherwise the path
+ * will be considered relative to the project root.
+ * If not set src/main/docker/Dockerfile.jvm will be used.
+ */
+ @ConfigDocDefault("src/main/docker/Dockerfile.jvm")
+ Optional dockerfileJvmPath();
+
+ /**
+ * Path to the native Dockerfile.
+ * If set to an absolute path then the absolute path will be used, otherwise the path
+ * will be considered relative to the project root.
+ * If not set src/main/docker/Dockerfile.native will be used.
+ */
+ @ConfigDocDefault("src/main/docker/Dockerfile.native")
+ Optional dockerfileNativePath();
+
+ /**
+ * Build args passed to docker via {@code --build-arg}
+ */
+ @ConfigDocMapKey("arg-name")
+ Map buildArgs();
+
+ /**
+ * Images to consider as cache sources. Values are passed to {@code docker build}/{@code podman build} via the
+ * {@code cache-from} option
+ */
+ Optional> cacheFrom();
+
+ /**
+ * The networking mode for the RUN instructions during build
+ */
+ Optional network();
+
+ /**
+ * Name of binary used to execute the docker/podman commands.
+ * This setting can override the global container runtime detection.
+ */
+ Optional executableName();
+
+ /**
+ * Additional arbitrary arguments passed to the executable when building the container image.
+ */
+ Optional> additionalArgs();
+}
diff --git a/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonProcessor.java b/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonProcessor.java
new file mode 100644
index 0000000000000..ff9e2bbdbc5a7
--- /dev/null
+++ b/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonProcessor.java
@@ -0,0 +1,343 @@
+package io.quarkus.container.image.docker.common.deployment;
+
+import static io.quarkus.container.image.deployment.util.EnablementUtil.buildContainerImageNeeded;
+import static io.quarkus.container.image.deployment.util.EnablementUtil.pushContainerImageNeeded;
+import static io.quarkus.container.util.PathsUtil.findMainSourcesRoot;
+import static io.quarkus.deployment.util.ContainerRuntimeUtil.detectContainerRuntime;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.container.image.deployment.ContainerImageConfig;
+import io.quarkus.container.image.deployment.util.NativeBinaryUtil;
+import io.quarkus.container.spi.ContainerImageBuildRequestBuildItem;
+import io.quarkus.container.spi.ContainerImageBuilderBuildItem;
+import io.quarkus.container.spi.ContainerImageInfoBuildItem;
+import io.quarkus.container.spi.ContainerImagePushRequestBuildItem;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.builditem.ContainerRuntimeStatusBuildItem;
+import io.quarkus.deployment.pkg.PackageConfig;
+import io.quarkus.deployment.pkg.PackageConfig.JarConfig.JarType;
+import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
+import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem;
+import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
+import io.quarkus.deployment.util.ContainerRuntimeUtil.ContainerRuntime;
+import io.quarkus.deployment.util.ExecUtil;
+
+public abstract class CommonProcessor {
+ private static final Logger LOGGER = Logger.getLogger(CommonProcessor.class);
+ protected static final String DOCKERFILE_JVM = "Dockerfile.jvm";
+ protected static final String DOCKERFILE_LEGACY_JAR = "Dockerfile.legacy-jar";
+ protected static final String DOCKERFILE_NATIVE = "Dockerfile.native";
+ protected static final String DOCKER_DIRECTORY_NAME = "docker";
+
+ protected abstract String getProcessorImplementation();
+
+ protected abstract String createContainerImage(ContainerImageConfig containerImageConfig, C config,
+ ContainerImageInfoBuildItem containerImageInfo, OutputTargetBuildItem out, DockerfilePaths dockerfilePaths,
+ boolean buildContainerImage, boolean pushContainerImage, PackageConfig packageConfig, String executableName);
+
+ protected void buildFromJar(C config,
+ ContainerRuntimeStatusBuildItem containerRuntimeStatusBuildItem,
+ ContainerImageConfig containerImageConfig,
+ OutputTargetBuildItem out,
+ ContainerImageInfoBuildItem containerImageInfo,
+ Optional buildRequest,
+ Optional pushRequest,
+ BuildProducer artifactResultProducer,
+ BuildProducer containerImageBuilder,
+ PackageConfig packageConfig,
+ ContainerRuntime containerRuntime) {
+
+ var buildContainerImage = buildContainerImageNeeded(containerImageConfig, buildRequest);
+ var pushContainerImage = pushContainerImageNeeded(containerImageConfig, pushRequest);
+
+ if (buildContainerImage || pushContainerImage) {
+ if (!containerRuntimeStatusBuildItem.isContainerRuntimeAvailable()) {
+ throw new RuntimeException(
+ "Unable to build container image. Please check your %s installation."
+ .formatted(getProcessorImplementation()));
+ }
+
+ var dockerfilePaths = getDockerfilePaths(config, false, packageConfig, out);
+ var dockerfileBaseInformation = DockerFileBaseInformationProvider.impl()
+ .determine(dockerfilePaths.dockerfilePath());
+
+ if (dockerfileBaseInformation.isPresent() && (dockerfileBaseInformation.get().javaVersion() < 17)) {
+ throw new IllegalStateException(
+ "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."
+ .formatted(
+ dockerfilePaths.dockerfilePath().toAbsolutePath(),
+ dockerfileBaseInformation.get().baseImage()));
+ }
+
+ if (buildContainerImage) {
+ LOGGER.infof("Starting (local) container image build for jar using %s", getProcessorImplementation());
+ }
+
+ var executableName = getExecutableName(config, containerRuntime);
+ var builtContainerImage = createContainerImage(containerImageConfig, config, containerImageInfo, out,
+ dockerfilePaths, buildContainerImage, pushContainerImage, packageConfig, executableName);
+
+ // a pull is not required when using this image locally because the strategy always builds the container image
+ // locally before pushing it to the registry
+ artifactResultProducer.produce(
+ new ArtifactResultBuildItem(
+ null,
+ "jar-container",
+ Map.of(
+ "container-image", builtContainerImage,
+ "pull-required", "false")));
+
+ containerImageBuilder.produce(new ContainerImageBuilderBuildItem(getProcessorImplementation()));
+ }
+ }
+
+ protected void buildFromNativeImage(C config,
+ ContainerRuntimeStatusBuildItem containerRuntimeStatusBuildItem,
+ ContainerImageConfig containerImageConfig,
+ ContainerImageInfoBuildItem containerImage,
+ Optional buildRequest,
+ Optional pushRequest,
+ OutputTargetBuildItem out,
+ BuildProducer artifactResultProducer,
+ BuildProducer containerImageBuilder,
+ PackageConfig packageConfig,
+ NativeImageBuildItem nativeImage,
+ ContainerRuntime containerRuntime) {
+
+ var buildContainerImage = buildContainerImageNeeded(containerImageConfig, buildRequest);
+ var pushContainerImage = pushContainerImageNeeded(containerImageConfig, pushRequest);
+
+ if (buildContainerImage || pushContainerImage) {
+ if (!containerRuntimeStatusBuildItem.isContainerRuntimeAvailable()) {
+ throw new RuntimeException(
+ "Unable to build container image. Please check your %s installation."
+ .formatted(getProcessorImplementation()));
+ }
+
+ if (!NativeBinaryUtil.nativeIsLinuxBinary(nativeImage)) {
+ throw new RuntimeException(
+ "The native binary produced by the build is not a Linux binary and therefore cannot be used in a Linux container image. Consider adding \"quarkus.native.container-build=true\" to your configuration.");
+ }
+
+ if (buildContainerImage) {
+ LOGGER.infof("Starting (local) container image build for jar using %s", getProcessorImplementation());
+ }
+
+ var executableName = getExecutableName(config, containerRuntime);
+ var dockerfilePaths = getDockerfilePaths(config, true, packageConfig, out);
+ var builtContainerImage = createContainerImage(containerImageConfig, config, containerImage, out, dockerfilePaths,
+ buildContainerImage, pushContainerImage, packageConfig, executableName);
+
+ // a pull is not required when using this image locally because the strategy always builds the container image
+ // locally before pushing it to the registry
+ artifactResultProducer.produce(
+ new ArtifactResultBuildItem(
+ null,
+ "native-container",
+ Map.of(
+ "container-image", builtContainerImage,
+ "pull-required", "false")));
+
+ containerImageBuilder.produce(new ContainerImageBuilderBuildItem(getProcessorImplementation()));
+ }
+ }
+
+ protected void loginToRegistryIfNeeded(ContainerImageConfig containerImageConfig,
+ ContainerImageInfoBuildItem containerImageInfo,
+ String executableName) {
+
+ var registry = containerImageInfo.getRegistry()
+ .orElseGet(() -> {
+ LOGGER.info("No container image registry was set, so 'docker.io' will be used");
+ return "docker.io";
+ });
+
+ // Check if we need to login first
+ if (containerImageConfig.username.isPresent() && containerImageConfig.password.isPresent()) {
+ var loginSuccessful = ExecUtil.exec(executableName, "login", registry, "-u", containerImageConfig.username.get(),
+ "-p", containerImageConfig.password.get());
+
+ if (!loginSuccessful) {
+ throw containerRuntimeException(executableName,
+ new String[] { "-u", containerImageConfig.username.get(), "-p", "********" });
+ }
+ }
+ }
+
+ protected List getContainerCommonBuildArgs(String image,
+ DockerfilePaths dockerfilePaths,
+ ContainerImageConfig containerImageConfig,
+ C config,
+ boolean addImageAsTag) {
+
+ var args = new ArrayList(6 + config.buildArgs().size() + config.additionalArgs().map(List::size).orElse(0));
+ args.addAll(List.of("build", "-f", dockerfilePaths.dockerfilePath().toAbsolutePath().toString()));
+
+ config.buildArgs().forEach((k, v) -> args.addAll(List.of("--build-arg", "%s=%s".formatted(k, v))));
+ containerImageConfig.labels.forEach((k, v) -> args.addAll(List.of("--label", "%s=%s".formatted(k, v))));
+ config.cacheFrom()
+ .filter(cacheFrom -> !cacheFrom.isEmpty())
+ .ifPresent(cacheFrom -> args.addAll(List.of("--cache-from", String.join(",", cacheFrom))));
+ config.network().ifPresent(network -> args.addAll(List.of("--network", network)));
+ config.additionalArgs().ifPresent(args::addAll);
+
+ if (addImageAsTag) {
+ args.addAll(List.of("-t", image));
+ }
+
+ return args;
+ }
+
+ protected void createAdditionalTags(String image, List additionalImageTags, String executableName) {
+ additionalImageTags.stream()
+ .map(additionalTag -> new String[] { "tag", image, additionalTag })
+ .forEach(tagArgs -> {
+ LOGGER.infof("Running '%s %s'", executableName, String.join(" ", tagArgs));
+ var tagSuccessful = ExecUtil.exec(executableName, tagArgs);
+
+ if (!tagSuccessful) {
+ throw containerRuntimeException(executableName, tagArgs);
+ }
+ });
+ }
+
+ protected void pushImages(ContainerImageInfoBuildItem containerImageInfo, String executableName) {
+ Stream.concat(containerImageInfo.getAdditionalImageTags().stream(), Stream.of(containerImageInfo.getImage()))
+ .forEach(imageToPush -> pushImage(imageToPush, executableName));
+ }
+
+ protected void pushImage(String image, String executableName) {
+ String[] pushArgs = { "push", image };
+ var pushSuccessful = ExecUtil.exec(executableName, pushArgs);
+
+ if (!pushSuccessful) {
+ throw containerRuntimeException(executableName, pushArgs);
+ }
+
+ LOGGER.infof("Successfully pushed %s image %s", getProcessorImplementation(), image);
+ }
+
+ protected void buildImage(ContainerImageInfoBuildItem containerImageInfo,
+ OutputTargetBuildItem out,
+ String executableName,
+ String[] args,
+ boolean createAdditionalTags) {
+
+ LOGGER.infof("Executing the following command to build image: '%s %s'", executableName,
+ String.join(" ", args));
+ var buildSuccessful = ExecUtil.exec(out.getOutputDirectory().toFile(), executableName, args);
+
+ if (!buildSuccessful) {
+ throw containerRuntimeException(executableName, args);
+ }
+
+ if (createAdditionalTags && !containerImageInfo.getAdditionalImageTags().isEmpty()) {
+ createAdditionalTags(containerImageInfo.getImage(), containerImageInfo.getAdditionalImageTags(),
+ executableName);
+ }
+ }
+
+ protected RuntimeException containerRuntimeException(String executableName, String[] args) {
+ return new RuntimeException(
+ "Execution of '%s %s' failed. See %s output for more details"
+ .formatted(
+ executableName,
+ String.join(" ", args),
+ getProcessorImplementation()));
+ }
+
+ private String getExecutableName(C config, ContainerRuntime containerRuntime) {
+ return config.executableName()
+ .orElseGet(() -> detectContainerRuntime(List.of(containerRuntime)).getExecutableName());
+ }
+
+ private DockerfilePaths getDockerfilePaths(C config,
+ boolean forNative,
+ PackageConfig packageConfig,
+ OutputTargetBuildItem out) {
+
+ var outputDirectory = out.getOutputDirectory();
+
+ if (forNative) {
+ return config.dockerfileNativePath()
+ .map(dockerfileNativePath -> ProvidedDockerfile.get(Paths.get(dockerfileNativePath), outputDirectory))
+ .orElseGet(() -> DockerfileDetectionResult.detect(DOCKERFILE_NATIVE, outputDirectory));
+ } else {
+ return config.dockerfileJvmPath()
+ .map(dockerfileJvmPath -> ProvidedDockerfile.get(Paths.get(dockerfileJvmPath), outputDirectory))
+ .orElseGet(() -> (packageConfig.jar().type() == JarType.LEGACY_JAR)
+ ? DockerfileDetectionResult.detect(DOCKERFILE_LEGACY_JAR, outputDirectory)
+ : DockerfileDetectionResult.detect(DOCKERFILE_JVM, outputDirectory));
+ }
+ }
+
+ protected interface DockerfilePaths {
+ Path dockerfilePath();
+
+ Path dockerExecutionPath();
+ }
+
+ protected record DockerfileDetectionResult(Path dockerfilePath, Path dockerExecutionPath) implements DockerfilePaths {
+ protected static DockerfilePaths detect(String resource, Path outputDirectory) {
+ var dockerfileToExecutionRoot = findDockerfileRoot(outputDirectory);
+ if (dockerfileToExecutionRoot == null) {
+ throw new IllegalStateException(
+ "Unable to find root of Dockerfile files. Consider adding 'src/main/docker/' to your project root.");
+ }
+
+ var dockerFilePath = dockerfileToExecutionRoot.getKey().resolve(resource);
+ if (!Files.exists(dockerFilePath)) {
+ throw new IllegalStateException(
+ "Unable to find Dockerfile %s in %s"
+ .formatted(resource, dockerfileToExecutionRoot.getKey().toAbsolutePath()));
+ }
+
+ return new DockerfileDetectionResult(dockerFilePath, dockerfileToExecutionRoot.getValue());
+ }
+
+ private static Map.Entry findDockerfileRoot(Path outputDirectory) {
+ var mainSourcesRoot = findMainSourcesRoot(outputDirectory);
+ if (mainSourcesRoot == null) {
+ return null;
+ }
+
+ var dockerfilesRoot = mainSourcesRoot.getKey().resolve(DOCKER_DIRECTORY_NAME);
+ if (!dockerfilesRoot.toFile().exists()) {
+ return null;
+ }
+
+ return new AbstractMap.SimpleEntry<>(dockerfilesRoot, mainSourcesRoot.getValue());
+ }
+ }
+
+ protected record ProvidedDockerfile(Path dockerfilePath, Path dockerExecutionPath) implements DockerfilePaths {
+ protected static DockerfilePaths get(Path dockerfilePath, Path outputDirectory) {
+ var mainSourcesRoot = findMainSourcesRoot(outputDirectory);
+
+ if (mainSourcesRoot == null) {
+ throw new IllegalStateException("Unable to determine project root");
+ }
+
+ var executionPath = mainSourcesRoot.getValue();
+ var effectiveDockerfilePath = dockerfilePath.isAbsolute() ? dockerfilePath : executionPath.resolve(dockerfilePath);
+
+ if (!effectiveDockerfilePath.toFile().exists()) {
+ throw new IllegalArgumentException(
+ "Specified Dockerfile path %s does not exist".formatted(effectiveDockerfilePath.toAbsolutePath()));
+ }
+
+ return new ProvidedDockerfile(effectiveDockerfilePath, executionPath);
+ }
+ }
+}
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-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/DockerFileBaseInformationProvider.java
similarity index 51%
rename from extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerFileBaseInformationProvider.java
rename to extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/DockerFileBaseInformationProvider.java
index 262cffd8c8a95..f80fdf5c5e950 100644
--- 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-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/DockerFileBaseInformationProvider.java
@@ -1,10 +1,10 @@
-package io.quarkus.container.image.docker.deployment;
+package io.quarkus.container.image.docker.common.deployment;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
-interface DockerFileBaseInformationProvider {
+public interface DockerFileBaseInformationProvider {
Optional determine(Path dockerFile);
@@ -16,32 +16,19 @@ static DockerFileBaseInformationProvider impl() {
@Override
public Optional determine(Path dockerFile) {
- for (DockerFileBaseInformationProvider delegate : delegates) {
- Optional result = delegate.determine(dockerFile);
+ for (var delegate : delegates) {
+ var 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;
- }
+ record DockerFileBaseInformation(String baseImage, int javaVersion) {
}
}
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-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/RedHatOpenJDKRuntimeBaseProvider.java
similarity index 90%
rename from extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProvider.java
rename to extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/RedHatOpenJDKRuntimeBaseProvider.java
index 1a955893486e1..0c15a1640a245 100644
--- 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-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/RedHatOpenJDKRuntimeBaseProvider.java
@@ -1,4 +1,4 @@
-package io.quarkus.container.image.docker.deployment;
+package io.quarkus.container.image.docker.common.deployment;
import java.io.IOException;
import java.nio.file.Files;
@@ -12,8 +12,7 @@
* 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 {
+class RedHatOpenJDKRuntimeBaseProvider implements DockerFileBaseInformationProvider {
@Override
public Optional determine(Path dockerFile) {
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-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/UbiMinimalBaseProvider.java
similarity index 94%
rename from extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProvider.java
rename to extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/UbiMinimalBaseProvider.java
index 1ad6adc24f6a7..4ac17527960e3 100644
--- 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-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/UbiMinimalBaseProvider.java
@@ -1,4 +1,4 @@
-package io.quarkus.container.image.docker.deployment;
+package io.quarkus.container.image.docker.common.deployment;
import java.io.IOException;
import java.nio.file.Files;
@@ -14,8 +14,7 @@
* 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 {
+class UbiMinimalBaseProvider implements DockerFileBaseInformationProvider {
public static final String UBI_MINIMAL_PREFIX = "registry.access.redhat.com/ubi8/ubi-minimal";
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-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
similarity index 64%
rename from extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
rename to extensions/container-image/container-image-docker-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
index d09507d72b2de..119f4f81eac8c 100644
--- 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-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
@@ -1,6 +1,6 @@
-package io.quarkus.container.image.docker.deployment;
+package io.quarkus.container.image.docker.common.deployment;
-import static io.quarkus.container.image.docker.deployment.TestUtil.getPath;
+import static io.quarkus.container.image.docker.common.deployment.TestUtil.getPath;
import static org.assertj.core.api.Assertions.assertThat;
import java.nio.file.Path;
@@ -16,8 +16,8 @@ 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:1.19");
- assertThat(v.getJavaVersion()).isEqualTo(17);
+ assertThat(v.baseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.19");
+ assertThat(v.javaVersion()).isEqualTo(17);
});
}
@@ -26,8 +26,8 @@ void testImageWithJava21() {
Path path = getPath("openjdk-21-runtime");
var result = sut.determine(path);
assertThat(result).hasValueSatisfying(v -> {
- assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-21-runtime:1.19");
- assertThat(v.getJavaVersion()).isEqualTo(21);
+ assertThat(v.baseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-21-runtime:1.19");
+ assertThat(v.javaVersion()).isEqualTo(21);
});
}
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-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/TestUtil.java
similarity index 87%
rename from extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/TestUtil.java
rename to extensions/container-image/container-image-docker-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/TestUtil.java
index ad45ce736c77a..180b4940429f8 100644
--- 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-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/TestUtil.java
@@ -1,4 +1,4 @@
-package io.quarkus.container.image.docker.deployment;
+package io.quarkus.container.image.docker.common.deployment;
import java.net.URISyntaxException;
import java.nio.file.Path;
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-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/UbiMinimalBaseProviderTest.java
similarity index 64%
rename from extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProviderTest.java
rename to extensions/container-image/container-image-docker-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/UbiMinimalBaseProviderTest.java
index a1b4b9d6747a4..784f6fc3b1bd1 100644
--- 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-common/deployment/src/test/java/io/quarkus/container/image/docker/common/deployment/UbiMinimalBaseProviderTest.java
@@ -1,6 +1,6 @@
-package io.quarkus.container.image.docker.deployment;
+package io.quarkus.container.image.docker.common.deployment;
-import static io.quarkus.container.image.docker.deployment.TestUtil.getPath;
+import static io.quarkus.container.image.docker.common.deployment.TestUtil.getPath;
import static org.assertj.core.api.Assertions.assertThat;
import java.nio.file.Path;
@@ -16,8 +16,8 @@ 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:8.9");
- assertThat(v.getJavaVersion()).isEqualTo(17);
+ assertThat(v.baseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal:8.9");
+ assertThat(v.javaVersion()).isEqualTo(17);
});
}
@@ -26,8 +26,8 @@ void testImageWithJava21() {
Path path = getPath("ubi-java21");
var result = sut.determine(path);
assertThat(result).hasValueSatisfying(v -> {
- assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal:8.9");
- assertThat(v.getJavaVersion()).isEqualTo(21);
+ assertThat(v.baseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal:8.9");
+ assertThat(v.javaVersion()).isEqualTo(21);
});
}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime b/extensions/container-image/container-image-docker-common/deployment/src/test/resources/openjdk-17-runtime
similarity index 100%
rename from extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
rename to extensions/container-image/container-image-docker-common/deployment/src/test/resources/openjdk-17-runtime
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime b/extensions/container-image/container-image-docker-common/deployment/src/test/resources/openjdk-21-runtime
similarity index 100%
rename from extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime
rename to extensions/container-image/container-image-docker-common/deployment/src/test/resources/openjdk-21-runtime
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17 b/extensions/container-image/container-image-docker-common/deployment/src/test/resources/ubi-java17
similarity index 100%
rename from extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17
rename to extensions/container-image/container-image-docker-common/deployment/src/test/resources/ubi-java17
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java21 b/extensions/container-image/container-image-docker-common/deployment/src/test/resources/ubi-java21
similarity index 100%
rename from extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java21
rename to extensions/container-image/container-image-docker-common/deployment/src/test/resources/ubi-java21
diff --git a/extensions/container-image/container-image-docker-common/pom.xml b/extensions/container-image/container-image-docker-common/pom.xml
new file mode 100644
index 0000000000000..c115d50d4603d
--- /dev/null
+++ b/extensions/container-image/container-image-docker-common/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ quarkus-container-image-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-container-image-docker-common-parent
+ Quarkus - Container Image - Docker Common - Parent
+ pom
+
+ deployment
+ runtime
+
+
+
diff --git a/extensions/container-image/container-image-docker-common/runtime/pom.xml b/extensions/container-image/container-image-docker-common/runtime/pom.xml
new file mode 100644
index 0000000000000..c7b6cebce33b9
--- /dev/null
+++ b/extensions/container-image/container-image-docker-common/runtime/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+
+ quarkus-container-image-docker-common-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+
+ quarkus-container-image-docker-common
+ Quarkus - Container Image - Docker Common
+ Build container images of your application using Docker APIs
+
+
+
+ io.quarkus
+ quarkus-container-image
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/container-image/container-image-docker-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/container-image/container-image-docker-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..3e7ecf140c725
--- /dev/null
+++ b/extensions/container-image/container-image-docker-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,15 @@
+---
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+name: "Container Image - Docker common"
+metadata:
+ keywords:
+ - "container"
+ - "image"
+ - "docker"
+ categories:
+ - "cloud"
+ status: "preview"
+ unlisted: true
+ config:
+ - "quarkus.docker."
+ - "quarkus.podman."
\ No newline at end of file
diff --git a/extensions/container-image/container-image-docker/deployment/pom.xml b/extensions/container-image/container-image-docker/deployment/pom.xml
index 0985cbc99c3b0..1d6fb564a0cea 100644
--- a/extensions/container-image/container-image-docker/deployment/pom.xml
+++ b/extensions/container-image/container-image-docker/deployment/pom.xml
@@ -20,7 +20,7 @@
io.quarkus
- quarkus-container-image-deployment
+ quarkus-container-image-docker-common-deployment
org.assertj
diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerConfig.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerConfig.java
index b3eb7f0c91454..ed802507fd777 100644
--- a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerConfig.java
+++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerConfig.java
@@ -1,78 +1,23 @@
package io.quarkus.container.image.docker.deployment;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
-import io.quarkus.runtime.annotations.ConfigDocDefault;
-import io.quarkus.runtime.annotations.ConfigDocMapKey;
+import io.quarkus.container.image.docker.common.deployment.CommonConfig;
import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigGroup;
-import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
-public class DockerConfig {
-
- /**
- * Path to the JVM Dockerfile.
- * If set to an absolute path then the absolute path will be used, otherwise the path
- * will be considered relative to the project root.
- * If not set src/main/docker/Dockerfile.jvm will be used.
- */
- @ConfigItem
- @ConfigDocDefault("src/main/docker/Dockerfile.jvm")
- public Optional dockerfileJvmPath;
-
- /**
- * Path to the native Dockerfile.
- * If set to an absolute path then the absolute path will be used, otherwise the path
- * will be considered relative to the project root.
- * If not set src/main/docker/Dockerfile.native will be used.
- */
- @ConfigItem
- @ConfigDocDefault("src/main/docker/Dockerfile.native")
- public Optional dockerfileNativePath;
-
- /**
- * Build args passed to docker via {@code --build-arg}
- */
- @ConfigItem
- @ConfigDocMapKey("arg-name")
- public Map buildArgs;
-
- /**
- * Images to consider as cache sources. Values are passed to {@code docker build} via the {@code cache-from} option
- */
- @ConfigItem
- public Optional> cacheFrom;
-
- /**
- * The networking mode for the RUN instructions during build
- */
- @ConfigItem
- public Optional network;
-
- /**
- * Name of binary used to execute the docker commands.
- * This setting can override the global container runtime detection.
- */
- @ConfigItem
- public Optional executableName;
-
- /**
- * Additional arbitrary arguments passed to the executable when building the container image.
- */
- @ConfigItem
- public Optional> additionalArgs;
-
+@ConfigMapping(prefix = "quarkus.docker")
+public interface DockerConfig extends CommonConfig {
/**
* Configuration for Docker Buildx options
*/
- @ConfigItem
@ConfigDocSection
- public DockerBuildxConfig buildx;
+ DockerBuildxConfig buildx();
/**
* Configuration for Docker Buildx options. These are only relevant if using Docker Buildx
@@ -82,13 +27,12 @@ public class DockerConfig {
* If any of these configurations are set, it will add {@code buildx} to the {@code executableName}.
*/
@ConfigGroup
- public static class DockerBuildxConfig {
+ interface DockerBuildxConfig {
/**
* Which platform(s) to target during the build. See
* https://docs.docker.com/engine/reference/commandline/buildx_build/#platform
*/
- @ConfigItem
- public Optional> platform;
+ Optional> platform();
/**
* Sets the export action for the build result. See
@@ -96,20 +40,18 @@ public static class DockerBuildxConfig {
* absolute paths,
* not relative from where the command is executed from.
*/
- @ConfigItem
- public Optional output;
+ Optional output();
/**
* Set type of progress output ({@code auto}, {@code plain}, {@code tty}). Use {@code plain} to show container output
* (default “{@code auto}”). See https://docs.docker.com/engine/reference/commandline/buildx_build/#progress
*/
- @ConfigItem
- public Optional progress;
+ Optional progress();
- boolean useBuildx() {
- return platform.filter(p -> !p.isEmpty()).isPresent() ||
- output.isPresent() ||
- progress.isPresent();
+ default boolean useBuildx() {
+ return platform().filter(p -> !p.isEmpty()).isPresent() ||
+ output().isPresent() ||
+ progress().isPresent();
}
}
}
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 5c94f9c13715b..830e214559474 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
@@ -1,27 +1,13 @@
package io.quarkus.container.image.docker.deployment;
-import static io.quarkus.container.image.deployment.util.EnablementUtil.buildContainerImageNeeded;
-import static io.quarkus.container.image.deployment.util.EnablementUtil.pushContainerImageNeeded;
-import static io.quarkus.container.util.PathsUtil.findMainSourcesRoot;
-import static io.quarkus.deployment.pkg.PackageConfig.JarConfig.JarType.*;
-import static io.quarkus.deployment.util.ContainerRuntimeUtil.detectContainerRuntime;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
-import java.util.stream.Stream;
import org.jboss.logging.Logger;
import io.quarkus.container.image.deployment.ContainerImageConfig;
-import io.quarkus.container.image.deployment.util.NativeBinaryUtil;
+import io.quarkus.container.image.docker.common.deployment.CommonProcessor;
import io.quarkus.container.spi.AvailableContainerImageExtensionBuildItem;
import io.quarkus.container.spi.ContainerImageBuildRequestBuildItem;
import io.quarkus.container.spi.ContainerImageBuilderBuildItem;
@@ -40,18 +26,18 @@
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.deployment.pkg.builditem.UpxCompressedBuildItem;
import io.quarkus.deployment.pkg.steps.NativeBuild;
-import io.quarkus.deployment.util.ExecUtil;
-
-public class DockerProcessor {
+import io.quarkus.deployment.util.ContainerRuntimeUtil.ContainerRuntime;
- private static final Logger log = Logger.getLogger(DockerProcessor.class);
+public class DockerProcessor extends CommonProcessor {
+ private static final Logger LOG = Logger.getLogger(DockerProcessor.class);
private static final String DOCKER = "docker";
- private static final String DOCKERFILE_JVM = "Dockerfile.jvm";
- private static final String DOCKERFILE_LEGACY_JAR = "Dockerfile.legacy-jar";
- private static final String DOCKERFILE_NATIVE = "Dockerfile.native";
- private static final String DOCKER_DIRECTORY_NAME = "docker";
static final String DOCKER_CONTAINER_IMAGE_NAME = "docker";
+ @Override
+ protected String getProcessorImplementation() {
+ return DOCKER;
+ }
+
@BuildStep
public AvailableContainerImageExtensionBuildItem availability() {
return new AvailableContainerImageExtensionBuildItem(DOCKER);
@@ -63,53 +49,19 @@ public void dockerBuildFromJar(DockerConfig dockerConfig,
ContainerImageConfig containerImageConfig,
OutputTargetBuildItem out,
ContainerImageInfoBuildItem containerImageInfo,
- CompiledJavaVersionBuildItem compiledJavaVersion,
+ @SuppressWarnings("unused") CompiledJavaVersionBuildItem compiledJavaVersion,
Optional buildRequest,
Optional pushRequest,
@SuppressWarnings("unused") Optional appCDSResult, // ensure docker build will be performed after AppCDS creation
BuildProducer artifactResultProducer,
BuildProducer containerImageBuilder,
PackageConfig packageConfig,
- @SuppressWarnings("unused") // used to ensure that the jar has been built
- JarBuildItem jar) {
-
- boolean buildContainerImage = buildContainerImageNeeded(containerImageConfig, buildRequest);
- boolean pushContainerImage = pushContainerImageNeeded(containerImageConfig, pushRequest);
- if (!buildContainerImage && !pushContainerImage) {
- return;
- }
+ @SuppressWarnings("unused") JarBuildItem jar // used to ensure that the jar has been built
+ ) {
- if (!dockerStatusBuildItem.isDockerAvailable()) {
- throw new RuntimeException("Unable to build docker image. Please check your docker installation");
- }
-
- DockerfilePaths dockerfilePaths = getDockerfilePaths(dockerConfig, false, packageConfig, out);
- DockerFileBaseInformationProvider dockerFileBaseInformationProvider = DockerFileBaseInformationProvider.impl();
- Optional dockerFileBaseInformation = dockerFileBaseInformationProvider
- .determine(dockerfilePaths.getDockerfilePath());
-
- if (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()));
- }
-
- if (buildContainerImage) {
- log.info("Starting (local) container image build for jar using docker.");
- }
-
- String builtContainerImage = createContainerImage(containerImageConfig, dockerConfig, containerImageInfo, out,
- false,
- buildContainerImage,
- pushContainerImage, packageConfig);
-
- // a pull is not required when using this image locally because the docker strategy always builds the container image
- // locally before pushing it to the registry
- artifactResultProducer.produce(new ArtifactResultBuildItem(null, "jar-container",
- Map.of("container-image", builtContainerImage, "pull-required", "false")));
- containerImageBuilder.produce(new ContainerImageBuilderBuildItem(DOCKER));
+ buildFromJar(dockerConfig, dockerStatusBuildItem, containerImageConfig, out, containerImageInfo,
+ buildRequest, pushRequest, artifactResultProducer, containerImageBuilder, packageConfig,
+ ContainerRuntime.DOCKER);
}
@BuildStep(onlyIf = { IsNormalNotRemoteDev.class, NativeBuild.class, DockerBuild.class })
@@ -120,49 +72,30 @@ public void dockerBuildFromNativeImage(DockerConfig dockerConfig,
Optional buildRequest,
Optional pushRequest,
OutputTargetBuildItem out,
- Optional upxCompressed, // used to ensure that we work with the compressed native binary if compression was enabled
+ @SuppressWarnings("unused") Optional upxCompressed, // used to ensure that we work with the compressed native binary if compression was enabled
BuildProducer artifactResultProducer,
BuildProducer containerImageBuilder,
PackageConfig packageConfig,
// used to ensure that the native binary has been built
NativeImageBuildItem nativeImage) {
- boolean buildContainerImage = buildContainerImageNeeded(containerImageConfig, buildRequest);
- boolean pushContainerImage = pushContainerImageNeeded(containerImageConfig, pushRequest);
- if (!buildContainerImage && !pushContainerImage) {
- return;
- }
-
- if (!dockerStatusBuildItem.isDockerAvailable()) {
- throw new RuntimeException("Unable to build docker image. Please check your docker installation");
- }
-
- if (!NativeBinaryUtil.nativeIsLinuxBinary(nativeImage)) {
- throw new RuntimeException(
- "The native binary produced by the build is not a Linux binary and therefore cannot be used in a Linux container image. Consider adding \"quarkus.native.container-build=true\" to your configuration");
- }
-
- log.info("Starting (local) container image build for native binary using docker.");
-
- String builtContainerImage = createContainerImage(containerImageConfig, dockerConfig, containerImage, out, true,
- buildContainerImage,
- pushContainerImage, packageConfig);
-
- // a pull is not required when using this image locally because the docker strategy always builds the container image
- // locally before pushing it to the registry
- artifactResultProducer.produce(new ArtifactResultBuildItem(null, "native-container",
- Map.of("container-image", builtContainerImage, "pull-required", "false")));
-
- containerImageBuilder.produce(new ContainerImageBuilderBuildItem(DOCKER));
+ buildFromNativeImage(dockerConfig, dockerStatusBuildItem, containerImageConfig, containerImage,
+ buildRequest, pushRequest, out, artifactResultProducer, containerImageBuilder, packageConfig, nativeImage,
+ ContainerRuntime.DOCKER);
}
- private String createContainerImage(ContainerImageConfig containerImageConfig, DockerConfig dockerConfig,
+ @Override
+ protected String createContainerImage(ContainerImageConfig containerImageConfig,
+ DockerConfig dockerConfig,
ContainerImageInfoBuildItem containerImageInfo,
- OutputTargetBuildItem out, boolean forNative, boolean buildContainerImage,
+ OutputTargetBuildItem out,
+ DockerfilePaths dockerfilePaths,
+ boolean buildContainerImage,
boolean pushContainerImage,
- PackageConfig packageConfig) {
+ PackageConfig packageConfig,
+ String executableName) {
- boolean useBuildx = dockerConfig.buildx.useBuildx();
+ boolean useBuildx = dockerConfig.buildx().useBuildx();
// useBuildx: Whether any of the buildx parameters are set
//
@@ -180,274 +113,90 @@ private String createContainerImage(ContainerImageConfig containerImageConfig, D
// This is because when using buildx with more than one platform, the resulting images are not loaded into 'docker images'.
// Therefore, a docker tag or docker push will not work after a docker build.
- DockerfilePaths dockerfilePaths = getDockerfilePaths(dockerConfig, forNative, packageConfig, out);
- String[] dockerArgs = getDockerArgs(containerImageInfo.getImage(), dockerfilePaths, containerImageConfig, dockerConfig,
- containerImageInfo, pushContainerImage);
-
if (useBuildx && pushContainerImage) {
// Needed because buildx will push all the images in a single step
- loginToRegistryIfNeeded(containerImageConfig, containerImageInfo, dockerConfig);
+ loginToRegistryIfNeeded(containerImageConfig, containerImageInfo, executableName);
}
if (buildContainerImage) {
- final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName());
- log.infof("Executing the following command to build docker image: '%s %s'", executableName,
- String.join(" ", dockerArgs));
- boolean buildSuccessful = ExecUtil.exec(out.getOutputDirectory().toFile(), executableName, dockerArgs);
- if (!buildSuccessful) {
- throw dockerException(executableName, dockerArgs);
- }
+ var dockerBuildArgs = getDockerBuildArgs(containerImageInfo.getImage(), dockerfilePaths, containerImageConfig,
+ dockerConfig, containerImageInfo, pushContainerImage, executableName);
+
+ buildImage(containerImageInfo, out, executableName, dockerBuildArgs, false);
- dockerConfig.buildx.platform
- .filter(platform -> platform.size() > 1)
+ dockerConfig.buildx().platform()
+ .filter(platform -> !platform.isEmpty())
.ifPresentOrElse(
- platform -> log.infof("Built container image %s (%s platform(s))\n", containerImageInfo.getImage(),
+ platform -> LOG.infof("Built container image %s (%s platform(s))\n", containerImageInfo.getImage(),
String.join(",", platform)),
- () -> log.infof("Built container image %s\n", containerImageInfo.getImage()));
+ () -> LOG.infof("Built container image %s\n", containerImageInfo.getImage()));
- }
-
- if (!useBuildx && buildContainerImage) {
// If we didn't use buildx, now we need to process any tags
- if (!containerImageInfo.getAdditionalImageTags().isEmpty()) {
- createAdditionalTags(containerImageInfo.getImage(), containerImageInfo.getAdditionalImageTags(), dockerConfig);
+ if (!useBuildx && !containerImageInfo.getAdditionalImageTags().isEmpty()) {
+ createAdditionalTags(containerImageInfo.getImage(), containerImageInfo.getAdditionalImageTags(),
+ executableName);
}
}
if (!useBuildx && pushContainerImage) {
// If not using buildx, push the images
- loginToRegistryIfNeeded(containerImageConfig, containerImageInfo, dockerConfig);
-
- Stream.concat(containerImageInfo.getAdditionalImageTags().stream(), Stream.of(containerImageInfo.getImage()))
- .forEach(imageToPush -> pushImage(imageToPush, dockerConfig));
+ loginToRegistryIfNeeded(containerImageConfig, containerImageInfo, executableName);
+ pushImages(containerImageInfo, executableName);
}
return containerImageInfo.getImage();
}
- private void loginToRegistryIfNeeded(ContainerImageConfig containerImageConfig,
- ContainerImageInfoBuildItem containerImageInfo, DockerConfig dockerConfig) {
- String registry = containerImageInfo.getRegistry()
- .orElseGet(() -> {
- log.info("No container image registry was set, so 'docker.io' will be used");
- return "docker.io";
- });
-
- // Check if we need to login first
- if (containerImageConfig.username.isPresent() && containerImageConfig.password.isPresent()) {
- final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName());
- boolean loginSuccessful = ExecUtil.exec(executableName, "login", registry, "-u",
- containerImageConfig.username.get(),
- "-p" + containerImageConfig.password.get());
- if (!loginSuccessful) {
- throw dockerException(executableName,
- new String[] { "-u", containerImageConfig.username.get(), "-p", "********" });
- }
- }
- }
+ private String[] getDockerBuildArgs(String image,
+ DockerfilePaths dockerfilePaths,
+ ContainerImageConfig containerImageConfig,
+ DockerConfig dockerConfig,
+ ContainerImageInfoBuildItem containerImageInfo,
+ boolean pushImages,
+ String executableName) {
- private String[] getDockerArgs(String image, DockerfilePaths dockerfilePaths, ContainerImageConfig containerImageConfig,
- DockerConfig dockerConfig, ContainerImageInfoBuildItem containerImageInfo, boolean pushImages) {
- List dockerArgs = new ArrayList<>(6 + dockerConfig.buildArgs.size() + dockerConfig.additionalArgs.map(
- List::size).orElse(0));
- boolean useBuildx = dockerConfig.buildx.useBuildx();
+ var dockerBuildArgs = getContainerCommonBuildArgs(image, dockerfilePaths, containerImageConfig, dockerConfig, true);
+ var buildx = dockerConfig.buildx();
+ var useBuildx = buildx.useBuildx();
if (useBuildx) {
- final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName());
// Check the executable. If not 'docker', then fail the build
if (!DOCKER.equals(executableName)) {
throw new IllegalArgumentException(
- String.format(
- "The 'buildx' properties are specific to 'executable-name=docker' and can not be used with " +
- "the '%s' executable name. Either remove the `buildx` properties or the `executable-name` property.",
- executableName));
+ "The 'buildx' properties are specific to 'executable-name=docker' and can not be used with the '%s' executable name. Either remove the `buildx` properties or the `executable-name` property."
+ .formatted(executableName));
}
- dockerArgs.add("buildx");
+ dockerBuildArgs.add(0, "buildx");
}
- dockerArgs.addAll(Arrays.asList("build", "-f", dockerfilePaths.getDockerfilePath().toAbsolutePath().toString()));
- dockerConfig.buildx.platform
+ buildx.platform()
.filter(platform -> !platform.isEmpty())
.ifPresent(platform -> {
- dockerArgs.add("--platform");
- dockerArgs.add(String.join(",", platform));
+ dockerBuildArgs.addAll(List.of("--platform", String.join(",", platform)));
if (platform.size() == 1) {
// Buildx only supports loading the image to the docker system if there is only 1 image
- dockerArgs.add("--load");
+ dockerBuildArgs.add("--load");
}
});
- dockerConfig.buildx.progress.ifPresent(progress -> dockerArgs.addAll(List.of("--progress", progress)));
- dockerConfig.buildx.output.ifPresent(output -> dockerArgs.addAll(List.of("--output", output)));
- dockerConfig.buildArgs
- .forEach((key, value) -> dockerArgs.addAll(Arrays.asList("--build-arg", String.format("%s=%s", key, value))));
- containerImageConfig.labels
- .forEach((key, value) -> dockerArgs.addAll(Arrays.asList("--label", String.format("%s=%s", key, value))));
- dockerConfig.cacheFrom
- .filter(cacheFrom -> !cacheFrom.isEmpty())
- .ifPresent(cacheFrom -> {
- dockerArgs.add("--cache-from");
- dockerArgs.add(String.join(",", cacheFrom));
- });
- dockerConfig.network.ifPresent(network -> {
- dockerArgs.add("--network");
- dockerArgs.add(network);
- });
- dockerConfig.additionalArgs.ifPresent(dockerArgs::addAll);
- dockerArgs.addAll(Arrays.asList("-t", image));
+
+ buildx.progress().ifPresent(progress -> dockerBuildArgs.addAll(List.of("--progress", progress)));
+ buildx.output().ifPresent(output -> dockerBuildArgs.addAll(List.of("--output", output)));
if (useBuildx) {
// When using buildx for multi-arch images, it wants to push in a single step
// 1) Create all the additional tags
containerImageInfo.getAdditionalImageTags()
- .forEach(additionalImageTag -> dockerArgs.addAll(List.of("-t", additionalImageTag)));
+ .forEach(additionalImageTag -> dockerBuildArgs.addAll(List.of("-t", additionalImageTag)));
if (pushImages) {
// 2) Enable the --push flag
- dockerArgs.add("--push");
- }
- }
-
- dockerArgs.add(dockerfilePaths.getDockerExecutionPath().toAbsolutePath().toString());
- return dockerArgs.toArray(new String[0]);
- }
-
- private void createAdditionalTags(String image, List additionalImageTags, DockerConfig dockerConfig) {
- final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName());
- for (String additionalTag : additionalImageTags) {
- String[] tagArgs = { "tag", image, additionalTag };
- boolean tagSuccessful = ExecUtil.exec(executableName, tagArgs);
- if (!tagSuccessful) {
- throw dockerException(executableName, tagArgs);
+ dockerBuildArgs.add("--push");
}
}
- }
-
- private void pushImage(String image, DockerConfig dockerConfig) {
- final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName());
- String[] pushArgs = { "push", image };
- boolean pushSuccessful = ExecUtil.exec(executableName, pushArgs);
- if (!pushSuccessful) {
- throw dockerException(executableName, pushArgs);
- }
- log.info("Successfully pushed docker image " + image);
- }
-
- private RuntimeException dockerException(String executableName, String[] dockerArgs) {
- return new RuntimeException(
- "Execution of '" + executableName + " " + String.join(" ", dockerArgs)
- + "' failed. See docker output for more details");
- }
- @SuppressWarnings("deprecation") // legacy JAR
- private DockerfilePaths getDockerfilePaths(DockerConfig dockerConfig, boolean forNative,
- PackageConfig packageConfig,
- OutputTargetBuildItem outputTargetBuildItem) {
- Path outputDirectory = outputTargetBuildItem.getOutputDirectory();
- if (forNative) {
- if (dockerConfig.dockerfileNativePath.isPresent()) {
- return ProvidedDockerfile.get(Paths.get(dockerConfig.dockerfileNativePath.get()), outputDirectory);
- } else {
- return DockerfileDetectionResult.detect(DOCKERFILE_NATIVE, outputDirectory);
- }
- } else {
- if (dockerConfig.dockerfileJvmPath.isPresent()) {
- return ProvidedDockerfile.get(Paths.get(dockerConfig.dockerfileJvmPath.get()), outputDirectory);
- } else if (packageConfig.jar().type() == LEGACY_JAR) {
- return DockerfileDetectionResult.detect(DOCKERFILE_LEGACY_JAR, outputDirectory);
- } else {
- return DockerfileDetectionResult.detect(DOCKERFILE_JVM, outputDirectory);
- }
- }
+ dockerBuildArgs.add(dockerfilePaths.dockerExecutionPath().toAbsolutePath().toString());
+ return dockerBuildArgs.toArray(String[]::new);
}
-
- private interface DockerfilePaths {
- Path getDockerfilePath();
-
- Path getDockerExecutionPath();
- }
-
- private static class DockerfileDetectionResult implements DockerfilePaths {
- private final Path dockerfilePath;
- private final Path dockerExecutionPath;
-
- private DockerfileDetectionResult(Path dockerfilePath, Path dockerExecutionPath) {
- this.dockerfilePath = dockerfilePath;
- this.dockerExecutionPath = dockerExecutionPath;
- }
-
- public Path getDockerfilePath() {
- return dockerfilePath;
- }
-
- public Path getDockerExecutionPath() {
- return dockerExecutionPath;
- }
-
- static DockerfileDetectionResult detect(String resource, Path outputDirectory) {
- Map.Entry dockerfileToExecutionRoot = findDockerfileRoot(outputDirectory);
- if (dockerfileToExecutionRoot == null) {
- throw new IllegalStateException(
- "Unable to find root of Dockerfile files. Consider adding 'src/main/docker/' to your project root");
- }
- Path dockerFilePath = dockerfileToExecutionRoot.getKey().resolve(resource);
- if (!Files.exists(dockerFilePath)) {
- throw new IllegalStateException(
- "Unable to find Dockerfile " + resource + " in "
- + dockerfileToExecutionRoot.getKey().toAbsolutePath());
- }
- return new DockerfileDetectionResult(dockerFilePath, dockerfileToExecutionRoot.getValue());
- }
-
- private static Map.Entry findDockerfileRoot(Path outputDirectory) {
- Map.Entry mainSourcesRoot = findMainSourcesRoot(outputDirectory);
- if (mainSourcesRoot == null) {
- return null;
- }
- Path dockerfilesRoot = mainSourcesRoot.getKey().resolve(DOCKER_DIRECTORY_NAME);
- if (!dockerfilesRoot.toFile().exists()) {
- return null;
- }
- return new AbstractMap.SimpleEntry<>(dockerfilesRoot, mainSourcesRoot.getValue());
- }
-
- }
-
- private static class ProvidedDockerfile implements DockerfilePaths {
- private final Path dockerfilePath;
- private final Path dockerExecutionPath;
-
- private ProvidedDockerfile(Path dockerfilePath, Path dockerExecutionPath) {
- this.dockerfilePath = dockerfilePath;
- this.dockerExecutionPath = dockerExecutionPath;
- }
-
- public static ProvidedDockerfile get(Path dockerfilePath, Path outputDirectory) {
- AbstractMap.SimpleEntry mainSourcesRoot = findMainSourcesRoot(outputDirectory);
- if (mainSourcesRoot == null) {
- throw new IllegalStateException("Unable to determine project root");
- }
- Path effectiveDockerfilePath = dockerfilePath.isAbsolute() ? dockerfilePath
- : mainSourcesRoot.getValue().resolve(dockerfilePath);
- if (!effectiveDockerfilePath.toFile().exists()) {
- throw new IllegalArgumentException(
- "Specified Dockerfile path " + effectiveDockerfilePath.toAbsolutePath() + " does not exist");
- }
- return new ProvidedDockerfile(
- effectiveDockerfilePath,
- mainSourcesRoot.getValue());
- }
-
- @Override
- public Path getDockerfilePath() {
- return dockerfilePath;
- }
-
- @Override
- public Path getDockerExecutionPath() {
- return dockerExecutionPath;
- }
- }
-
}
diff --git a/extensions/container-image/container-image-docker/pom.xml b/extensions/container-image/container-image-docker/pom.xml
index 9e778d91943f8..b3eab9c5d2909 100644
--- a/extensions/container-image/container-image-docker/pom.xml
+++ b/extensions/container-image/container-image-docker/pom.xml
@@ -17,4 +17,4 @@
runtime
-
\ No newline at end of file
+
diff --git a/extensions/container-image/container-image-docker/runtime/pom.xml b/extensions/container-image/container-image-docker/runtime/pom.xml
index 38b3aeccb159f..8e172cf9a7466 100644
--- a/extensions/container-image/container-image-docker/runtime/pom.xml
+++ b/extensions/container-image/container-image-docker/runtime/pom.xml
@@ -17,7 +17,7 @@
io.quarkus
- quarkus-container-image
+ quarkus-container-image-docker-common
@@ -49,4 +49,4 @@
-
\ No newline at end of file
+
diff --git a/extensions/container-image/container-image-docker/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/container-image/container-image-docker/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 163f70c3e2511..c7737a60750eb 100644
--- a/extensions/container-image/container-image-docker/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/container-image/container-image-docker/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -8,4 +8,6 @@ metadata:
- "image"
categories:
- "cloud"
- status: "preview"
\ No newline at end of file
+ status: "preview"
+ config:
+ - "quarkus.docker."
\ No newline at end of file
diff --git a/extensions/container-image/container-image-podman/deployment/pom.xml b/extensions/container-image/container-image-podman/deployment/pom.xml
new file mode 100644
index 0000000000000..3db78bfc5e407
--- /dev/null
+++ b/extensions/container-image/container-image-podman/deployment/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-container-image-podman-parent
+ 999-SNAPSHOT
+
+
+ quarkus-container-image-podman-deployment
+ Quarkus - Container Image - Podman - Deployment
+
+
+
+ io.quarkus
+ quarkus-container-image-podman
+
+
+ io.quarkus
+ quarkus-container-image-docker-common-deployment
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
+
diff --git a/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanBuild.java b/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanBuild.java
new file mode 100644
index 0000000000000..7e48b7fbbc493
--- /dev/null
+++ b/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanBuild.java
@@ -0,0 +1,20 @@
+package io.quarkus.container.image.podman.deployment;
+
+import java.util.function.BooleanSupplier;
+
+import io.quarkus.container.image.deployment.ContainerImageConfig;
+
+public class PodmanBuild implements BooleanSupplier {
+ private final ContainerImageConfig containerImageConfig;
+
+ public PodmanBuild(ContainerImageConfig containerImageConfig) {
+ this.containerImageConfig = containerImageConfig;
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ return containerImageConfig.builder
+ .map(b -> b.equals(PodmanProcessor.PODMAN_CONTAINER_IMAGE_NAME))
+ .orElse(true);
+ }
+}
diff --git a/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanConfig.java b/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanConfig.java
new file mode 100644
index 0000000000000..c49c12715c78e
--- /dev/null
+++ b/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanConfig.java
@@ -0,0 +1,19 @@
+package io.quarkus.container.image.podman.deployment;
+
+import java.util.List;
+import java.util.Optional;
+
+import io.quarkus.container.image.docker.common.deployment.CommonConfig;
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+
+@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
+@ConfigMapping(prefix = "quarkus.podman")
+public interface PodmanConfig extends CommonConfig {
+ /**
+ * Which platform(s) to target during the build. See
+ * https://docs.podman.io/en/latest/markdown/podman-build.1.html#platform-os-arch-variant
+ */
+ Optional> platform();
+}
diff --git a/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanProcessor.java b/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanProcessor.java
new file mode 100644
index 0000000000000..6593bc944f93e
--- /dev/null
+++ b/extensions/container-image/container-image-podman/deployment/src/main/java/io/quarkus/container/image/podman/deployment/PodmanProcessor.java
@@ -0,0 +1,186 @@
+package io.quarkus.container.image.podman.deployment;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.container.image.deployment.ContainerImageConfig;
+import io.quarkus.container.image.docker.common.deployment.CommonProcessor;
+import io.quarkus.container.spi.AvailableContainerImageExtensionBuildItem;
+import io.quarkus.container.spi.ContainerImageBuildRequestBuildItem;
+import io.quarkus.container.spi.ContainerImageBuilderBuildItem;
+import io.quarkus.container.spi.ContainerImageInfoBuildItem;
+import io.quarkus.container.spi.ContainerImagePushRequestBuildItem;
+import io.quarkus.deployment.IsNormalNotRemoteDev;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.PodmanStatusBuildItem;
+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;
+import io.quarkus.deployment.pkg.builditem.UpxCompressedBuildItem;
+import io.quarkus.deployment.pkg.steps.NativeBuild;
+import io.quarkus.deployment.util.ContainerRuntimeUtil.ContainerRuntime;
+import io.quarkus.deployment.util.ExecUtil;
+
+public class PodmanProcessor extends CommonProcessor {
+ private static final Logger LOG = Logger.getLogger(PodmanProcessor.class);
+ private static final String PODMAN = "podman";
+ static final String PODMAN_CONTAINER_IMAGE_NAME = "podman";
+
+ @Override
+ protected String getProcessorImplementation() {
+ return PODMAN;
+ }
+
+ @BuildStep
+ public AvailableContainerImageExtensionBuildItem availability() {
+ return new AvailableContainerImageExtensionBuildItem(PODMAN);
+ }
+
+ @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, PodmanBuild.class }, onlyIfNot = NativeBuild.class)
+ public void podmanBuildFromJar(PodmanConfig podmanConfig,
+ PodmanStatusBuildItem podmanStatusBuildItem,
+ ContainerImageConfig containerImageConfig,
+ OutputTargetBuildItem out,
+ ContainerImageInfoBuildItem containerImageInfo,
+ @SuppressWarnings("unused") CompiledJavaVersionBuildItem compiledJavaVersion,
+ Optional buildRequest,
+ Optional pushRequest,
+ @SuppressWarnings("unused") Optional appCDSResult, // ensure podman build will be performed after AppCDS creation
+ BuildProducer artifactResultProducer,
+ BuildProducer containerImageBuilder,
+ PackageConfig packageConfig,
+ @SuppressWarnings("unused") JarBuildItem jar) {
+
+ buildFromJar(podmanConfig, podmanStatusBuildItem, containerImageConfig, out, containerImageInfo, buildRequest,
+ pushRequest, artifactResultProducer, containerImageBuilder, packageConfig, ContainerRuntime.PODMAN);
+ }
+
+ @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, NativeBuild.class, PodmanBuild.class })
+ public void podmanBuildFromNativeImage(PodmanConfig podmanConfig,
+ PodmanStatusBuildItem podmanStatusBuildItem,
+ ContainerImageConfig containerImageConfig,
+ ContainerImageInfoBuildItem containerImage,
+ Optional buildRequest,
+ Optional pushRequest,
+ OutputTargetBuildItem out,
+ @SuppressWarnings("unused") Optional upxCompressed, // used to ensure that we work with the compressed native binary if compression was enabled
+ BuildProducer artifactResultProducer,
+ BuildProducer containerImageBuilder,
+ PackageConfig packageConfig,
+ // used to ensure that the native binary has been built
+ NativeImageBuildItem nativeImage) {
+
+ buildFromNativeImage(podmanConfig, podmanStatusBuildItem, containerImageConfig, containerImage,
+ buildRequest, pushRequest, out, artifactResultProducer, containerImageBuilder, packageConfig, nativeImage,
+ ContainerRuntime.PODMAN);
+ }
+
+ @Override
+ protected String createContainerImage(ContainerImageConfig containerImageConfig,
+ PodmanConfig podmanConfig,
+ ContainerImageInfoBuildItem containerImageInfo,
+ OutputTargetBuildItem out,
+ DockerfilePaths dockerfilePaths,
+ boolean buildContainerImage,
+ boolean pushContainerImage,
+ PackageConfig packageConfig,
+ String executableName) {
+
+ // Following https://developers.redhat.com/articles/2023/11/03/how-build-multi-architecture-container-images#testing_multi_architecture_containers
+ // If we are building more than 1 platform, then the build needs to happen in 2 separate steps
+ // 1) podman manifest create
+ // 2) podman build --platform --manifest
+
+ // Then when pushing you push the manifest, not the image:
+ // podman manifest push
+
+ var isMultiPlatformBuild = isMultiPlatformBuild(podmanConfig);
+ var image = containerImageInfo.getImage();
+
+ if (isMultiPlatformBuild) {
+ createManifest(image, executableName);
+ }
+
+ if (buildContainerImage) {
+ var podmanBuildArgs = getPodmanBuildArgs(image, dockerfilePaths, containerImageConfig, podmanConfig,
+ isMultiPlatformBuild);
+ buildImage(containerImageInfo, out, executableName, podmanBuildArgs, true);
+ }
+
+ if (pushContainerImage) {
+ loginToRegistryIfNeeded(containerImageConfig, containerImageInfo, executableName);
+
+ if (isMultiPlatformBuild) {
+ pushManifests(containerImageInfo, executableName);
+ } else {
+ pushImages(containerImageInfo, executableName);
+ }
+ }
+
+ return image;
+ }
+
+ private String[] getPodmanBuildArgs(String image,
+ DockerfilePaths dockerfilePaths,
+ ContainerImageConfig containerImageConfig,
+ PodmanConfig podmanConfig,
+ boolean isMultiPlatformBuild) {
+
+ var podmanBuildArgs = getContainerCommonBuildArgs(image, dockerfilePaths, containerImageConfig, podmanConfig,
+ !isMultiPlatformBuild);
+
+ podmanConfig.platform()
+ .filter(platform -> !platform.isEmpty())
+ .ifPresent(platform -> {
+ podmanBuildArgs.addAll(List.of("--platform", String.join(",", platform)));
+
+ if (isMultiPlatformBuild) {
+ podmanBuildArgs.addAll(List.of("--manifest", image));
+ }
+ });
+
+ podmanBuildArgs.add(dockerfilePaths.dockerExecutionPath().toAbsolutePath().toString());
+ return podmanBuildArgs.toArray(String[]::new);
+ }
+
+ private void pushManifests(ContainerImageInfoBuildItem containerImageInfo, String executableName) {
+ Stream.concat(containerImageInfo.getAdditionalImageTags().stream(), Stream.of(containerImageInfo.getImage()))
+ .forEach(manifestToPush -> pushManifest(manifestToPush, executableName));
+ }
+
+ private void pushManifest(String image, String executableName) {
+ String[] pushArgs = { "manifest", "push", image };
+ var pushSuccessful = ExecUtil.exec(executableName, pushArgs);
+
+ if (!pushSuccessful) {
+ throw containerRuntimeException(executableName, pushArgs);
+ }
+
+ LOG.infof("Successfully pushed podman manifest %s", image);
+ }
+
+ private void createManifest(String image, String executableName) {
+ var manifestCreateArgs = new String[] { "manifest", "create", image };
+
+ LOG.infof("Running '%s %s'", executableName, String.join(" ", manifestCreateArgs));
+ var createManifestSuccessful = ExecUtil.exec(executableName, manifestCreateArgs);
+
+ if (!createManifestSuccessful) {
+ throw containerRuntimeException(executableName, manifestCreateArgs);
+ }
+ }
+
+ private boolean isMultiPlatformBuild(PodmanConfig podmanConfig) {
+ return podmanConfig.platform()
+ .map(List::size)
+ .orElse(0) >= 2;
+ }
+}
diff --git a/extensions/container-image/container-image-podman/pom.xml b/extensions/container-image/container-image-podman/pom.xml
new file mode 100644
index 0000000000000..57386b321fa80
--- /dev/null
+++ b/extensions/container-image/container-image-podman/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ quarkus-container-image-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-container-image-podman-parent
+ Quarkus - Container Image - Podman - Parent
+ pom
+
+ deployment
+ runtime
+
+
+
diff --git a/extensions/container-image/container-image-podman/runtime/pom.xml b/extensions/container-image/container-image-podman/runtime/pom.xml
new file mode 100644
index 0000000000000..1bdbbdf23b8ac
--- /dev/null
+++ b/extensions/container-image/container-image-podman/runtime/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-container-image-podman-parent
+ 999-SNAPSHOT
+
+
+ quarkus-container-image-podman
+ Quarkus - Container Image - Podman
+ Build container images of your application using Podman
+
+
+
+ io.quarkus
+ quarkus-container-image-docker-common
+
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+
+
+
+ io.quarkus.container.image.podman.deployment.PodmanBuild
+ io.quarkus.container.image.podman
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/container-image/container-image-podman/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/container-image/container-image-podman/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..563a067358c16
--- /dev/null
+++ b/extensions/container-image/container-image-podman/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,14 @@
+---
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+name: "Container Image Podman"
+metadata:
+ keywords:
+ - "podman"
+ - "container"
+ - "image"
+ guide: "https://quarkus.io/guides/container-image"
+ categories:
+ - "cloud"
+ status: "preview"
+ config:
+ - "quarkus.podman."
\ No newline at end of file
diff --git a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageCapabilitiesUtil.java b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageCapabilitiesUtil.java
index 5a4f027a0a3a3..53b344b27d956 100644
--- a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageCapabilitiesUtil.java
+++ b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageCapabilitiesUtil.java
@@ -13,12 +13,14 @@ public final class ContainerImageCapabilitiesUtil {
public final static Map CAPABILITY_TO_EXTENSION_NAME = Map.of(
Capability.CONTAINER_IMAGE_JIB, "quarkus-container-image-jib",
Capability.CONTAINER_IMAGE_DOCKER, "quarkus-container-image-docker",
+ Capability.CONTAINER_IMAGE_PODMAN, "quarkus-container-image-podman",
Capability.CONTAINER_IMAGE_OPENSHIFT, "quarkus-container-image-openshift",
Capability.CONTAINER_IMAGE_BUILDPACK, "quarkus-container-image-buildpack");
private final static Map CAPABILITY_TO_BUILDER_NAME = Map.of(
Capability.CONTAINER_IMAGE_JIB, "jib",
Capability.CONTAINER_IMAGE_DOCKER, "docker",
+ Capability.CONTAINER_IMAGE_PODMAN, "podman",
Capability.CONTAINER_IMAGE_OPENSHIFT, "openshift",
Capability.CONTAINER_IMAGE_BUILDPACK, "buildpack");
diff --git a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
index e99b79f53624b..b5ad69727968c 100644
--- a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
+++ b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
@@ -91,7 +91,7 @@ public class ContainerImageConfig {
public Optional push;
/**
- * The name of the container image extension to use (e.g. docker, jib, s2i).
+ * The name of the container image extension to use (e.g. docker, podman, jib, s2i).
* The option will be used in case multiple extensions are present.
*/
@ConfigItem
diff --git a/extensions/container-image/pom.xml b/extensions/container-image/pom.xml
index 4e84fd15310c4..bb2474fd039f7 100644
--- a/extensions/container-image/pom.xml
+++ b/extensions/container-image/pom.xml
@@ -20,7 +20,9 @@
spi
util
container-image-buildpack
+ container-image-docker-common
container-image-docker
+ container-image-podman
container-image-jib
container-image-openshift
diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java
index a5ab0f15c4e3e..bde32b21a071a 100644
--- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java
+++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java
@@ -249,8 +249,7 @@ private RunningDevService startDevDb(
}
if (devDbProvider.isDockerRequired() && !dockerStatusBuildItem.isDockerAvailable()) {
- String message = "Please configure the datasource URL for "
- + dataSourcePrettyName
+ String message = "Please configure the datasource URL for " + dataSourcePrettyName
+ " or ensure the Docker daemon is up and running.";
if (launchMode == LaunchMode.TEST) {
throw new IllegalStateException(message);