Optimizing multi-platform Docker builds (amd64 & arm64) with registry Cache
In modern CI/CD pipelines, supporting multiple architectures for Docker image builds, specifically amd64 and arm64, is crucial. This ensures compatibility for your team, whether they're on Apple silicon or Intel machines, and aligns with your production infrastructure.
If you don’t have access to Github Runners or Machines in your CI/CD with both architectures, you might be using emulation, like QEMU and it can be quite slow to build an arm64 image on an amd64 machine.
That’s why you can optimize your builds and use cache to make it faster.
This article details a robust 3-step workflow for achieving efficient multiplatform builds and covers best practices for frontend (FE) projects where static assets should be built only once for amd64.
Sponsor
Duplicating microservice environments for testing creates unsustainable costs and operational complexity. Instead, modern engineering teams are adopting application-layer isolation with Signadot’s "sandboxes" - sharing underlying infrastructure while maintaining isolation through smart request routing. This approach cuts infrastructure costs by over 90% while enabling 10x faster testing cycles and boosting developer productivity.
Why multiplatform builds need special cache handling
Docker's BuildKit and buildx make multiplatform builds possible, but cache storage is architecture-specific.
If you build for both amd64 and arm64 in a single step and push cache to a registry, only one architecture's cache is stored due to cache overwriting. This leads to inefficient builds and missed cache hits on subsequent builds for the other architecture.
3-step workflow for optimized multiplatform caching
To ensure both amd64
and arm64
builds benefit from proper cache, follow this three-step process:
1. Build and cache for arm64
(with --load
)
docker buildx build \
--cache-from=type=registry,ref=image:buildcache-arm64 \
--cache-to=type=registry,ref=image:buildcache-arm64 \
--platform=linux/arm64 \
--load .
or for those who use Github Actions:
- name: Build and load cache for arm64
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
load: true
cache-from: type=registry,ref=image:buildcache-arm64
cache-to: type=type=registry,ref=image:buildcache-arm64,compression=zstd,mode=max
env:
DOCKER_BUILDKIT: 1
This command builds only for arm64
, loads the result to the local Docker daemon, and updates the arm64-specific cache in the registry.
2. Build and cache for amd64
(with --load
)
docker buildx build \
--cache-from=type=registry,ref=image:buildcache-amd64 \
--cache-to=type=registry,ref=image:buildcache-amd64 \
--platform=linux/amd64 \
--load .
Similarly, this builds for amd64
and updates the amd64-specific cache in the registry.
3. Build final multiplatform image using both caches
docker buildx build \
--cache-from=type=registry,ref=image:buildcache-arm64 \
--cache-from=type=registry,ref=image:buildcache-amd64 \
--platform=linux/amd64,linux/arm64 \
-t myimage:latest \
--push .
or for those who use Github Actions:
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64, linux/arm64
tags: myimage:latest
cache-from: |
type=registry,ref=image:buildcache-arm64
type=registry,ref=image:buildcache-amd64
env:
DOCKER_BUILDKIT: 1
This step imports both architecture caches, enabling maximum cache reuse, and pushes the multiplatform image to your registry.
Note: Each cache must be exported to a unique registry reference to avoid overwriting.
Registry cache: best practices
Separate cache references: Use distinct registry tags for each architecture (e.g.,
image:buildcache-arm64
,image:buildcache-amd64
).Import multiple caches: The final build should import both caches using multiple
--cache-from
flags.Builder consistency: Perform all steps on the same builder instance to ensure local cache continuity.
Frontend projects: building static assets only once
For frontend projects (e.g., React, Vue, Angular), static assets are architecture-agnostic. There's no need to build them for both amd64
and arm64
. Instead, let’s assume the machine in your CI is amd64:
Force Builder to
amd64
: Use--platform=linux/amd64
or setDOCKER_DEFAULT_PLATFORM=linux/amd64
to ensure static files are always built onamd64
.Import Static Files in Final Multiplatform Layer: In your Dockerfile, copy the prebuilt static assets into the final image layer, which is then assembled as multiplatform.
Example:
# Stage 1: Build static assets (amd64 only)
FROM --platform=linux/amd64 node:18 AS fe-build
WORKDIR /app
COPY frontend/ .
RUN npm ci && npm run build
# Stage 2: Multi-platform runtime
FROM nginx:alpine
COPY --from=fe-build /app/build /usr/share/nginx/html
The
fe-build
stage always runs onamd64
, while the final image can be built for both architectures.
Key takeaways
Build and cache each architecture independently before building the final multiplatform image.
Always use unique cache references for each architecture to avoid cache overwriting.
For frontend projects, build static files only on
amd64
and import them into the multiplatform image.Use Dockerfile's
--platform
flag or environment variables to control build architecture.
By following these steps, you ensure fast, reliable, and cache-efficient multiplatform Docker builds for both backend and frontend projects.