Native Arm64 Execution in GitHub Actions

The shift toward ARM64 architecture in the consumer and enterprise sectors—driven by the efficiency of Apple Silicon M-Series chips and the scalability of Ampere Altra processors—has created a critical need for native build environments in Continuous Integration (CI) pipelines. For years, developers relying on GitHub Actions were tethered to x86-64 runners, forcing them to rely on emulation layers to produce binaries for AArch64 targets. The transition to native Arm64 runners marks a paradigm shift, eliminating the performance penalties of emulation and streamlining the delivery of software for devices ranging from Raspberry Pi servers to high-performance cloud instances.

The Architectural Struggle: Emulation vs. Native Execution

Historically, producing Arm64 artifacts on GitHub Actions required the use of QEMU (Quick Emulator), a generic open-source machine emulator and virtualizer. QEMU allows an x86-64 processor to simulate the instruction set of an Arm64 processor, enabling the execution of code designed for a different CPU architecture. While this provided a functional bridge, it introduced significant technical debt.

The technical layer of emulation involves a translation process where every Arm64 instruction is mapped to an x86-64 equivalent. This overhead results in a massive performance degradation. In real-world scenarios, this translates to longer build times, increased CI spend, and a higher probability of encountering platform-specific bugs that are hidden by the emulator but appear in the final native environment. For developers, this meant that "successful" builds in CI could still fail upon deployment to actual Arm64 hardware due to inconsistencies in the emulation layer.

The impact of moving to native Arm64 execution is immediate: the elimination of the translation layer ensures that the binary is compiled and tested on the actual hardware it will target. This removes the "black box" effect of QEMU, allowing for precise debugging and significantly faster execution cycles.

Native Arm64 Runner Ecosystems

There are currently three primary paths to achieving native Arm64 execution within GitHub Actions: official GitHub-hosted runners, managed third-party runners like Blacksmith, and self-hosted custom runners.

Official GitHub-Hosted Arm64 Runners

In 2024, GitHub introduced Arm64 free CI runners in beta for Organizations. This capability was subsequently expanded to personal accounts, effectively making native Arm64 support available to the general public. This is a critical development for independent developers and small projects, such as the Joplin application, which has a large user base relying on M-Series Macbooks and Raspberry Pi servers.

The technical implementation of these runners allows developers to treat Arm64 as a native target without needing to configure complex external infrastructure. However, a key limitation currently exists: these runners are not yet available for private repositories. For public projects, the performance gap between these native runners and traditional x86 runners is often negligible, providing a seamless transition for those who previously struggled with separate, difficult-to-inspect building environments.

Blacksmith Managed Runners

Blacksmith provides a specialized offering of managed Arm64 runners designed specifically to optimize Docker workflows. Their service focuses on reducing the operational overhead of managing hardware while improving cost-efficiency.

Technically, Blacksmith's runners integrate directly into the GitHub Actions workflow via the runs-on keyword. By specifying a runner like blacksmith-2vcpu-ubuntu-2204-arm, the job is routed to native Arm64 hardware. Blacksmith claims a 50% reduction in CI spending compared to standard GitHub runners, which is achieved through optimized resource allocation and faster execution times that reduce the total billable minutes of a build.

Self-Hosted Custom Runners

For enterprises or developers requiring total control over the hardware, self-hosting a runner on an Ampere A1 instance (such as those available on Oracle Cloud Infrastructure) is a viable strategy. This approach involves deploying a Linux distribution, such as AlmaLinux 8.6, and registering it as a GitHub runner.

The process for setting up a self-hosted runner involves:

  • Navigating to Repository Settings -> Actions -> Runners.
  • Selecting the appropriate operating system and architecture.
  • Downloading and running the runner client.
  • Executing a checksum verification (notably, on Red Hat derivatives like AlmaLinux, sha256sum is used instead of shasum -I 256).
  • Running the configuration script and following the prompts to register the runner with the GitHub instance.

One significant security caveat is that GitHub advises using self-hosted runners exclusively for private repositories to prevent malicious actors from triggering unauthorized code execution on private hardware via public pull requests.

Implementing Multi-Platform Docker Workflows

Building Docker images for multiple architectures (multi-arch) is a common requirement for modern software distribution. There are two primary methodologies for achieving this within GitHub Actions: the Emulation Method and the Native Method.

The Emulation Method (QEMU)

When native runners are unavailable, developers use the docker/setup-qemu-action to enable multi-platform builds on an x86-64 runner. This allows a single job to target both linux/amd64 and linux/arm64.

The workflow for this approach is structured as follows:

yaml jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set Up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Build and push uses: docker/build-push-action@v3 with: platforms: linux/amd64,linux/arm64

While functional, this is considered a "last resort" due to the performance penalties associated with emulating the Arm64 instruction set on an x86 processor.

The Native Method (Blacksmith/Direct Arm64)

The ideal approach is to use native hardware to build the Arm64 image. This removes the overhead of QEMU and ensures reliability. Using a managed service like Blacksmith, the workflow is simplified by directing the job to a native Arm64 runner.

yaml jobs: build: runs-on: blacksmith-2vcpu-ubuntu-2204-arm steps: - uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Build and push uses: docker/build-push-action@v3 with: platforms: linux/arm64

To handle both architectures without emulation, developers can use the include keyword or a build matrix to define multiple jobs, one for each platform, ensuring each image is built on its respective native hardware.

Technical Implementation: Build Matrices and Custom Runners

To verify builds across different architectures, developers utilize the "build matrix" feature. This allows a single job definition to be executed across multiple operating systems or hardware configurations.

For a project utilizing a compiled language like C (for example, a "FizzBuzz" program using make), the transition from a standard runner to a multi-architecture setup involves modifying the strategy block.

The following comparison illustrates the shift in configuration:

Feature Standard x86-64 Runner Multi-Arch Matrix (inc. Self-Hosted Arm64)
runs-on ubuntu-latest ${{ matrix.os }}
strategy Not required matrix: os: [ubuntu-latest, [self-hosted, linux, ARM64]]
Hardware GitHub-hosted x86 Hybrid (GitHub-hosted + Ampere A1)
Target x86-64 x86-64 and AArch64

Example of a full matrix implementation for a C project:

```yaml
name: Custom GitHub Actions runner Demo
on: [push]

jobs:
Build-and-Run-FizzBuzz:
strategy:
matrix:
os: [ubuntu-latest, [self-hosted, linux, ARM64]]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Compile project in repository
run: |
make clean && make
- name: Execute command created
run: |
./fizzbuzz
```

In this setup, the project is compiled and executed on both the standard GitHub environment and the custom Ampere-powered runner. This ensures that the binary behaves identically across both architectures.

Comparative Analysis of Arm64 Integration Strategies

The choice of how to integrate AArch64 into a CI pipeline depends on the project's scale, budget, and security requirements.

Strategy Performance Cost Setup Complexity Best Use Case
QEMU Emulation Low Medium Low Low-volume, occasional builds
GitHub-Hosted Arm64 High Free (Beta) Very Low Public projects, personal accounts
Blacksmith Managed High Low (50% less) Low High-volume Docker multi-arch builds
Self-Hosted (Ampere) Maximum Variable High Private enterprise repos, total HW control

Analysis of Operational Impact

The transition to native Arm64 runners has a profound impact on the software development lifecycle. By removing the reliance on QEMU, developers eliminate a layer of uncertainty. In the case of the Joplin project, the ability to use native runners meant that the Arm64 release of the desktop snap could be built in an environment that is easy to inspect, rather than relying on a separate, fragile build environment that is prone to breaking.

Furthermore, the economic impact is significant. Managed services like Blacksmith leverage native hardware to reduce the time a runner is active. Since CI billing is typically based on the minute, reducing a build time from 20 minutes (emulated) to 5 minutes (native) provides a direct 75% reduction in cost for that specific job, compounded by Blacksmith's own pricing model which is 50% cheaper than GitHub's native offerings.

From a technical stability perspective, native execution prevents the "it works in CI but not on the device" syndrome. Because the code is compiled and executed on the same architecture as the end-user's hardware (e.g., a Raspberry Pi or an Ampere-based cloud server), the risk of architecture-specific bugs—such as alignment issues or floating-point discrepancies—is virtually eliminated.

Sources

  1. Building Multi-platform Docker Images for Arm64 in GitHub Actions
  2. Adding AArch64 to your GitHub CI with GitHub Actions and Ampere Altra
  3. GitHub Actions now provides ARM64 for free

Related Posts