Shorts

How to containerize native images

Mar 10, 2023
Dmitry Chuyko
4.6

Native Image technology is gaining traction among developers whose primary goal is to accelerate startup time of applications. In this article, we will learn how to containerize native images for further deployment in the Cloud. We will use Liberica Native Image Kit (NIK) as a native-image compiler and Alpaquita Stream as a base image.

Create an application

Create a folder for your demo project. Then go to the project folder and build a simple Java application in the console:

cat >./Demo.java <<EOL
public class Demo {
    public static void main(String[] args) {
        System.out.println("Hello from Native Image!");
    }
}
EOL

Pull a Docker image

Liberica NIK is a GraalVM-based utility for native image generation. It is used as the default native-image compiler with Cloud Native Buildpacks and recommended by the Spring team as a Native Build tool. Liberica NIK is based on the latest patch versions of Liberica JDK (11 or 17) and GraalVM (21 or 22).

BellSoft provides a variety of images with Liberica NIK hosted on Docker Hub. For instance, if you want Liberica NIK 21 for Java 11 and musl libc, pull the following image:

$ docker container run --rm -it bellsoft/liberica-native-image-kit-container:jdk-11-nik-21.3.3-stream-musl

You can skip this step, but pulling an image is recommended if you don’t want to do that every time you repeat the build process.

Write a Dockerfile

we are going to build a native image straight in a container, which is useful when the development and deployment architectures are different. It is also helpful if you want to build a musl-based image, which takes up less memory than a glibc-based one.

Firstly, We need to write a Dockerfile to generate a Docker image container. Put the following file into the application folder:

FROM bellsoft/liberica-native-image-kit-container:jdk-11-nik-21.3.3-stream-musl
WORKDIR /home/myapp
COPY Demo.java /home/myapp/
RUN javac Demo.java
RUN native-image Demo
FROM bellsoft/alpaquita-linux-base:stream-musl-230404
WORKDIR /home/myapp
COPY --from=0 /home/myapp/demo .
CMD [“./demo”]

Where we

  1. Specify the base image for Native Image generation;
  2. Point to the directory where the image will execute inside Docker;
  3. Copy the program to the directory;
  4. Run the javac compiler to create the bytecode of our app;
  5. Run the native-image tool to build a native image;
  6. Create another image with Alpaquita Linux base image (the native image doesn’t need a JVM to execute);
  7. Specify the executable directory;
  8. Copy the app into the new image;
  9. Run the program inside the container. 

Build a native image container

We recommend closing the browser, IDE, and other programs consuming a lot of memory before building a container. To generate a native image and containerize it, run

docker build .

Check that the image was create with the following command:

docker images
REPOSITORY     TAG            IMAGE ID       CREATED              SIZE
<none>         <none>         2921e3483bb2   21 seconds ago       18.4MB

Tag the newly created image:

docker tag 2921e3483bb2 nik-example

Now you can run the image with

docker run -it --rm 2921e3483bb2
Hello from Native Image!

Conclusion

Native image containerization is as simple as creating Docker container images of standard Java apps. Much trickier is to migrate a Java application to Native Image.

We used a simple program that didn’t require any manual configuration. But dynamic Java features (Reflection, JNI, Serialization, etc.) are not supported by GraalVM, so you have to make the native-image tool aware of them.

If you are a Spring developer and want to benefit from the build-in support for GraalVM Native Image in Spring Boot, check out our guides on specifics and common issues of Spring Boot Native Image development.

Subcribe to our newsletter

figure

Read the industry news, receive solutions to your problems, and find the ways to save money.

Further reading