Multi-Architecture Spring OCI from anywhere with Paketo

Multi-Architecture Spring OCI from anywhere with Paketo

Learn how to build multi-architecture Spring Boot 3.4.0 OCI images for both AMD64 and ARM64 using Paketo buildpacks.

·

4 min read

Multi-Architecture Spring OCI from anywhere with Paketo

My favorite feature of Spring Boot 3.4.0 is the adoption of multi-architecture buildpacks. This feature empowers you to construct native images and traditional JVM-based applications for both AMD64 and ARM64, from a unified build process. The fact that OCI images will also be smaller because of the new default buildpack is a bonus.

Emotionally Invested

I've been building native images for ARM64 since 2021, because I'm a Raspberry Pi enthusiast and passionate about Spring Boot. Since that time, I've created dozens of solutions, to put production-ready, enterprise-grade, Spring Boot on Raspberry Pi ARM64 devices with only 512MB of RAM. I no longer have to support my own solution.

Getting Started

Prerequisites:

  • At least Java 17
  • Docker

Docker Desktop Configuration

Docker Desktop comes with built-in QEMU support for multi-architecture builds. However, if you need to install QEMU manually, you have two options:

Option 1: Using tonistiigi/binfmt

# Install QEMU support for all architectures
docker run --privileged --rm tonistiigi/binfmt --install all

This command was found here: docs.docker.com/build/building/multi-platform

Option 2: Using multiarch/qemu-user-static

# Install and configure QEMU
docker run --privileged --rm multiarch/qemu-user-static --reset -p yes

More information can be found here: github.com/multiarch/qemu-user-static

Spring Boot 3.4.0 application

First, let's create a new Spring Boot application:

mkdir mydemo
cd mydemo
curl https://start.spring.io/starter.tgz -d dependencies=web,actuator -d type=maven-project | tar -xvzf -

You could also use https://start.spring.io

Create a multi-architecture OCI Image

Here's the step-by-step process to build and publish multi-architecture images:

# Build ARM64 image
./mvnw spring-boot:build-image -Dspring-boot.build-image.imagePlatform=linux/arm64 -Dspring-boot.build-image.imageName=dashaun/blog-3-4-0:arm64
docker push dashaun/blog-3-4-0:arm64

# Build AMD64 image
./mvnw spring-boot:build-image -Dspring-boot.build-image.imagePlatform=linux/amd64 -Dspring-boot.build-image.imageName=dashaun/blog-3-4-0:amd64
docker push dashaun/blog-3-4-0:amd64

# Create and push multi-architecture manifest
docker manifest create dashaun/blog-3-4-0:multiarch --amend dashaun/blog-3-4-0:arm64 --amend dashaun/blog-3-4-0:amd64
docker manifest push dashaun/blog-3-4-0:multiarch

Inspect

Inspect the multi-architecture manifest:

docker manifest inspect dashaun/blog-3-4-0:multiarch

Inspect the manifest, results below.

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "size": 2407,
      "digest": "sha256:96d95eed9308f86a8055156913ed8710c25917bdf0389bd6d5cf2ab0a79c77fc",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "size": 2406,
      "digest": "sha256:4c42eebe412f56a03d840d2660b9defde3820ce1c5e510ffba0067be0ae11c8e",
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    }
  ]
}

The manifest shows both platforms linux/amd64 and linux\arm64

Looking at the Docker Hub tags Digest sections, notice that the sha values for the arm64 and amd64 tags match the multiarch tag's values.

The new default buildpack, builder-jammy-java-tiny also results in much smaller images. Both of the images we created here are under 275mb. Switching to Spring Boot 3.3.6 and the previous default buildpack paketobuildpacks/builder-jammy-base results in a ~350mb image.

Building Native Images

The process for building native images is similar, with the addition of GraalVM support:

mkdir my-native-demo
cd my-native-demo
curl https://start.spring.io/starter.tgz -d dependencies=web,actuator,native -d type=maven-project | tar -xvzf -

You could also use https://start.spring.io

Building Multi-Architecture Native Images

When the buildpack see's the native-maven-plugin in the project, it creates native images with GraalVM. Our steps won't change, we will just use different tags.

# Build ARM64 native image
./mvnw spring-boot:build-image -Dspring-boot.build-image.imagePlatform=linux/arm64 -Dspring-boot.build-image.imageName=dashaun/blog-3-4-0:native-arm64
docker push dashaun/blog-3-4-0:native-arm64

#Build AMD64 native image
./mvnw spring-boot:build-image -Dspring-boot.build-image.imagePlatform=linux/amd64 -Dspring-boot.build-image.imageName=dashaun/blog-3-4-0:native-amd64
docker push dashaun/blog-3-4-0:native-amd64

# Create and push multi-architecture manifest
docker manifest create dashaun/blog-3-4-0:native-multiarch --amend dashaun/blog-3-4-0:native-arm64 --amend dashaun/blog-3-4-0:native-amd64
docker manifest push dashaun/blog-3-4-0:native-multiarch

Inspect Native

docker manifest inspect dashaun/blog-3-4-0:native-multiarch

Inspect the manifest, results below.

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 2407,
         "digest": "sha256:70311015a28ff9ebbaa0ef0552e91bd85d2220716b69a8444ecf18ae542fa3f1",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 2406,
         "digest": "sha256:4b9243d946a61f5fd8c9762f9e8a5ef107ba5a51a3679faa54b5a3cd96e4147c",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

The manifest shows both platforms linux/amd64 and linux/arm64

Take a look at these image sizes!

dashaun/blog-3-3-6                         latest           0e61fed5cf4a   44 years ago   348MB
dashaun/blog-3-4-0                         amd64            6ce61c588eba   44 years ago   268MB
dashaun/blog-3-4-0                         native-amd64     9f63e381e26c   44 years ago   117MB
dashaun/blog-3-4-0                         native-arm64     7a9b08bf964a   44 years ago   111MB
dashaun/blog-3-4-0                         arm64            0d07d6f15c86   44 years ago   259MB

I did this experiment on MacOS with ARM64 (M3 chip), but these exact same steps can be used on either ARM64 or AMD64 machines.

Conclusion

Spring Boot 3.4.0's multi-architecture support with Paketo buildpacks revolutionizes how we build and deploy applications across different architectures. Whether you're targeting cloud platforms or edge devices like Raspberry Pi, you can now build and deploy with confidence.

The combination of:

  • Multi-architecture support
  • Smaller image sizes
  • Native image capabilities
  • Simplified build process

Makes Spring Boot 3.4.0 a compelling choice for modern cloud-native and edge-native applications.

Keep Learning