
September 09, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Docker Buildx: Multi-Platform Image Builds solve a problem every team hits once Apple Silicon laptops and AWS Graviton servers share the same codebase. You build on one CPU architecture and deploy on another. The image starts, then crashes with exec format error. Buildx extends the Docker CLI with BuildKit backends so one command produces a manifest list covering linux/amd64 and linux/arm64. If you already use multi-stage Dockerfiles for Laravel production, adding platform targets is the next practical step.
docker buildx create --use, add --platform linux/amd64,linux/arm64, and push to your registry.What Are Docker Buildx Multi-Platform Image Builds?
Standard docker build targets your host CPU only. Buildx talks to BuildKit, which can fan out builds across platforms and merge them into one image reference. The registry stores a manifest list. Each platform gets its own digest underneath.
That matters for three common cases. Developers on arm64 MacBooks need images that also run on amd64 cloud VMs. Cost-conscious teams move Laravel and Node workloads to Graviton or Ampere nodes. Edge and IoT deployments often require arm64 exclusively. One tag simplifies deploy scripts and Kubernetes pull policies.
Buildx is not a separate product. It ships with Docker Desktop and recent Docker Engine packages on Ubuntu. You enable the plugin, create a builder instance, and point your existing Dockerfile at multiple platforms. The workflow pairs well with multi-stage builds that shrink image size because each platform stage compiles independently.
Buildx vs Classic docker build
| Feature | docker build | docker buildx build |
|---|---|---|
| Multi-platform output | No (host arch only) | Yes via --platform |
| BuildKit features | Optional, limited | Full cache, secrets, attestations |
| Manifest list push | Manual tooling | Built in with --push |
| Remote builders | No | Yes (docker-container driver) |
| CI fit | Single arch per job | One job, all platforms |
Official docs describe Buildx as the recommended path for cross-compilation. See the Docker Buildx documentation for driver details and the multi-platform build guide for registry behaviour.
How Do You Set Up a Buildx Builder for Multi-Platform Builds?
Start on a machine with Docker Engine 24 or newer, or Docker Desktop. Confirm the plugin exists. Then create a dedicated builder that supports multiple platforms.
- Verify Buildx is available:
docker buildx version
docker buildx ls - Install QEMU binfmt handlers so non-native platforms can run inside build containers:
docker run --privileged --rm tonistiigi/binfmt --install all - Create and select a builder using the
docker-containerdriver:
docker buildx create \
--name multiarch \
--driver docker-container \
--bootstrap \
--use
docker buildx inspect --bootstrap The inspect output should list linux/amd64, linux/arm64, and possibly linux/arm/v7 under platforms. If you only see one platform, binfmt is missing or the builder driver is wrong.
On Ubuntu servers I maintain for client projects, I follow the same baseline as our Docker install on Ubuntu guide. Pin the Docker package version in production. Test the builder after every kernel upgrade.
Builder driver choices
The default docker driver works for single-platform local builds. Multi-platform builds need docker-container because it runs BuildKit inside a container with full isolation. Remote teams sometimes use the kubernetes driver to schedule build pods across arm and amd64 nodes. That removes QEMU overhead for the non-native arch.
For a single VPS running Laravel queues and a registry mirror, the container driver plus binfmt is enough. Keep it simple until build times force native runners.
How Do You Run Docker Buildx Multi-Platform Image Builds?
Point Buildx at your Dockerfile and list every platform you support. Push directly to the registry. Local --load only accepts a single platform, which catches many first-time users.
Single-command build and push
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag registry.example.com/myapp:1.4.2 \
--tag registry.example.com/myapp:latest \
--push \
. BuildKit builds each platform in parallel. It uploads layer blobs once when content is identical. The registry stores one manifest list. Clients pull the variant matching their CPU.
On a production Laravel app I dockerized recently, the pattern matched our Docker Compose multi-container local setup. Only the build command changed. Runtime Compose files stayed identical.
Using buildx bake for teams
When three microservices each need amd64 and arm64, a docker-bake.hcl file beats long shell scripts. Bake inherits targets and keeps CI readable.
// docker-bake.hcl
group "default" {
targets = ["api", "worker"]
}
target "api" {
context = "./services/api"
dockerfile = "Dockerfile"
platforms = ["linux/amd64", "linux/arm64"]
tags = ["registry.example.com/api:${TAG}"]
cache-from = ["type=registry,ref=registry.example.com/api:buildcache"]
cache-to = ["type=registry,ref=registry.example.com/api:buildcache,mode=max"]
}
target "worker" {
context = "./services/worker"
dockerfile = "Dockerfile"
platforms = ["linux/amd64", "linux/arm64"]
tags = ["registry.example.com/worker:${TAG}"]
} TAG=2.0.0 docker buildx bake --push Bake integrates with the same builder instance. Cache exporters cut rebuild time on GitLab CI runners I use for sister legal-tech sites. That pattern aligns with broader build pipeline automation practices.
Inspecting the manifest list
docker buildx imagetools inspect registry.example.com/myapp:1.4.2 You should see separate entries for each platform with distinct digests. If only one platform appears, the build did not finish both targets or push failed silently mid-upload.
Why Do Multi-Platform Builds Fail on CI Pipelines?
Most failures fall into four buckets. I have seen each one break a deploy on real client infrastructure.
- Using
--loadwith multiple platforms. Docker loads one image into the local daemon. Multi-platform output must use--pushor--output type=oci,dest=.... - Missing binfmt on Linux runners. amd64 GitLab runners emulating arm64 without binfmt produce opaque QEMU errors.
- Architecture-specific base images. Some legacy tags publish amd64 only. Pin multi-arch bases like
php:8.3-fpm-bookworm. - CGO and native extensions. PHP extensions, Ruby gems, and Go CGO builds compile per platform. Cache mounts help but cannot skip the compile.
Validate your JSON CI config with the JSON formatter tool before pushing pipeline changes. A trailing comma in a workflow file wastes an hour of build time.
GitLab CI example
This job runs on a single amd64 runner with binfmt installed. It pushes both architectures for a Laravel API.
build-multiarch:
stage: build
image: docker:27-cli
services:
- docker:27-dind
variables:
DOCKER_BUILDKIT: "1"
before_script:
- docker run --privileged --rm tonistiigi/binfmt --install all
- docker buildx create --use --driver docker-container --name ci-builder
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
--tag $CI_REGISTRY_IMAGE:latest \
--cache-from type=registry,ref=$CI_REGISTRY_IMAGE:buildcache \
--cache-to type=registry,ref=$CI_REGISTRY_IMAGE:buildcache,mode=max \
--push \
. Pair this with container image scanning using Trivy on the manifest digest. Scan once per platform or use imagetools to resolve each child digest.
Testing without arm64 hardware
Run the arm64 variant locally through QEMU:
docker run --platform linux/arm64 registry.example.com/myapp:1.4.2 uname -m
# Expected: aarch64 That confirms pull and execution. It does not replace integration tests on real Graviton hardware before you cut traffic over.
How Do You Optimize Docker Buildx Multi-Platform Image Builds for Production?
Multi-platform does not mean multi-megabyte bloat. Apply the same discipline as single-arch production images. Prefer distroless or slim runtime bases when your stack allows it. Keep build tools in earlier stages only.
Dockerfile patterns that cross-compile cleanly
Use explicit platform args when downloading binaries. BuildKit sets these automatically.
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.23 AS build
ARG TARGETOS TARGETARCH
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/app /app
ENTRYPOINT ["/app"] For PHP Laravel apps, compile extensions in the platform stage. Do not copy /usr/lib from a single-arch cache mount. On deployments like Quick And Easy Nepalese Grocery, we kept Composer and npm in build stages. The runtime stage carried only PHP-FPM and opcache config.
Tagging and signing
Follow a consistent scheme from our Docker image tagging strategies article. Immutable SHA tags plus a moving latest or semver tag work well. Sign manifest lists with cosign so policy engines trust both architectures under one signature. See signing container images with Cosign for the full workflow.
Resource limits on build hosts
Parallel platform builds spike CPU and disk. On a 4-vCPU VPS, two emulated compiles can starve other services. Schedule builds off-peak or split platforms across matrix jobs. Production runtime limits differ; read limiting Docker container resources for the deploy side.
When Buildx sits inside a wider release process, document the builder version and binfmt image digest. Reproducible builds matter for audit trails on legal-tech and eCommerce platforms. Our Adventure Third Pole Trek booking platform uses the same GitLab plus registry pattern on shared EC2 infrastructure.
When to skip multi-platform
Not every container needs arm64 on day one. A internal admin tool running on a single amd64 VPS gains nothing from dual-arch builds. The CI cost doubles or triples under emulation. Add platforms when deployment targets actually mix architectures.
For teams without dedicated DevOps staff, managed Linux system administration or ongoing support and maintenance can cover builder setup on Nepali hosting budgets starting around Rs 8,000/month (~USD 60).
Key Takeaways
- Create a
docker-containerBuildx builder and install binfmt before cross-compiling on amd64 Linux. - Always use
--pushor OCI output for multi-platform builds;--loadaccepts one platform only. - Verify manifest lists with
docker buildx imagetools inspectbefore pointing production at a new tag. - Prefer native arm64 and amd64 CI runners over QEMU when Dockerfiles compile PHP, Node, or Go with CGO.
- Combine Buildx with registry cache exporters, Trivy scanning, and Cosign signing for production-grade pipelines.
- Add arm64 only when deploy targets require it; single-arch saves CI minutes on constrained budgets.
People Also Ask
Can Docker Buildx build for platforms without native hardware?
Yes. QEMU user-mode emulation through binfmt lets an amd64 host build arm64 images. Builds run slower than native hardware. Install binfmt with the official tonistiigi/binfmt image before creating your builder.
What is the difference between a manifest list and a multi-stage Dockerfile?
A multi-stage Dockerfile reduces image size by separating build and runtime layers within one architecture. A manifest list points to separate image digests per CPU architecture under one tag. You often use both together.
Does Docker Desktop include Buildx by default?
Modern Docker Desktop ships Buildx and BuildKit enabled. Linux servers need the docker-buildx-plugin package from Docker's official repository. Confirm with docker buildx version.
How do Kubernetes clusters pull the correct architecture?
The container runtime requests the manifest list from the registry. It selects the digest matching the node's CPU architecture automatically. No pod spec change is required if the image tag is multi-platform.
Ship One Tag to Every Architecture
Docker Buildx: Multi-Platform Image Builds turn architecture mismatch from a production incident into a solved CI step. Set up the builder once, pin your base images, push manifest lists, and verify with imagetools. Your amd64 and arm64 nodes pull the same tag without custom deploy logic.
If you want help wiring Buildx into a Laravel pipeline or Graviton migration, see our enterprise application development services or custom software development offerings. For networking and volume questions after deploy, read Docker networking and volumes explained. Contact us to review your Dockerfile and CI setup before the next release.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

