Table of Contents

Containerise a Native Executable and Run in a Docker Container

Docker containers provide the flexibility of development environments to match a production environment, to help isolate your application, and to minimize overhead. For self-contained executables, generated with GraalVM Native Image, containers are an obvious deployment scenario.

To support container-based development, there are several GraalVM container images available, depending on the platform, the architecture, the Java version, and the edition:

This guide shows how to containerise a Java application with Docker on macOS. You will use ghcr.io/graalvm/jdk:ol8-java17 which is a size compact GraalVM Community container image with the GraalVM JDK pre-installed. The Dockerfile will be provided.

Prerequisites

Note on a Sample Application

For the demo you will use the Spring Boot 3 Native Image Microservice example.

  1. Clone the GraalVM Demos repository and enter the application directory:

     git clone https://github.com/graalvm/graalvm-demos
     cd spring-native-image
    
  2. Build a native executable and run the application:

     mvn -Pnative native:compile
    

    The -Pnative profile is used to turn on building a native executable with Maven.

    This will create a binary executable target/benchmark-jibber. Start it to see the application running:

     ./target/benchmark-jibber &
     curl http://localhost:8080/jibber
     fg
    

Now that you have a native executable version of the sample application (target/jibber) and seen it working, you can proceed to the next steps.

Containerise a Native Executable

The output of a native executable is platform-dependent. If you use a Mac or Windows, to build a Docker image containing your native executable, you build a native executable within a Docker container - so you need a container with a JDK distribution. If you are a Linux user, you can just pass a native executable to Docker and use the simplest slim or distroless container, depending on static libraries your application is linked against. For example:

FROM gcr.io/distroless/base
ARG APP_FILE
EXPOSE 8080
COPY target/${APP_FILE} app 
ENTRYPOINT ["/jibber"]

For user’s convenience, Dockerfiles are provided with the sample application.

  1. From application root folder, run this command to create a native executable within a container and then build a Docker image containing that native executable:

     docker build -f Dockerfiles/Dockerfile \
                 --build-arg APP_FILE=./target/jibber \
                 -t localhost/jibber:native.01 .
    

    It will take several minutes to set up Maven in the container and do rest of the job.

  2. Query Docker to look at your newly built image:
     docker images | head -n2
    

    You should see a new image listed.

  3. Run the image as follows:

     docker run --rm --name native -d -p 8080:8080 localhost/jibber:native.01 
    
  4. Then call the endpoint using the curl command in the same console window:

     curl http://localhost:8080/jibber
    

    You should receive a nonsense verse in the style of the poem Jabberwocky.

You can take a look at how long the application took to startup by looking at the logs:

docker logs <CONTAINER ID>

You can also query Docker to get the size of the produced container:

docker images localhost/jibber:native.01

The difference will be more visible if you build a Docker image of the same Spring Boot application containing a JAR file instead of a native executable, and compare images startup times and file sizes.

On Linux, you can shrink your container size even more. With GraalVM Native Image you have the ability to build a statically linked native executable by packaging the native executable directly into an empty Docker image, also known as a scratch container. Continue to Build a Static or Mostly-Static Native Executable guide to learn more.