Docker makes it easier to package an application with its dependencies and run it consistently across different environments. However, a Docker image built on one processor architecture may not run natively on another. This becomes important when your development machine uses an Intel or AMD processor, but your production servers use ARM-based processors.
For example, you might build a Docker image on an AMD64 laptop and deploy it to an ARM64 server. The image may fail to start because it contains binaries compiled for a different architecture. Even if the application uses a language such as Python or Node.js, some dependencies may include architecture-specific native components.
Docker multi-platform builds solve this problem by allowing you to publish one image reference that supports multiple processor architectures. When someone pulls that image, Docker selects the appropriate platform-specific image for the target environment.
In this article, you will learn how Docker multi-platform builds work, how to build images for AMD64 and ARM64, how to publish them to a container registry, and how to troubleshoot common problems.
What Are Docker Multi-Platform Builds?
A multi-platform Docker image supports more than one operating system and processor architecture.
The two architectures commonly encountered in modern container environments are:
AMD64: The 64-bit x86 architecture used by most traditional Intel and AMD servers and desktop computers.
ARM64: The 64-bit ARM architecture used by many ARM-based cloud instances, Apple Silicon computers, and other ARM systems.
A standard Docker build usually targets the platform on which the build is running, unless you explicitly select another platform.
For example, running the following command on an AMD64 machine generally produces an AMD64 image:
docker build -t myapp:1.0 .
If you deploy that image to an ARM64 environment, Docker may report an architecture mismatch or fail when executing an incompatible binary.
A multi-platform build creates separate image variants for the requested architectures and publishes them under a shared image reference.
For example:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myregistry/myapp:1.0 \
--push .
This command requests images for both architectures and pushes the resulting multi-platform image to a registry.
The important point is that Docker does not normally combine AMD64 and ARM64 binaries into one filesystem. Instead, the registry stores platform-specific image manifests and an index that references the supported variants.
How Multi-Platform Images Work
A multi-platform image typically consists of an image index and multiple platform-specific image manifests.
The image index identifies the available variants. Each variant points to its own image configuration and filesystem layers.
Consider an image published as:
myregistry/myapp:1.0
The registry might provide the following variants:
Platform | Typical use |
|---|
linux/amd64 | Intel and AMD servers |
linux/arm64 | ARM-based servers and compatible devices |
When an AMD64 machine pulls the image, Docker selects the AMD64 variant. When an ARM64 machine pulls the same image reference, Docker selects the ARM64 variant.
The application deployment configuration does not need a different image tag for each architecture, provided both platforms are included in the published image index.
This is particularly useful when the same application runs across different cloud instance types, development computers, and container environments.
Understanding Docker Buildx
Docker Buildx is the command-line interface for Docker's extended build capabilities. It uses BuildKit and supports features such as multi-platform builds, advanced caching, and different build environments.
Before creating a multi-platform image, verify that Docker and Buildx are available:
docker version
docker buildx version
Check the platforms supported by your current builder:
docker buildx inspect
Depending on the builder configuration, the output may list platforms such as:
Platforms: linux/amd64, linux/arm64
The exact list depends on your Docker installation and builder.
If you need to create a dedicated Buildx builder, use the following commands:
docker buildx create \
--name multiarch-builder \
--driver docker-container \
--use
Start the builder and inspect its configuration:
docker buildx inspect --bootstrap
The docker-container driver runs a BuildKit container to perform builds. It is commonly used for multi-platform builds because it supports exporting images to registries and other destinations.
The builder must have a suitable execution method for each requested platform. Depending on your environment, this can involve native build nodes, emulation, or a combination of both.
Prepare a Sample Application
Let's create a simple Node.js application and build a multi-platform image for it.
Create a directory named docker-multiarch with the following two files.
Create package.json
{
"name": "docker-multiarch-demo",
"version": "1.0.0",
"description": "Docker multi-platform build example",
"main": "server.js",
"scripts": {
"start": "node server.js"
}
}
Create server.js
const http = require("http");
const os = require("os");
const port = process.env.PORT || 3000;
const server = http.createServer((req, res) => {
res.writeHead(200, {
"Content-Type": "application/json"
});
res.end(JSON.stringify({
message: "Application is running",
architecture: process.arch,
platform: process.platform,
hostname: os.hostname()
}));
});
server.listen(port, "0.0.0.0", () => {
console.log(`Server listening on port ${port}`);
});
The application returns its runtime architecture in the HTTP response. This helps verify which architecture-specific image Docker selected.
For example, an AMD64 container running Node.js will typically report x64 for process.arch, while an ARM64 container will typically report arm64.
Create the Dockerfile
Create a file named Dockerfile:
FROM node:22-alpine
WORKDIR /app
COPY package.json ./
COPY server.js ./
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
CMD ["npm", "start"]
The Dockerfile uses an official Node.js image and copies the application files into the container.
The example intentionally keeps the application simple. The same multi-platform principles apply to more complex applications, but applications with compiled dependencies require additional attention to architecture compatibility.
For a production application, select a Node.js version that is supported by your project and security requirements.
Build an Image for AMD64 and ARM64
With the application files ready, you can create a multi-platform image.
Step 1: Authenticate with the Container Registry
For Docker Hub, run:
docker login
Follow the prompts to authenticate.
For another registry, use the appropriate registry hostname and credentials. In automated environments, prefer short-lived credentials or a secrets-management system instead of storing passwords in source code.
Step 2: Select an Image Name
Replace yourusername with your registry namespace:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t yourusername/docker-multiarch-demo:1.0 \
.
This command requests builds for both platforms.
However, there is an important detail: the command above does not specify an output destination. Depending on the Buildx driver and configuration, the result may remain in the builder's cache rather than being loaded into the local Docker image store.
For a multi-platform image intended for distribution, pushing directly to a registry is usually the simplest approach.
Step 3: Build and Push the Multi-Platform Image
Run:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t yourusername/docker-multiarch-demo:1.0 \
--push .
Replace the sample image name with your actual registry namespace.
The --platform option specifies the target platforms. The -t option assigns the image reference, and --push publishes the generated image index and its platform-specific images to the registry.
If the build succeeds, the registry will contain a single image reference that supports both requested architectures.
Step 4: Inspect the Published Image
Use the following command:
docker buildx imagetools inspect \
yourusername/docker-multiarch-demo:1.0
The output should identify an image index or manifest list containing the supported platform variants.
Look for entries corresponding to:
linux/amd64
linux/arm64
The precise output format can vary by registry and image metadata.
If only one platform appears, verify the build command, the registry tag, and whether the intended multi-platform image was successfully published.
Run and Test the Image on Different Architectures
After publishing the image, you can test it on machines that support the respective architectures.
On an AMD64 machine, run:
docker run --rm \
-p 3000:3000 \
yourusername/docker-multiarch-demo:1.0
Open the application endpoint on port 3000 using your browser or an HTTP client.
The response should contain a JSON object similar to:
{
"message": "Application is running",
"architecture": "x64",
"platform": "linux",
"hostname": "container-hostname"
}
On an ARM64 machine, run the same command against the same image reference. The architecture field should normally report arm64.
The exact hostname and other runtime values will differ between containers.
You can also inspect the architecture of a locally pulled image:
docker image inspect \
yourusername/docker-multiarch-demo:1.0 \
--format '{{.Os}}/{{.Architecture}}'
This command reports the platform of the image selected and available in the local image store. It does not list every platform published under the remote image reference. Use docker buildx imagetools inspect to examine the registry's multi-platform image index.
If you are testing on a single machine, remember that running a foreign architecture may depend on emulation support. For the most reliable validation, test on native AMD64 and ARM64 environments when possible.
Building Multi-Platform Images with GitHub Actions
Manually building and publishing images works well during development, but automated builds are more reliable for regular releases.
GitHub Actions can build and publish multi-platform images whenever code is merged or a release is created.
The following example uses Docker's maintained GitHub Actions for Buildx setup, registry authentication, and image publishing.
Create a workflow file at .github/workflows/docker-build.yml:
name: Build Multi-Platform Docker Image
on:
push:
branches:
- main
tags:
- "v*"
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
yourusername/docker-multiarch-demo:latest
yourusername/docker-multiarch-demo:${{ github.sha }}
Before using this workflow, configure the DOCKERHUB_USERNAME repository variable and DOCKERHUB_TOKEN repository secret. Replace yourusername in the image tags with the correct registry namespace.
The example pins actions to version tags for readability. Production workflows should follow your organization's action-version policy and may pin actions to verified full commit SHAs.
What Each Step Does
The checkout action retrieves the source code.
The QEMU setup action configures user-mode emulation for foreign architectures when required by the build environment. QEMU is useful for many builds, but it does not eliminate every cross-platform limitation.
The Buildx setup action configures the builder.
The registry login action authenticates using repository-managed credentials.
Finally, the build-push action builds both platform variants and publishes them to the registry.
The workflow publishes a latest tag and a commit-specific tag. In a production release process, consider using immutable version tags and deploying a verified digest so that the exact image tested is the image promoted to production.
QEMU, Native Builders, and Cross-Compilation
Docker can build images for different architectures using several approaches. Choosing the right method affects build speed, compatibility, and operational complexity.
Approach | How it works | Advantages | Limitations |
|---|
QEMU emulation | Runs foreign-architecture instructions through emulation | Simple to configure in many environments | Compiled workloads can be slower |
Native builders | Builds each architecture on compatible hardware | Good performance and native execution | Requires access to multiple build environments |
Cross-compilation | Compiles application binaries for a target architecture from another architecture | Can be efficient for supported toolchains | Requires correct compiler, libraries, and target configuration |
QEMU is often convenient for getting started. However, emulated builds can be noticeably slower when compiling large applications or installing dependencies that compile native extensions.
Native builders can be more suitable for large production pipelines. They execute architecture-specific operations on compatible hardware and reduce dependence on emulation.
Cross-compilation is useful for languages and toolchains that support explicit target architectures. However, cross-compiling an application binary does not automatically solve every dependency or container-base-image compatibility issue.
For many teams, the best solution is to begin with Buildx and emulation, measure build performance, and introduce native builders when build time or compatibility becomes a concern.
Common Problems and How to Fix Them
The Image Works on AMD64 but Fails on ARM64
This usually means that part of the image contains architecture-specific content that is incompatible with ARM64.
Check the base image, compiled binaries, and native dependencies. Ensure that the selected base image supports ARM64 and that dependencies are installed or compiled for the target platform.
Do not assume that every third-party image supports both architectures simply because it can be pulled successfully on your development machine.
Buildx Reports an Unsupported Platform
Check the builder configuration:
docker buildx inspect --bootstrap
Verify that the target platform is supported by the selected builder. Depending on your environment, you may need to configure emulation or add a native build node.
Also verify that the image's base image supports the requested architecture.
The Image Is Missing from the Local Docker Image List
A multi-platform build may be exported directly to a registry rather than loaded into the local Docker image store.
If your goal is to publish the image, use --push.
For local testing, a single-platform build can often be loaded with --load:
docker buildx build \
--platform linux/amd64 \
-t docker-multiarch-demo:test \
--load .
This is useful for testing one architecture locally. Do not assume that --load supports loading a complete multi-platform image index into every Docker image store configuration.
A Dependency Installs Successfully but Fails at Runtime
Some packages download precompiled native modules during installation. Those modules must match the architecture and operating system used by the final container.
A build that downloads an AMD64 binary and then places it into an ARM64 image can fail even when the application source code is portable.
Keep dependency installation inside the target-platform build stage when appropriate, and verify that downloaded binaries and native modules match the intended architecture.
The Published Image Contains Only One Architecture
Inspect the remote image reference:
docker buildx imagetools inspect \
yourusername/docker-multiarch-demo:1.0
Confirm that the build command specifies both platforms and that the published tag points to the new image index.
Also check whether a later single-platform build overwrote the same tag.
Best Practices for Production Multi-Platform Builds
Choose Compatible Base Images
Use base images that explicitly support the architectures you need. Review third-party images carefully because architecture support can vary by image, version, and tag.
Test the Final Image on Both Platforms
A successful build does not prove that the application behaves correctly on every architecture.
Run integration tests against both AMD64 and ARM64 images, particularly when your application includes native extensions, compiled binaries, or architecture-sensitive dependencies.
Use Multi-Stage Builds Where Appropriate
Multi-stage builds can separate compilation tools from the final runtime image. They can also help you control which artifacts enter the final image.
However, make sure compiled artifacts target the correct architecture. Copying a binary compiled for the build machine into an image for a different architecture can introduce runtime failures.
Use Build Cache Efficiently
BuildKit supports caching that can reduce repeated build work. In CI pipelines, a registry-backed cache can help reuse build results between workflow runs.
For example, the build-push action supports cache configuration:
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: yourusername/docker-multiarch-demo:1.0
cache-from: type=gha
cache-to: type=gha,mode=max
The GitHub Actions cache backend depends on the workflow environment and its supported configuration. Review cache limits and retention policies when applying this to large builds.
Avoid Mutable Tags for Critical Deployments
Tags such as latest can point to different image versions over time. For controlled deployments, use versioned tags and, where appropriate, deploy by image digest.
This makes rollbacks easier and helps ensure that the image validated during testing is the same image used in production.
Keep Credentials Out of the Dockerfile
Do not copy registry credentials, access tokens, or other secrets into image layers.
Use the registry's authentication mechanism and your CI platform's secret-management features. Be careful with build arguments and environment variables because they are not automatically secure mechanisms for handling secrets.
Monitor Build Time and Image Size
Multi-platform builds may increase build time because multiple variants must be produced. The final image sizes can also differ between architectures.
Track build duration, cache effectiveness, and image size. These measurements help you decide whether emulation remains suitable or native builders would provide a worthwhile improvement.
Advantages and Limitations of Multi-Platform Builds
Multi-platform builds offer several practical benefits:
One image reference can support multiple processor architectures.
Teams can use compatible images across different development and production environments.
CI/CD pipelines can publish architecture-specific variants automatically.
Cloud deployments can choose supported instance architectures without requiring a separate application image naming convention for each platform.
Platform-specific dependency installation can be handled during the appropriate build.
There are also limitations to consider:
Builds may take longer because multiple platform variants are generated.
Emulation can slow down compilation and dependency installation.
Some base images and third-party dependencies may not support every architecture.
Native dependencies require careful testing.
CI pipelines need suitable caching, credentials, and publishing configuration.
These limitations do not make multi-platform builds unsuitable. They mean that architecture support should be treated as part of the application's build and release process rather than as a setting that can be enabled without testing.
Conclusion
Docker multi-platform builds allow teams to publish images that support AMD64 and ARM64 using a shared image reference. Docker Buildx makes this possible by building platform-specific image variants and publishing an image index that allows Docker to select the appropriate variant at pull time.
To get started, prepare a Dockerfile that uses compatible base images, configure Buildx, and build with --platform linux/amd64,linux/arm64. Publish the result to a container registry and inspect the image index to confirm that both architectures are available.
For production environments, add automated builds, caching, native dependency checks, and architecture-specific tests to your CI/CD pipeline. These steps help ensure that the image does more than build successfully: they help ensure that it runs reliably on the systems where your application will be deployed.
Join the conversation! Your thoughts help the community grow.