DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Comparing the Efficiency of a Spring Boot Project to a Go Project
  • Using KRaft Kafka for Development and Kubernetes Deployment
  • Setting Up Local Kafka Container for Spring Boot Application
  • Setting Up a Local Development Environment With IntelliJ, DevContainers, and Amazon Linux 2023

Trending

  • Zero Trust for AWS NLBs: Why It Matters and How to Do It
  • How to Convert XLS to XLSX in Java
  • Why Documentation Matters More Than You Think
  • Optimize Deployment Pipelines for Speed, Security and Seamless Automation
  1. DZone
  2. Coding
  3. Java
  4. How To Create a GraalVM Docker Image

How To Create a GraalVM Docker Image

Learn how to create a Docker image for your GraalVM native image and find out it is a bit trickier than what you are used to when creating Docker images.

By 
Gunter Rotsaert user avatar
Gunter Rotsaert
DZone Core CORE ·
Apr. 05, 23 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
10.5K Views

Join the DZone community and get the full member experience.

Join For Free

In this post, you will learn how to create a Docker image for your GraalVM native image. By means of some hands-on experiments, you will learn that it is a bit trickier than what you are used to when creating Docker images. Enjoy!

Introduction

In a previous post, you learned how to create a GraalVM native image for a Spring Boot 3 application. Nowadays, applications are often distributed as Docker images, so it is interesting to verify how this is done for a GraalVM native image. A GraalVM native image does not need a JVM, so can you use a more minimalistic Docker base image for example? You will execute some experiments during this blog and will learn by doing.

The sources used in this blog are available on GitHub.

The information provided in the GraalVM documentation is a good starting point for learning. It is good reference material when reading this blog.

As an example application, you will use the Spring Boot application from the previous post. The application contains one basic RestController which just returns a hello message. The RestController also includes some code in order to execute tests in combination with Reflection, but this part was added for the previous post.

Java
 
@RestController
public class HelloController {
 
    @RequestMapping("/hello")
    public String hello() {
        // return "Hello GraalVM!"
        String helloMessage = "Default message";
        try {
            Class<?> helloClass = Class.forName("com.mydeveloperplanet.mygraalvmplanet.Hello");
            Method helloSetMessageMethod = helloClass.getMethod("setMessage", String.class);
            Method helloGetMessageMethod = helloClass.getMethod("getMessage");
            Object helloInstance = helloClass.getConstructor().newInstance();
            helloSetMessageMethod.invoke(helloInstance, "Hello GraalVM!");
            helloMessage = (String) helloGetMessageMethod.invoke(helloInstance);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return helloMessage;
    }
}


Build the application:

Shell
 
$ mvn clean verify


Run the application from the root of the repository:

Shell
 
$ java -jar target/mygraalvmplanet-0.0.1-SNAPSHOT.jar


Test the endpoint:

Shell
 
$ curl http://localhost:8080/hello
Hello GraalVM!


You are now ready for Dockerizing this application!

Prerequisites

Prerequisites for this blog are:

  • Basic Linux knowledge, Ubuntu 22.04 is used during this post
  • Basic Java and Spring Boot knowledge
  • Basic GraalVM knowledge
  • Basic Docker knowledge
  • Basic SDKMAN knowledge

Create Docker Image for Spring Boot Application

In this section, you will create a Dockerfile for the Spring Boot application. This is a very basic Dockerfile and is not to be used in production code. See previous posts "Docker Best Practices" and "Spring Boot Docker Best Practices" for tips and tricks for production-ready Docker images. The Dockerfile you will be using is the following:

Dockerfile
 
FROM eclipse-temurin:17.0.5_8-jre-alpine
COPY target/mygraalvmplanet-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]


You use a Docker base image containing a Java JRE, copy the JAR file into the image, and, in the end, you run the JAR file.

Build the Docker image:

Shell
 
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT


Verify the size of the image. It is 188MB in size.

Shell
 
$ docker images
REPOSITORY                                                             TAG                    IMAGE ID       CREATED          SIZE
mydeveloperplanet/mygraalvmplanet                                      0.0.1-SNAPSHOT         be12e1deda89   33 seconds ago   188MB


Run the Docker image:

Shell
 
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
...
2023-02-26T09:20:48.033Z  INFO 1 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Started MyGraalVmPlanetApplication in 2.389 seconds (process running for 2.981)


As you can see, the application started in about 2 seconds.

Test the endpoint again. First, find the IP Address of your Docker container. In the output below, the IP Address is 172.17.0.2, but it will probably be something else on your machine.

Shell
 
$ docker inspect mygraalvmplanet | grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.2",
                    "IPAddress": "172.17.0.2",


Invoke the endpoint with the IP Address and verify that it works.

Shell
 
$ curl http://172.17.0.2:8080/hello
Hello GraalVM!


In order to continue, stop the container, remove it, and also remove the image. Do this after each experiment. This way, you can be sure that you start from a clean situation each time.

Shell
 
$ docker rm mygraalvmplanet
$ docker rmi mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT


Create Docker Image for GraalVM Native Image

Let’s do the same for the GraalVM native image. First, switch to using GraalVM.

Shell
 
$ sdk use java 22.3.r17-nik


Create the native image:

Shell
 
$ mvn -Pnative native:compile


Create a similar Dockerfile (Dockerfile-native-image). This time, you use an Alpine Docker base image without a JVM. You do not need a JVM for running a GraalVM native image as it is an executable and not a JAR file.

Dockerfile
 
FROM alpine:3.17.1
COPY target/mygraalvmplanet mygraalvmplanet
ENTRYPOINT ["/mygraalvmplanet"]


Build the Docker image, this time with an extra --file argument because the file name deviates from the default.

Shell
 
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT --file Dockerfile-native-image


Verify the size of the Docker image. It is now only 76.5MB instead of the 177MB earlier.

Shell
 
$ docker images
REPOSITORY                                                             TAG                    IMAGE ID       CREATED          SIZE
mydeveloperplanet/mygraalvmplanet                                      0.0.1-SNAPSHOT         4f7c5c6a9b29   25 seconds ago   76.5MB


Run the container and note that it does not start correctly.

Shell
 
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
exec /mygraalvmplanet: no such file or directory


What is wrong here? Why does this not work?

It is a vague error, but the Alpine Linux Docker image uses musl as a standard C library whereas the GraalVM native image is compiled using an Ubuntu Linux distro, which uses glibc.

Let’s change the Docker base image to Ubuntu. The Dockerfile is Dockerfile-native-image-ubuntu:

Dockerfile
 
FROM ubuntu:jammy
COPY target/mygraalvmplanet mygraalvmplanet
ENTRYPOINT ["/mygraalvmplanet"]


Build the Docker image.

Shell
 
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT --file Dockerfile-native-image-ubuntu


Verify the size of the Docker image, it is now 147MB.

Shell
 
$ docker images
REPOSITORY                                                             TAG                    IMAGE ID       CREATED         SIZE
mydeveloperplanet/mygraalvmplanet                                      0.0.1-SNAPSHOT         1fa90b1bfc54   3 hours ago     147MB


Run the container and it starts successfully in less than 200ms.

Shell
 
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
...
2023-02-26T12:48:26.140Z  INFO 1 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Started MyGraalVmPlanetApplication in 0.131 seconds (process running for 0.197)


Create Docker Image Based on Distroless Image

The size of the Docker image build with the Ubuntu base image is 147MB. But, the Ubuntu image does contain a lot of tooling which is not needed. Can we reduce the size of the image by using a distroless image which is very small in size?

Create a Dockerfile Dockerfile-native-image-distroless and use a distroless base image.

Dockerfile
 
FROM gcr.io/distroless/base
COPY target/mygraalvmplanet mygraalvmplanet
ENTRYPOINT ["/mygraalvmplanet"]


Build the Docker image.

Shell
 
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT --file Dockerfile-native-image-distroless


Verify the size of the Docker image, it is now 89.9MB.

Shell
 
$ docker images
REPOSITORY                                                             TAG                    IMAGE ID       CREATED         SIZE
mydeveloperplanet/mygraalvmplanet                                      0.0.1-SNAPSHOT         6fd4d44fb622   9 seconds ago   89.9MB


Run the container and see that it is failing to start. It appears that several necessary libraries are not present in the distroless image.

Shell
 
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
/mygraalvmplanet: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory


When Googling this error message, you will find threads that mention copying the required libraries from other images (e.g., the Ubuntu image), but you will encounter a next error and a next error. This is a difficult path to follow and costs some time. See, for example, this thread.

A solution for using distroless images can be found here.

Create Docker Image Based on Oracle Linux

Another approach for creating Docker images is the one that can be found on the GraalVM GitHub page. Build the native image in a Docker container and use a multistage build to build the target image.

The Dockerfile being used is copied from here and can be found in the repository as Dockerfile-oracle-linux.

Create a new file Dockerfile-native-image-oracle-linux, copy the contents of Dockerfile-oracle-linux into it, and change the following:

  • Update the Maven SHA and DOWNLOAD_URL.
  • Change L36 in order to compile the native image as you used to do: mvn -Pnative native:compile
  • Change L44 and L45 in order to copy and use the mygraalvmplanet native image.

The resulting Dockerfile is the following:

Dockerfile
 
FROM ghcr.io/graalvm/native-image:ol8-java17-22 AS builder
 
# Install tar and gzip to extract the Maven binaries
RUN microdnf update \
 && microdnf install --nodocs \
    tar \
    gzip \
 && microdnf clean all \
 && rm -rf /var/cache/yum
 
# Install Maven
# Source:
# 1) https://github.com/carlossg/docker-maven/blob/925e49a1d0986070208e3c06a11c41f8f2cada82/openjdk-17/Dockerfile
# 2) https://maven.apache.org/download.cgi
ARG USER_HOME_DIR="/root"
ARG SHA=1ea149f4e48bc7b34d554aef86f948eca7df4e7874e30caf449f3708e4f8487c71a5e5c072a05f17c60406176ebeeaf56b5f895090c7346f8238e2da06cf6ecd
ARG MAVEN_DOWNLOAD_URL=https://dlcdn.apache.org/maven/maven-3/3.9.0/binaries/apache-maven-3.9.0-bin.tar.gz
 
RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
  && curl -fsSL -o /tmp/apache-maven.tar.gz ${MAVEN_DOWNLOAD_URL} \
  && echo "${SHA}  /tmp/apache-maven.tar.gz" | sha512sum -c - \
  && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
  && rm -f /tmp/apache-maven.tar.gz \
  && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn
 
ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2"
 
# Set the working directory to /home/app
WORKDIR /build
 
# Copy the source code into the image for building
COPY . /build
 
# Build
RUN mvn -Pnative native:compile
 
# The deployment Image
FROM docker.io/oraclelinux:8-slim
 
EXPOSE 8080
 
# Copy the native executable into the containers
COPY --from=builder /build/target/mygraalvmplanet .
ENTRYPOINT ["/mygraalvmplanet"]


Build the Docker image. Relax, this will take quite some time.

Shell
 
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT -f Dockerfile-native-image-oracle-linux


This image size is 177MB.

Shell
 
$ docker images
REPOSITORY                                                             TAG                    IMAGE ID       CREATED         SIZE
mydeveloperplanet/mygraalvmplanet                                      0.0.1-SNAPSHOT         57e0fda006f0   9 seconds ago   177MB


Run the container and it starts in 55ms.

Shell
 
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
...
2023-02-26T13:13:50.188Z  INFO 1 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Started MyGraalVmPlanetApplication in 0.055 seconds (process running for 0.061)


So, this works just fine. This is the way to go when creating Docker images for your GraalVM native image:

  • Prepare a Docker image based on your target base image;
  • Install the necessary tooling, in the case of this application, GraalVM and Maven;
  • Use a multistage Docker build in order to create the target image.

Conclusion

Creating a Docker image for your GraalVM native image is possible, but you need to be aware of what you are doing. Using a multistage build is the best option. Dependent on whether you need to shrink the size of the image by using a distroless image, you need to prepare the image to build the native image yourself.

GraalVM Docker (software) Java Development Kit Spring Boot Linux (operating system)

Published at DZone with permission of Gunter Rotsaert, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Comparing the Efficiency of a Spring Boot Project to a Go Project
  • Using KRaft Kafka for Development and Kubernetes Deployment
  • Setting Up Local Kafka Container for Spring Boot Application
  • Setting Up a Local Development Environment With IntelliJ, DevContainers, and Amazon Linux 2023

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!