Ballerina meets GraalVM in the Cloud: A Winning Formula for Cloud-Native Applications

This article is written using Ballerina Swan Lake Update 7(2201.7.0). In this article, we will explore how to containerize a GraalVM native executable for a ballerina application.

Welcome back to our series exploring the powerful synergy between Ballerina and GraalVM! In our previous article, ‘Ballerina meets GraalVM: From code to native executable’, we delved into the seamless integration of Ballerina and GraalVM, witnessing how Ballerina applications can build GraalVM native executable and achieve improved performance and reduced memory consumption. In this exciting continuation, we take the next step in our journey, exploring how to containerize a Ballerina GraalVM executable. If you haven’t read the previous article, I recommend you do so before continuing with this one. Let’s get started!

We will use the same Conference Service application to build a docker image containing the GraalVM executable. The code for this application can be found in the below link.

We will be looking into the following ways to create the docker image.

  1. Using a custom docker file.
  2. Using the Ballerina Code to Cloud feature.

Using a custom docker file

As we know already, the GraalVM native executable is platform-dependent. If you are a Linux user, then you can build the GraalVM executable locally and pass it to a docker with the simplest slim container. If you are using macOS or Windows to build a docker image containing the GraalVM executable, then you have to build the executable in a docker container.

In this post, I am using a macOS, so I need to build the executable in a docker container that has the GraalVM native image tool. The GraalVM community already has container images with the native-image tool. The images can be found on the GraalVM container page. Since Ballerina Swan Lake Update 7 works with Java11, I have chosen this image: ghcr.io/graalvm/native-image:ol8-java11–22.3.3.

Let’s start by building the application and obtaining the JAR file.

$ bal build

Compiling source
tharmigan/conference_service:1.0.0

Generating executable
target/bin/conference_service.jar

Use the following docker file to build the GraalVM executable in the graalvm/native-image container and run the executable in adebian:stable-slim container.

Build the docker image.

$ docker build . -t ktharmi176/conference-service:1.0.0

Use the following Docker compose file to run the conference_service and the mock country_service in the host network.

Check the image names and run the following command

$ docker compose up

Now, the two services have been started. Test the service using the request.http file.

Using the Ballerina Code to Cloud feature

Default mode

The Code to Cloud feature in Ballerina enables developers to quickly deploy their Ballerina applications to cloud platforms without the need for extensive configuration or manual setup. It aims to reduce the complexity of cloud-native development and streamline the deployment process.

We can simply run the following command to build the GraalVM executable in a docker container.

$ bal build --graalvm --cloud=docker

Compiling source
tharmigan/conference_service:1.0.0


Generating artifacts

Building the native image. This may take a while

[+] Building 331.1s (13/13) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 439B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for gcr.io/distroless/base:latest 3.0s
=> [internal] load metadata for ghcr.io/graalvm/native-image:ol8-java11-22.3.3 3.7s
=> [build 1/4] FROM ghcr.io/graalvm/native-image:ol8-java11-22.3.3@sha256:c0b4d9c31013d4fd91c4dec25f8772602e851ee67b8510d21bfdab532da4c17c 0.0s
=> [stage-1 1/3] FROM gcr.io/distroless/base@sha256:73deaaf6a207c1a33850257ba74e0f196bc418636cada9943a03d7abea980d6d 0.0s
=> [internal] load build context 0.4s
=> => transferring context: 42.65MB 0.4s
=> CACHED [stage-1 2/3] WORKDIR /home/ballerina 0.0s
=> CACHED [build 2/4] WORKDIR /app/build 0.0s
=> [build 3/4] COPY conference_service.jar . 0.1s
=> [build 4/4] RUN native-image -jar conference_service.jar -H:Name=conference_service --no-fallback -H:+StaticExecutableWithDynamicLibC 326.3s
=> [stage-1 3/3] COPY --from=build /app/build/conference_service . 0.3s
=> exporting to image 0.3s
=> => exporting layers 0.3s
=> => writing image sha256:4a6e1223a8d5a0446b688b110522bdc796027bfc1bc4fe533c62be649900ee05 0.0s
=> => naming to docker.io/library/conference_service:latest 0.0s

Execute the below command to run the generated Docker image:
docker run -d conference_service:latest

The auto-generated docker file can be found in the following path: target/docker/conference_service

Note: By default, Ballerina builds a mostly-static native-image and pack it in a distorless container. For more information on GraalVM mostly-static image, see Static and Mostly Static Images.

Now, let’s run docker-compose after changing the image name of the conference_service.

Test the service using the request.http file.

Configure mode

The Code to Cloud feature supports overriding the default mode where we configure the following:

  • The GraalVM build-image
  • The native-image build command
  • The base image for the deployment

This can be achieved by providing the configurations via Cloud.toml file. The following shows an example to provide the same configurations used for the custom docker file.

Run the following command to build the docker image with the above configurations.

$ bal build --graalvm --cloud=docker


Compiling source
tharmigan/conference_service:1.0.0


Generating artifacts

Building the native image. This may take a while

[+] Building 310.0s (14/14) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 372B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/debian:stable-slim 3.9s
=> [internal] load metadata for ghcr.io/graalvm/native-image:ol8-java11-22.3.3 2.5s
=> [auth] library/debian:pull token for registry-1.docker.io 0.0s
=> [build 1/4] FROM ghcr.io/graalvm/native-image:ol8-java11-22.3.3@sha256:c0b4d9c31013d4fd91c4dec25f8772602e851ee67b8510d21bfdab532da4 0.0s
=> [stage-1 1/3] FROM docker.io/library/debian:stable-slim@sha256:6fe30b9cb71d604a872557be086c74f95451fecd939d72afe3cffca3d9e60607 0.0s
=> [internal] load build context 0.3s
=> => transferring context: 42.65MB 0.3s
=> CACHED [stage-1 2/3] WORKDIR /home/ballerina 0.0s
=> CACHED [build 2/4] WORKDIR /app/build 0.0s
=> [build 3/4] COPY conference_service.jar . 0.1s
=> [build 4/4] RUN native-image -jar conference_service.jar --no-fallback 305.1s
=> [stage-1 3/3] COPY --from=build /app/build/conference_service . 0.2s
=> exporting to image 0.3s
=> => exporting layers 0.3s
=> => writing image sha256:1f5b5b30653a48a6d27258f785d93a1654dde25d2e70899e14f2b61996e01996 0.0s
=> => naming to docker.io/ktharmi176/conference-service:1.0.0 0.0s

Execute the below command to run the generated Docker image:
docker run -d ktharmi176/conference-service:1.0.0

The auto-generated docker file with the above configurations will look like this.

This is the same as the one we wrote manually. Run docker-compose and check the functionality using the request.http file.

In conclusion, we have built a GraalVM executable for a Ballerina application and containerized it in Docker. GraalVM and Ballerina with the Code to Cloud feature simplify the experience of developing and deploying the Ballerina GraalVM native image in the cloud. It also enables the use of cloud-native technologies with GraalVM easily without in-depth knowledge. This is a winning formula to build enterprise-grade cloud-native applications 🚀

--

--