Building ARM64 on Github
Getting your CI/CD pipeline right can be a daunting task. Here is one I had to address:
- Create a Quarkus Java application
- Compile it to a native executable
- Build a container for it
- Make the container available to both Linux and MacOS
The little irony, Docker on macOS or Windowsruns Linux under the hood.
The easy part - Quarkus
As I've written before it is easy to get started with Quarkus. It provides 5 ways to build containers, and detailed instructions to build a native image.
Building a native image looked daunting, with quite some prerequisites like GraalVM, CLI and C compiler. Luckily all this is available in a builder image, and a simple property settin in your pom.xml
settles it:
<properties>
<quarkus.container-image.group>stwissel</quarkus.container-image.group>
<quarkus.container-image.name>code-with-quarkus</quarkus.container-image.name>
<quarkus.container-image.tag>${project.version}</quarkus.container-image.tag>
<quarkus.native.builder-image>quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21</quarkus.native.builder-image>
</properties>
I use Google's gib project to build containers since is doesn't depend on local Docker, is optimized for Java and Quarkus supports it well. All it takes is a dependency:
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-jib</artifactId>
</dependency>
</dependencies>
Building a native image now is just a Maven command away:
#!/bin/bash
# run a build on github
version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
if [[ "$BRANCH_NAME" == "main" ]]; then
tag="latest"
else
tag="$BRANCH_NAME"
fi
mvn -Pnative \
--no-transfer-progress \
--show-version \
--fail-fast \
--batch-mode \
-DskipTests \
-Dquarkus.container-image.push=true \
-Dquarkus.container-image.tag=${tag} \
-Dquarkus.container-image.additional-tags=${version} \
clean package
The result lands in Github's container registry ghcr.io, but you can target any registry Github can reach.
All fine and dandy? Not so fast! Native means: depending on the hardware architecture. You build on GitHub, you get amd64
code by default, you build on your M1|2|3|4 Mac, you get arm64
code.
While you can use QEMU to bridge the platform gap, you would need to package all steps inside a single run-on-architecture action instead of picking what you fancy from the marketplace.
Using a native runner
Github provides multiple runners for the runs-on
entry in your yaml file. Typically you would have something like ubuntu-latest
. I tried macos-latest
only to learn that it doesn't come with Docker installed and fiercly resisted any installation attempt.
Luckily GitHub allows you to create arm64
runners. You need an organisation for that. Head tohttps://github.com/organizations/[insert-org-name-here]/settings/actions/runners
and start defining a new runner. When you are on the free plan, you have to host it yourself, on a paid plan GitHub offers:
- Linux x64
- Linux ARM64
- Windows x64
- Windows ARM64
The wizzard walks you through all steps, picking size, image and concurrency. I build the smallest Linux ARM64 image and named it ArmRunner
. Add it to a runner group that allows your repo, in my case called Public
. A small update to the workflow and you are set:
jobs:
arm_build:
runs-on:
group: Public
labels: ArmBuilder
name: Build arm image
steps:
# Define your usual steps here
As usual YMMV
Posted by Stephan H Wissel on 20 November 2024 | Comments (0) | categories: Java Quarkus WebDevelopment