Optimize Quarkus JVM Container Image Size

Optimize Quarkus JVM Container Image Size

After creating a new Quarkus project from the default template, the new project already has four Dockerfiles to build container images using Docker. These Dockerfiles are in the directory src/main/docker. The file Dockerfile.jvm is used to build container images for Quarkus apps running in JVM mode.

Dockerfile.jvm uses RedHat's UBI as the base image. The actual base image is ubi8/ubi-minimal. Based on the base image, java-11-openjdk-headless is installed using microdnf.

The result container image is quite big. The sample app used for testing is a simple app generated from code.quarkus.io with extensions resteasy and resteasy-jackson.

Size of the built image is 520MB. We can use dive to check the layers in the image.

From the screen-shot below, we can see that the installation of java-11-openjdk-headless contributes a layer with size 402MB. That's too big.

Quarkus default container image

A better choice is to use OpenJDK Alpine base image. The code below is the Dockerfile that uses Eclipse Temurin JRE 11 alpine image as the base image.

FROM curlimages/curl AS downloader
RUN curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /tmp/run-java.sh

# FROM eclipse-temurin:11-jdk-alpine
FROM eclipse-temurin:11-jre-alpine

RUN mkdir /deployments \
    && chown 1001 /deployments \
    && chmod "g+rwX" /deployments \
    && chown 1001:root /deployments

COPY --from=downloader /tmp/run-java.sh /deployments/run-java.sh

RUN chown 1001 /deployments/run-java.sh \
    && chmod 540 /deployments/run-java.sh

# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
ENV JAVA_OPTIONS="-Dquarkus.http.host= -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=1001 target/quarkus-app/*.jar /deployments/
COPY --chown=1001 target/quarkus-app/app/ /deployments/app/
COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/

USER 1001

ENTRYPOINT [ "/deployments/run-java.sh" ]

This Dockerfile is named Dockerfile.jvm-alpine. We can update the property quarkus.docker.dockerfile-jvm-path with path of this new Dockerfile.

$ quarkus build -Dquarkus.docker.dockerfile-jvm-path=src/main/docker/Dockerfile.jvm-alpine

Below is the screen-shot of the new image. As you can see, the result image size is only 149MB.

Quarkus alpine JRE container image

We can also use JDK base image eclipse-temurin:11-jdk-alpine instead of JRE. The result image size is 342MB.

Quarkus alpine JDK container image

The table below shows the differences of container image sizes.

Base ImageSize (MB)Difference
UBI minimal520N/A
Alpine JRE14928.65%
Alpine JDK34265.77%

As you can see, using Alpine JRE/JDK base image can save a lot of spaces.

© 2022 VividCode