Running Spring Boot GraalVM Native Images with Docker & Heroku

No Comments

Combining Spring Boot with the benefits of GraalVM Native Images is really cool. But how about doing all that magic inside a Docker container also? How about running those native apps on cloud infrastructures like Heroku?

Spring Boot & GraalVM – blog series

Part 1: Running Spring Boot apps as GraalVM Native Images
Part 2: Running Spring Boot GraalVM Native Images with Docker & Heroku
Part 3: Simplifying Spring Boot GraalVM Native Image builds with the native-image-maven-plugin

Say ‘works on my machine’ one more time!

Working on the first article about Running Spring Boot apps as GraalVM Native Images I got really stoked about what’s already possible today when we try to use Spring Boot together with GraalVM Native Images. But no matter if I am at the customer’s site or giving lectures to my students at Fachhochschule Erfurt, I really try to avoid this works on my machine dilemma. And so far we’ve only compiled Spring Boot apps into GraalVM Native Images on our local workstation.

Since we’re in 2020, we shouldn’t stop there and instead try to use some sort of container to build and run our apps, right? And we should continuously do that with the help of some Continuous Integration cloud platform. Finally, we need to deploy and run our native apps on some sort of cloud platform!

Run Spring Boot GraalVM Native Image Apps in Docker

Logo sources: Docker logo, Spring Boot logo, Computer logo, GraalVM logo

So first things first – let’s figure out how to compile our Spring Boot apps into GraalVM Native Images using Docker!

Compiling Spring Boot Apps into GraalVM Native Images with Docker

The easiest way to use Docker here is to rely on the official GraalVM Docker image from Oracle. Interestingly, this image lacks both Maven and the native-image GraalVM plugin. So let’s simply add them to the image creating our own Dockerfile. Again all code examples are available in an example project on GitHub.

In the first article of this blog post series we already got used to leveraging SDKMAN to install Maven. As the official GraalVM Docker image from Oracle is based on oraclelinux:7-slim, we need to install unzip and zip first. Both is needed by SDKMAN in order to work properly:

FROM oracle/graalvm-ce:20.0.0-java11
 
# For SDKMAN to work we need unzip & zip
RUN yum install -y unzip zip
 
RUN \
    # Install SDKMAN
    curl -s "https://get.sdkman.io" | bash; \
    source "$HOME/.sdkman/bin/sdkman-init.sh"; \
    # Install Maven
    sdk install maven; \
    # Install GraalVM Native Image
    gu install native-image;
 
RUN source "$HOME/.sdkman/bin/sdkman-init.sh" && mvn --version
 
RUN native-image --version
 
# Always use source sdkman-init.sh before any command, so that we will be able to use 'mvn' command
ENTRYPOINT bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && $0"

We shouldn’t forget to enable the mvn command for a user of our Docker image. Therefore we craft a slightly more interesting ENTRYPOINT that always prefixes commands with source $HOME/.sdkman/bin/sdkman-init.sh. Having defined our Dockerfile, we should build our image with:

docker build . --tag=graalvm-ce:20.0.0-java11-mvn-native-image

After the build is finished, we are able to launch our GraalVM Native Image compilation inside a Docker container. But wait, the following command inherits a second Docker volume definition with --volume "$HOME"/.m2:/root/.m2. Why is that? Because I really wanted to avoid downloading all the Spring Maven dependencies over and over again every time we start our Docker container. With this mount we simply use the Maven repository cached on our machine already:

docker run -it --rm \
    --volume $(pwd):/build \
    --workdir /build \
    --volume "$HOME"/.m2:/root/.m2 \
    graalvm-ce:20.0.0-java11-mvn-native-image ./compile.sh

The first volume --volume $(pwd):/build simply mounts our Spring Boot app’s sources including our .compile.sh script for GraalVM Native Image compilation into the Docker container. Running this Docker build, the resulting spring-boot-graal native app should be ready after some minutes of heavy compilation.

Preventing java.lang.OutOfMemoryError errors

When I started experimenting with GraalVM Native Images compilations of Spring Boot apps, I often experienced that the docker run command seemed to take ages to complete. And at the end a java.lang.OutOfMemoryError error was thrown into the log like this:

14:06:34.609 [ForkJoinPool-2-worker-3] DEBUG io.netty.handler.codec.compression.ZlibCodecFactory - -Dio.netty.noJdkZlibEncoder: false
Exception in thread "native-image pid watcher"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "native-image pid watcher"

In this case it is very likely that your Docker engine is not able to use enough memory. On my Mac’s Docker installation the default was only 2.00 GB. As stated in the comments of this stackoverflow q&a, you have to give Docker much more memory since the GraalVM Native Image compilation process is really RAM-intensive. Allotting the Docker engine around 9 to 12 GB of RAM, I was able get my compilation working inside the Docker container:

docker engine memory configuration

If everything goes fine, you should find the natively compiled Spring Boot app as spring-boot-graal inside the /target/native-image directory. So in order to run our app, simply run it with ./target/native-image/spring-boot-graal:

$ ./spring-boot-graal
zsh: exec format error: ./spring-boot-graal

Ooops! It turns out that this doesn’t work! Why? We really need to keep in mind that we are compiling native executables from our Java applications! So they’re absolutely platform-dependent now! And our Docker container’s base image will be mostly different than our host operating system. I guess this is something new for all the Java folks! Since from the beginning we were told that Java is platform-independent thanks to its virtual machine. And this problem becomes really clear to us only at this point, where we started to compile our app inside a Docker container.

The solution to this problem is simple: we don’t only compile our apps inside Docker containers, but also run them inside of them.

Running native Spring Boot apps in Docker

If we want to run our native Spring Boot apps inside a container, the Docker multi-stage build feature comes in handy. Using it, we could do the GraalVM Native Image compilation inside the first container – and only use the resulting native Spring Boot app inside the second container and run it. Therefore we need to extend our Dockerfile slightly:

FROM oracle/graalvm-ce:20.1.0-java11
 
ADD . /build
WORKDIR /build
 
# For SDKMAN to work we need unzip & zip
RUN yum install -y unzip zip
 
RUN \
    # Install SDKMAN
    curl -s "https://get.sdkman.io" | bash; \
    source "$HOME/.sdkman/bin/sdkman-init.sh"; \
    # Install Maven
    sdk install maven; \
    # Install GraalVM Native Image
    gu install native-image;
 
RUN source "$HOME/.sdkman/bin/sdkman-init.sh" && mvn --version
 
RUN native-image --version
 
RUN source "$HOME/.sdkman/bin/sdkman-init.sh" && ./compile.sh
 
 
# We use a Docker multi-stage build here so that we only take the compiled native Spring Boot app from the first build container
FROM oraclelinux:7-slim
 
MAINTAINER Jonas Hecht
 
# Add Spring Boot Native app spring-boot-graal to Container
COPY --from=0 "/build/target/native-image/spring-boot-graal" spring-boot-graal
 
# Fire up our Spring Boot Native app by default
CMD [ "sh", "-c", "./spring-boot-graal" ]

We simply copy the compilation result from the first build container via COPY --from=0 here. Then we define the app’s startup command ./spring-boot-graal as we would do on our machine also. Here it’s just wrapped inside a CMD statement. And as you might notice, we switched to oraclelinux:7-slim as the base image for our second run container. This saves a lot of memory since the resulting image only needs around 180 MB as opposed to nearly 2 GB, which it would need if we stuck to oracle/graalvm-ce:20.1.0-java11.

That’s already all that needs to be prepared here and we’re now able to run our Docker multi-stage build with the following command:

docker build . --tag=spring-boot-graal

This again will take a while – you may grab a coffee. 🙂 The Docker build is successfully finished when you get something like the following output:

[spring-boot-graal:289]   (typeflow): 114,554.33 ms,  6.58 GB
[spring-boot-graal:289]    (objects):  63,145.07 ms,  6.58 GB
[spring-boot-graal:289]   (features):   6,990.75 ms,  6.58 GB
[spring-boot-graal:289]     analysis: 190,400.92 ms,  6.58 GB
[spring-boot-graal:289]     (clinit):   1,970.98 ms,  6.67 GB
[spring-boot-graal:289]     universe:   6,263.93 ms,  6.67 GB
[spring-boot-graal:289]      (parse):  11,824.83 ms,  6.67 GB
[spring-boot-graal:289]     (inline):   7,216.63 ms,  6.73 GB
[spring-boot-graal:289]    (compile):  63,692.52 ms,  6.77 GB
[spring-boot-graal:289]      compile:  86,836.76 ms,  6.77 GB
[spring-boot-graal:289]        image:  10,050.63 ms,  6.77 GB
[spring-boot-graal:289]        write:   1,319.52 ms,  6.77 GB
[spring-boot-graal:289]      [total]: 313,644.65 ms,  6.77 GB
 
real  5m16.447s
user  16m32.096s
sys 1m34.441s
Removing intermediate container 151e1413ec2f
 ---> be671d4f237f
Step 10/13 : FROM oracle/graalvm-ce:20.0.0-java11
 ---> 364d0bb387bd
Step 11/13 : MAINTAINER Jonas Hecht
 ---> Using cache
 ---> 445833938b60
Step 12/13 : COPY --from=0 "/build/target/native-image/spring-boot-graal" spring-boot-graal
 ---> 2d717a0db703
Step 13/13 : CMD [ "sh", "-c", "./spring-boot-graal" ]
 ---> Running in 7fa931991d7e
Removing intermediate container 7fa931991d7e
 ---> a0afe30b3619
Successfully built a0afe30b3619
Successfully tagged spring-boot-graal:latest

With an output like that we could simply run our Spring Boot native app with docker run -p 8080:8080 spring-boot-graal:

$ docker run -p 8080:8080 spring-boot-graal
 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::
 
2020-04-19 09:22:51.547  INFO 1 --- [           main] i.j.s.SpringBootHelloApplication         : Starting SpringBootHelloApplication on 06274db526b0 with PID 1 (/spring-boot-graal started by root in /)
2020-04-19 09:22:51.547  INFO 1 --- [           main] i.j.s.SpringBootHelloApplication         : No active profile set, falling back to default profiles: default
2020-04-19 09:22:51.591  WARN 1 --- [           main] io.netty.channel.DefaultChannelId        : Failed to find the current process ID from ''; using a random value: -949685832
2020-04-19 09:22:51.593  INFO 1 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2020-04-19 09:22:51.594  INFO 1 --- [           main] i.j.s.SpringBootHelloApplication         : Started SpringBootHelloApplication in 0.063 seconds (JVM running for 0.065)

Wow, I guess this was simple and fast again. Now finally access your app inside a browser at http://localhost:8080/hello!

Configuring the Spring Boot native app’s port dynamically inside a Docker container

Being able to build and run our natively compiled Spring Boot apps inside Docker containers, we’re now truly free in our actions! As some of the readers may already know, I really like Heroku. So why not run our native Spring Boot app there?

How to run Spring Boot GraalVM Native Image Apps on Heroku

Logo sources: Docker logo, Heroku logo, Spring Boot logo, Computer logo, GraalVM logo

One of the things we need for most cloud platform as a service providers is the possibility to configure our Spring Boot native app’s port dynamically at runtime. This is simply because most cloud providers put some sort of proxy in front of our apps. And Heroku is no exception here. As the Heroku docs state:

The web process must listen for HTTP traffic on $PORT, which is set by Heroku. EXPOSE in Dockerfile is not respected, but can be used for local testing. Only HTTP requests are supported.

To achieve this, we need to somehow pass a port variable to our Spring Boot native app at runtime. Since the GraalVM support is just in its early stages, we can’t rely on a huge documentation. But the answer is quite simple! We only need to pass a -D parameter like -Dserver.port=8087 to the native app – just as we’re used to from non-native Spring Boot apps already:

./spring-boot-graal -Dserver.port=8087

After doing this, our app starts using port 8087. Having this in mind, we need to define the port also within a docker run command. Therefore a small change to our Dockerfile is required again:

...
# Add Spring Boot Native app spring-boot-graal to Container
COPY --from=0 "/build/target/native-image/spring-boot-graal" spring-boot-graal
 
# Fire up our Spring Boot Native app by default
CMD [ "sh", "-c", "./spring-boot-graal -Dserver.port=$PORT" ]

With this we are able to run our Dockerized native Spring Boot app with a dynamic port setting from command line like this:

docker run -e "PORT=8087" -p 8087:8087 spring-boot-graal

Our app can now be accessed at http://localhost:8087/hello.

If you want to simply run a native Spring Boot app without doing all the described steps yourself, you’re encoraged to use the example project’s Docker image released on hub.docker.com/r/jonashackt/spring-boot-graalvm. Simply run the pre-packaged app by executing: docker run jonashackt/spring-boot-graalvm:latest

Travis CI & Heroku Container Registry & Runtime to save us from ‘exit status 137’ errors

As we move forward to deploy our app on Heroku, we shouldn’t forget to create a Heroku app if we haven’t already:

heroku create spring-boot-graal

As we plan to use Heroku in “Docker mode”, we need to set the Heroku stack to container also:

heroku stack:set container --app spring-boot-graal

Sadly we can’t use the instructions inside the post on Running Spring Boot on Heroku with Docker, JDK 11 & Maven 3.5.x in our case here. Using them, we would run into the following error:

Error: Image build request failed with exit status 137
real  2m51.946s
user  2m9.594s
sys 0m19.085s
The command '/bin/sh -c source "$HOME/.sdkman/bin/sdkman-init.sh" && ./compile.sh' returned a non-zero code: 137

This error usually appears when Docker does not have enough memory. And since the free Heroku dyno only guarantees us 512 MB of RAM 🙁 (see Dyno Types), we won’t get far with our GraalVM native compilation here.

But as the docs state, the way of building Docker Images with heroku.yml isn’t the only option to run Docker containers on Heroku. Luckily there’s another way to use the Container Registry & Runtime (Docker Deploys). This allows us to decouple the Docker image build process (which is so memory-hungry!) from running our Docker container.

Work around the Heroku 512 MB RAM cap: Compiling Heroku-ready Docker images with TravisCI

So we need to shift the Docker build process onto another CI cloud platform like TravisCI. It already proved to work directly on the Travis virtual host, so why not also use the Travis Docker service?

Using TravisCI Docker service and Heroku Container Registry to run Spring Boot GraalVM Native Image Apps on Heroku

Logo sources: Docker logo, GitHub logo, TravisCI logo, Heroku logo, Spring Boot logo, Computer logo, GraalVM logo

And as we know how to do the native compilation of our Spring Boot inside a Docker container, the required .travis.yml becomes extremely simple:

dist: bionic
language: minimal
 
services:
  - docker
 
script:
  # Compile App with Docker
  - docker build . --tag=spring-boot-graal

The example projects .travis.yml additionally implement a separate build job Native Image compile on Travis Host to show how GraalVM Native Image compilation can be done on TravisCI without Docker also.

But also on Travis, we need to brace ourselves against the 'Error: Image build request failed with exit status 137' error. This one happened to me many times before I really solved the issue!

Using the native-image with –no-server option and a suitable -J-Xmx parameter

As mentioned in the Spring docs, we should use the --no-server option when running Native Image compilations with Spring for now. But what does this parameter do to our Native Image compilation process? As the official docs state:

Another prerequisite to consider is the maximum heap size. Physical memory for running a JVM-based application may be insufficient to build a native image. For server-based image building we allow to use 80% of the reported physical RAM for all servers together, but never more than 14 GB per server (for exact details please consult the native-image source code). If you run with --no-server option, you will get the whole 80% of what is reported as physical RAM as the baseline. This mode respects -Xmx arguments additionally.

We could leave out the no-server option in order to reduce the amount of memory our native image compilation consumes. But there’s an open GraalVM issue in combination with Spring which makes image building without --no-server sometimes unreliable. Luckily I found a hint in this GitHub issue that we could configure the amount of memory the --no-server option takes in total. This is done with the help of an Xmx parameter like -J-Xmx4G:

time native-image \
  --no-server -J-Xmx4G \
  --no-fallback \
  --initialize-at-build-time \
  -H:+TraceClassInitialization \
  -H:Name=$ARTIFACT \
  -H:+ReportExceptionStackTraces \
  -Dspring.graal.remove-unused-autoconfig=true \
  -Dspring.graal.remove-yaml-support=true \
  -cp $CP $MAINCLASS;

Using that option like this in our native-image command, we can repeatably reduce the amount of memory to 4 GB of RAM. And this should be enough for TravisCI, since it provides us with more than 6 GB using the Docker service (see this build for example). Using the option results in the following output for a native image compilation of our Spring Boot app:

08:07:23.999 [ForkJoinPool-2-worker-3] DEBUG io.netty.util.internal.PlatformDependent - maxDirectMemory: 4294967296 bytes (maybe)
...
[spring-boot-graal:215]   (typeflow): 158,492.53 ms,  4.00 GB
[spring-boot-graal:215]    (objects):  94,986.72 ms,  4.00 GB
[spring-boot-graal:215]   (features): 104,518.36 ms,  4.00 GB
[spring-boot-graal:215]     analysis: 368,005.35 ms,  4.00 GB
[spring-boot-graal:215]     (clinit):   3,107.18 ms,  4.00 GB
[spring-boot-graal:215]     universe:  12,502.04 ms,  4.00 GB
[spring-boot-graal:215]      (parse):  22,617.13 ms,  4.00 GB
[spring-boot-graal:215]     (inline):  10,093.57 ms,  3.49 GB
[spring-boot-graal:215]    (compile):  82,256.99 ms,  3.59 GB
[spring-boot-graal:215]      compile: 119,502.78 ms,  3.59 GB
[spring-boot-graal:215]        image:  12,087.80 ms,  3.59 GB
[spring-boot-graal:215]        write:   3,573.06 ms,  3.59 GB
[spring-boot-graal:215]      [total]: 558,194.13 ms,  3.59 GB
 
real  9m22.984s
user  24m41.948s
sys 2m3.179s

The one thing to take into account is that native image compilation will be a bit slower now. So if you run on your local machine with lots of memory (I hear you Jan with your 64 GB “Rechenzentrum” 🙂 ), feel free to erase the -J-Xmx4G parameter.

Pushing our dockerized native Spring Boot app to Heroku Container Registry

Now we should be able to finally push the build Docker image into Heroku’s Container Registry, from where we’re able to run our Spring Boot native app later on. Therefore we need to configure some environment variables in order to push to Heroku’s Container Registry inside our TravisCI job’s settings. The first one HEROKU_USERNAME should keep your Heroku email and HEROKU_PASSWORD will inherit your Heroku API key. Be sure to avoid displaying the values in the build log:

defining TravisCI environment variables for Heroku Container Registry access

With the following configuration inside our .travis.yml, we should be able to successfully log in to Heroku Container Registry:

    - script:
        # Login into Heroku Container Registry first, so that we can push our Image later
        - echo "$HEROKU_PASSWORD" | docker login -u "$HEROKU_USERNAME" --password-stdin registry.heroku.com

Now after a successful Docker build that compiles our Spring Boot app into a native executable, we finally need to push the resulting Docker image into Heroku Container Registry. Therefore we need to use the correct tag for our Docker image build (see the docs:

docker build . --tag=registry.heroku.com/yourAppName/HerokuProcessType
docker push registry.heroku.com/yourAppName/HerokuProcessType

For our example application the concrete docker build and docker push commands inside the .travis.yml look like this:

    - docker build . --tag=registry.heroku.com/spring-boot-graal/web
    - docker push registry.heroku.com/spring-boot-graal/web

Releasing our dockerized native Spring Boot app on Heroku container infrastructure

The final step after a successful docker push is to release our native Spring Boot app on Heroku container infrastructure. Since May 2018 this is always the last step to really run an app on Heroku using Docker (before that, a push was all you had to do).

There are two ways to achieve this according to the docs. Either through the CLI via heroku container:release web or with the API. The first would require us to install Heroku CLI inside TravisCI, the latter should work out of the box. Therefore, let’s craft the required curl command:

curl -X PATCH https://api.heroku.com/apps/spring-boot-graal/formation \
          -d '{
                "updates": [
                {
                  "type": "web",
                  "docker_image": "'"$(docker inspect registry.heroku.com/spring-boot-graal/web --format={{.Id}})"'"
                }]
              }' \
          -H "Content-Type: application/json" \
          -H "Accept: application/vnd.heroku+json; version=3.docker-releases" \
          -H "Authorization: Bearer $DOCKER_PASSWORD"

This command is even better than the documented one in the official Heroku docs. It already incorporates the docker inspect registry.heroku.com/spring-boot-graal/web --format={{.Id}} command to retrieve the required Docker image ID. Additionally, it also omits the need to log into Heroku CLI beforehand to create the needed ~/.netrc mentioned in the docs. This is because we simply use -H "Authorization: Bearer $DOCKER_PASSWORD" here (where $DOCKER_PASSWORD is our Heroku API Key).

The problem with Travis: It does not understand our nice curl command, since it interprets it totally wrong. Even if we mind the correct multiline usage. I guess our Java User Group Thüringen speaker Kai Tödter already knew that restriction of some CI systems. And that’s why he crafted a bash script for exactly that purpose. At that point I started to work with a simple script called heroku-release.sh in order to achieve the release of our Spring Boot app on Heroku:

#!/usr/bin/env bash
 
herokuAppName=$1
dockerImageId=$(docker inspect registry.heroku.com/$herokuAppName/web --format={{.Id}})
 
curl -X PATCH https://api.heroku.com/apps/$herokuAppName/formation \
          -d '{
                "updates": [
                {
                  "type": "web",
                  "docker_image": "'"$dockerImageId"'"
                }]
              }' \
          -H "Content-Type: application/json" \
          -H "Accept: application/vnd.heroku+json; version=3.docker-releases" \
          -H "Authorization: Bearer $DOCKER_PASSWORD"

Using this script, we finally have our fully working .travis.yml ready:

dist: bionic
language: minimal
 
services:
  - docker
 
- script:
    # Login into Heroku Container Registry first, so that we can push our Image later
    - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin registry.heroku.com
 
    # Compile App with Docker
    - docker build . --tag=registry.heroku.com/spring-boot-graal/web
 
    # Push to Heroku Container Registry
    - docker push registry.heroku.com/spring-boot-graal/web
 
    # Release Dockerized Native Spring Boot App on Heroku
    - ./heroku-release.sh spring-boot-graal

That’s it! After the next successful TravisCI build, we should be able to see our natively compiled and dockerized Spring Boot app running on Heroku at https://spring-boot-graal.herokuapp.com/hello

Running Spring Boot app on Heroku

You can even use heroku logs command to see what’s happening behind the scenes:

$ heroku logs -a spring-boot-graal
 
2020-04-24T12:02:14.562471+00:00 heroku[web.1]: State changed from down to starting
2020-04-24T12:02:41.564599+00:00 heroku[web.1]: State changed from starting to up
2020-04-24T12:02:41.283549+00:00 app[web.1]:
2020-04-24T12:02:41.283574+00:00 app[web.1]: .   ____          _            __ _ _
2020-04-24T12:02:41.283575+00:00 app[web.1]: /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
2020-04-24T12:02:41.283575+00:00 app[web.1]: ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
2020-04-24T12:02:41.283576+00:00 app[web.1]: \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
2020-04-24T12:02:41.283576+00:00 app[web.1]: '  |____| .__|_| |_|_| |_\__, | / / / /
2020-04-24T12:02:41.283578+00:00 app[web.1]: =========|_|==============|___/=/_/_/_/
2020-04-24T12:02:41.286498+00:00 app[web.1]: :: Spring Boot ::
2020-04-24T12:02:41.286499+00:00 app[web.1]:
2020-04-24T12:02:41.287774+00:00 app[web.1]: 2020-04-24 12:02:41.287  INFO 3 --- [           main] i.j.s.SpringBootHelloApplication         : Starting SpringBootHelloApplication on 1c7f1944-1f01-4284-8931-bc1a0a2d1fa5 with PID 3 (/spring-boot-graal started by u11658 in /)
2020-04-24T12:02:41.287859+00:00 app[web.1]: 2020-04-24 12:02:41.287  INFO 3 --- [           main] i.j.s.SpringBootHelloApplication         : No active profile set, falling back to default profiles: default
2020-04-24T12:02:41.425964+00:00 app[web.1]: 2020-04-24 12:02:41.425  WARN 3 --- [           main] io.netty.channel.DefaultChannelId        : Failed to find the current process ID from ''; using a random value: -36892848
2020-04-24T12:02:41.427326+00:00 app[web.1]: 2020-04-24 12:02:41.427  INFO 3 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 59884
2020-04-24T12:02:41.430874+00:00 app[web.1]: 2020-04-24 12:02:41.430  INFO 3 --- [           main] i.j.s.SpringBootHelloApplication         : Started SpringBootHelloApplication in 0.156 seconds (JVM running for 0.159)

Running Spring Boot apps as GraalVM Native Images with Docker is really cool!

Being able to leverage the power of containers together with the benefits of Spring Boot & GraalVM Native Image really takes us to a new level! Now we’re able to build and run our native Spring Boot apps nearly everywhere. If we keep a few basic conditions in mind, we can build our native apps inside pretty much every Continous Integration cloud platform. Be it TravisCI, CircleCI or something else. And having built it there, we can simply run it everywhere. As a first example, we saw how to run our native apps on Heroku in this article and we now know what to watch out for. Having Continuous Integration & Delivery in place, we’re again back in calmer waters.

But wait! Didn’t we use GraalVM Native Image compilation to be able to really benefit from cloud-native platforms like Kubernetes? As we reduced the memory footprint and startup time of our Spring Boot app tremendously and are able to ship those native apps inside Docker containers as well, we have everything in place to run our apps inside a Kubernetes cluster! Just as we’re used to from all those hip Quarkus.io or Go apps. 🙂 So as always: Stay tuned for follow-up posts!

Jonas Hecht

Trying to bridge the gap between software architecture and hands on coding, Jonas hired at codecentric. He has deep knowledge in all kinds of enterprise software development, paired with passion for new technology. Connecting systems via integration frameworks Jonas learned to not only get the hang of technical challenges.

Comment

Your email address will not be published. Required fields are marked *