GitHub Actions supports both x64 and ARM64 runner environments, giving teams more options for building and testing modern applications.

At first glance, moving a workflow from an x64 runner to ARM64 may look as simple as changing:

runs-on: ubuntu-latest

to an ARM64 runner label.

In practice, the processor architecture affects more than the operating system. Native binaries, Docker images, package managers, language runtimes, third-party tools, and compiled dependencies can all behave differently when the architecture changes.

GitHub provides ARM64 hosted runners for supported plans and repositories, including Ubuntu ARM64 environments. GitHub's runner documentation also distinguishes standard x64 and Arm64 runner images and their supported labels.

This article explains what developers should check before moving GitHub Actions workflows to ARM64 runners and how to test the migration safely.

What Is an ARM64 Runner?

ARM64, also called AArch64, is a 64-bit processor architecture based on the ARM instruction set.

Traditional GitHub-hosted Linux CI environments have commonly used x86-64 processors.

The architecture difference can be represented as:

x64
 |
 +-- Intel/AMD instruction set
 |
 +-- x86-64 binaries
 |
 +-- x64 container images


ARM64
 |
 +-- ARM instruction set
 |
 +-- ARM64 binaries
 |
 +-- ARM64 container images

Your application code may not care about this difference.

Native dependencies often do.

For example, a managed .NET application may run on both architectures, while a native library loaded by that application may only have an x64 binary.

That is where migration problems usually appear.

Why Use ARM64 in GitHub Actions?

ARM64 runners can be useful when your production infrastructure or development environments already use ARM-based systems.

Typical examples include:

There is also an important testing benefit.

If your application is eventually deployed to ARM64 infrastructure, testing only on x64 can leave architecture-specific problems undiscovered until later.

Check Runner Availability First

Before modifying a workflow, verify that the ARM64 runner type you want is available for your GitHub plan and repository.

GitHub-hosted runner availability differs by account type and operating system. GitHub also documents separate labels and specifications for Arm64 runners.

Do not assume that every workflow using:

runs-on: ubuntu-latest

can simply be changed to an ARM64 equivalent.

First confirm:

  1. The required ARM64 hosted runner is available.

  2. The repository is eligible to use it.

  3. The required operating system is supported.

  4. Your workflow actions support the architecture.

  5. Your application dependencies support ARM64.

Step 1: Identify Architecture-Specific Dependencies

Start by searching your repository for native dependencies.

Look for:

.dll
.so
.dylib
.exe

and package references that include architecture-specific binaries.

For example, a .NET application may contain:

runtimes/
├── linux-x64/
└── linux-arm64/

This is a good sign because the package explicitly provides architecture-specific assets.

However, if you find only:

runtimes/
└── linux-x64/

you should investigate before switching the CI runner.

A managed application can compile successfully while failing at runtime when it attempts to load an x64 native library on ARM64.

Step 2: Check Your .NET Runtime Configuration

For .NET applications, inspect the runtime identifiers used by the project.

For example:

<PropertyGroup>
  <RuntimeIdentifiers>
    linux-x64;linux-arm64
  </RuntimeIdentifiers>
</PropertyGroup>

This tells the build system that both runtime environments are supported.

If the project only contains:

<PropertyGroup>
  <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
</PropertyGroup>

do not assume ARM64 compatibility.

You may need to update the project configuration and verify every native dependency.

Step 3: Check GitHub Actions Used by the Workflow

Your application is not the only component that needs ARM64 support.

The workflow itself may use third-party actions.

For example:

steps:
  - uses: actions/checkout@v4

  - uses: actions/setup-dotnet@v4

  - uses: third-party/action@v1

The first two actions may support ARM64, but that does not automatically mean the third-party action does.

An action can contain:

Actions that package native binaries require particular attention.

Before migration, check whether each important action supports ARM64.

JavaScript Actions vs Docker Actions

GitHub Actions can be implemented in different ways.

A JavaScript action may be relatively portable if it relies only on architecture-independent Node.js functionality.

A Docker-based action can be more complicated.

For example:

- uses: vendor/security-scan@v1

The action may internally use:

Docker image
    |
    +-- x64 binary

If the image does not have an ARM64 variant, the action may fail on an ARM64 runner.

This is why checking the action's implementation and supported platforms is important.

Step 4: Check Docker Images

Docker is one of the biggest areas to validate when moving to ARM64.

Consider:

- name: Build container
  run: docker build -t sample-api .

Your Dockerfile may look perfectly normal:

FROM mcr.microsoft.com/dotnet/aspnet:10.0

WORKDIR /app

COPY . .

ENTRYPOINT ["dotnet", "SampleApi.dll"]

But the base image must support the architecture on which you are building.

Modern container registries commonly publish multi-platform images.

Conceptually:

sample-api:latest
        |
        +-- linux/amd64
        |
        +-- linux/arm64

If the image provides only:

linux/amd64

you cannot assume it will work natively on an ARM64 runner.

Check the Image Architecture

Docker can inspect an image manifest.

For example:

docker buildx imagetools inspect \
  mcr.microsoft.com/dotnet/aspnet:10.0

The output can show which platforms are available.

Look for entries such as:

linux/amd64
linux/arm64

If both are present, the image is published for both architectures.

This is especially important for:

Step 5: Check Multi-Architecture Builds

If your workflow publishes Docker images, ARM64 migration may actually be an opportunity to improve the pipeline.

For example:

- name: Set up QEMU
  uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build and push
  uses: docker/build-push-action@v6
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: example/app:latest

Here, the final image can target multiple architectures.

However, building a multi-platform image is not the same thing as proving that the application works correctly on both architectures.

You should still test the application on ARM64.

Step 6: Check Package Managers

Language package managers can also expose architecture problems.

For Node.js:

npm install

For Python:

pip install -r requirements.txt

For .NET:

dotnet restore

Most popular packages work across architectures, but native packages can behave differently.

For example, a Node.js package may contain native bindings.

Python packages may depend on native libraries or require compilation.

.NET packages may contain architecture-specific runtime assets.

If installation suddenly attempts to compile something from source, the ARM64 environment may expose a dependency that was hidden on x64.

Step 7: Check Native Build Tools

ARM64 builds may require native compilation.

Check whether your workflow installs tools such as:

gcc
g++
make
cmake
clang
pkg-config

For example:

- name: Install build tools
  run: |
    sudo apt-get update
    sudo apt-get install -y \
      build-essential \
      cmake \
      pkg-config

Do not automatically add packages just because the runner changed architecture.

First determine what your application actually requires.

The goal is to make the build reproducible rather than depending on the runner's preinstalled environment.

Step 8: Check Shell Scripts

Most shell scripts are architecture-independent.

But some scripts invoke architecture-specific commands.

For example:

./tools/my-x64-tool

will not work natively on ARM64 if the binary is compiled only for x64.

Search for checked-in executables and downloaded command-line tools.

A safer approach is to download the correct binary for the detected architecture.

For example:

ARCH=$(uname -m)

case "$ARCH" in
  x86_64)
    echo "Using x64 tooling"
    ;;
  aarch64)
    echo "Using ARM64 tooling"
    ;;
  *)
    echo "Unsupported architecture: $ARCH"
    exit 1
    ;;
esac

This makes architecture assumptions explicit.

Step 9: Check Third-Party CLI Tools

Many CI pipelines download tools during execution.

For example:

- name: Install scanner
  run: |
    curl -L https://example.com/tool-linux-x64.tar.gz \
      -o tool.tar.gz

This is a problem on ARM64 because the URL explicitly requests an x64 binary.

Instead, architecture-aware installation should be used when the vendor provides separate builds.

For example:

ARCH=$(uname -m)

if [ "$ARCH" = "aarch64" ]; then
    PACKAGE="tool-linux-arm64.tar.gz"
else
    PACKAGE="tool-linux-x64.tar.gz"
fi

The exact implementation depends on the software vendor.

Step 10: Check Test Services

Integration tests often start supporting services.

For example:

services:
  postgres:
    image: postgres:latest

or:

services:
  redis:
    image: redis:latest

Before moving the workflow to ARM64, verify that those container images support ARM64.

This is easy to overlook because the application itself may be fully compatible.

A test could fail because the database container cannot start, not because your application has an ARM64 problem.

Testing ARM64 Without Changing Production CI

Do not immediately replace every production workflow.

Instead, create a temporary matrix:

jobs:
  test:
    strategy:
      matrix:
        runner:
          - ubuntu-24.04
          - ubuntu-24.04-arm

    runs-on: ${{ matrix.runner }}

    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.x'

      - name: Restore
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release

      - name: Test
        run: dotnet test --configuration Release --no-build

Use the exact ARM64 runner label available for your GitHub environment; GitHub's current runner documentation identifies ARM64 labels separately from x64 labels.

This gives you side-by-side results.

What to Compare

When testing x64 and ARM64, compare more than whether the workflow is green.

Check:

Area

What to Check

Restore

Dependencies resolve correctly

Build

Native compilation succeeds

Tests

Unit tests pass

Integration tests

Supporting services start

Containers

Images build correctly

Packaging

Correct artifacts are produced

CLI tools

Required binaries execute

Deployment

Target platform accepts the artifact

Runtime

Application starts and behaves correctly

A successful build is only one part of the migration.

Common Problems

Problem 1: Exec Format Error

You may see:

exec format error

This usually indicates that the system attempted to execute a binary built for a different architecture.

Check:

uname -m

and inspect the binary or container image architecture.

Problem 2: Docker Image Does Not Support ARM64

A container may fail to start because the image contains only an x64 variant.

Check the image manifest and replace it with a multi-platform image if appropriate.

Problem 3: Native Package Compilation Fails

A package that previously downloaded an x64 prebuilt binary may try to compile from source on ARM64.

Check the package's ARM64 support and required system dependencies.

Problem 4: Third-Party Action Fails

An action may depend on an x64-only executable.

Check the action's implementation and supported platforms before replacing it.

Problem 5: Tests Pass but Deployment Fails

Your CI artifact may be architecture-specific.

For example:

ARM64 build
     |
     v
ARM64 binary
     |
     v
x64 production host
     |
     v
Failure

Make sure the architecture produced by CI matches the architecture expected by the deployment environment.

ARM64 and x64 Side by Side

Area

x64

ARM64

Instruction architecture

x86-64

AArch64

Native x64 binaries

Native

Usually incompatible without translation

Native ARM64 binaries

Usually incompatible

Native

Multi-platform containers

Supported

Supported

Managed application code

Often portable

Often portable

Native dependencies

Need x64 builds

Need ARM64 builds

CI testing

Common

Useful for ARM deployments

The important difference is native compatibility.

A source-code project can be architecture-independent while one dependency is not.

Best Practices

Test Before Switching

Run the workflow on ARM64 before making it the default.

Check Every Native Dependency

Do not stop at the application's main runtime.

Check:

Prefer Multi-Architecture Images

Where possible, use images that publish both:

linux/amd64
linux/arm64

Pin Important Dependencies

Reproducible CI becomes more important when changing architecture.

Use explicit runtime and tool versions where appropriate.

Keep Architecture Visible

If your project supports multiple architectures, document that clearly.

For example:

Supported:
- linux-x64
- linux-arm64

Test the Actual Deployment Artifact

Do not assume that successful compilation means successful execution.

Run the generated artifact in an environment matching the intended production architecture.

Advantages and Disadvantages

Advantages

Disadvantages

Tests ARM64 production compatibility

Some dependencies may be x64-only

Useful for multi-architecture applications

Migration requires additional testing

Helps identify architecture-specific bugs

Third-party actions may have limitations

Supports ARM-based infrastructure

Native builds may need extra configuration

Useful for multi-platform container builds

Some tools may need architecture-specific installation

ARM64 Migration Checklist

[ ] Confirm ARM64 runner availability
[ ] Identify all workflows that need ARM64 testing
[ ] Check GitHub Actions compatibility
[ ] Check native application dependencies
[ ] Check .NET runtime identifiers if applicable
[ ] Check Docker base images
[ ] Check service containers
[ ] Check package-manager dependencies
[ ] Check downloaded CLI tools
[ ] Check shell scripts for architecture assumptions
[ ] Run the complete test suite
[ ] Test container builds
[ ] Test the actual deployment artifact
[ ] Compare x64 and ARM64 results
[ ] Monitor the workflow after migration

Summary

Moving GitHub Actions to ARM64 is more than changing the runner label. The operating system may remain Linux, but the underlying processor architecture changes how native binaries, containers, libraries, package dependencies, and command-line tools behave.

The safest approach is to test ARM64 alongside your existing x64 environment first. Review GitHub Actions, native dependencies, Docker images, test services, downloaded binaries, and deployment artifacts before making ARM64 the default.

For applications that already target ARM-based infrastructure, ARM64 CI testing can also provide an important validation layer. It allows architecture-specific issues to be discovered during development rather than after deployment.

The key question before switching is not simply, "Does my application build on ARM64?"

It is:

"Does every important component of my CI/CD pipeline support ARM64?"

Answering that question before migration will make the transition much more predictable.