Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Docker Buildx: Multi-Platform Image Builds

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.

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 Multi-Platform ArchitectureDeveloperdocker buildxBuildKitbuildx builderRegistrymanifest listlinux/amd64native or QEMUlinux/arm64native or QEMUlinux/arm/v7optional targetRuntime: kubelet or docker pull selectscorrect platform digest automatically
Docker Buildx multi-platform image builds fan out per architecture and publish one manifest list tag to the registry.

Buildx vs Classic docker build

Featuredocker builddocker buildx build
Multi-platform outputNo (host arch only)Yes via --platform
BuildKit featuresOptional, limitedFull cache, secrets, attestations
Manifest list pushManual toolingBuilt in with --push
Remote buildersNoYes (docker-container driver)
CI fitSingle arch per jobOne 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.

  1. Verify Buildx is available:
docker buildx version
docker buildx ls
  1. Install QEMU binfmt handlers so non-native platforms can run inside build containers:
docker run --privileged --rm tonistiigi/binfmt --install all
  1. Create and select a builder using the docker-container driver:
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.

Multi-Platform Build FlowDockerfilebuildx--platformBuildKitparallel--pushStage: amd64compile + layerStage: arm64compile + layerManifestlist tagVerify: docker buildx imagetools inspectregistry.example.com/myapp:1.4.2
Buildx compiles each platform in parallel, assembles a manifest list, and pushes one tag your deploy pipeline can reference.

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 --load with multiple platforms. Docker loads one image into the local daemon. Multi-platform output must use --push or --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.

Native vs Emulated BuildsNative Runnersarm64 node builds arm64amd64 node builds amd64Fast compilesNo QEMU overheadBest for heavy PHP/NodeQEMU EmulationSingle amd64 runnerbinfmt handles arm643-10x slower compileFine for slim imagesOK for small teamsBuildx merges both into one manifest tag
Native per-arch runners speed up Docker Buildx multi-platform image builds; QEMU emulation works but slows compile-heavy Dockerfiles.

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.

Production CI PipelineGit pushbuildxmulti-archRegistrymanifestTrivy scanboth digestsDeployDeployer / Kubernetes pulls correct archamd64 EC2 + arm64 Graviton from same tagRollback: retag previous manifest digestboth platforms roll back together
Production pipelines build multi-platform images once, scan each digest, and deploy a single tag to mixed-arch infrastructure.

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-container Buildx builder and install binfmt before cross-compiling on amd64 Linux.
  • Always use --push or OCI output for multi-platform builds; --load accepts one platform only.
  • Verify manifest lists with docker buildx imagetools inspect before 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

Buildx uses BuildKit to compile one Dockerfile for several CPU architectures, then publish a single manifest list tag. Each platform gets its own digest under that tag.

Yes. Modern Docker Desktop ships with Buildx and BuildKit enabled. On Linux servers, install the docker-buildx-plugin package and confirm with docker buildx version.

Managed Linux system administration covering builder setup starts around Rs 8,000 per month, roughly USD 60, for teams without dedicated DevOps staff on constrained hosting budgets.

You built the image on one CPU architecture and deployed on another, such as arm64 MacBook to amd64 cloud VM or Graviton server. The binary inside the container does not match the host CPU. Buildx multi-platform builds prevent this by producing amd64 and arm64 variants under one tag so each node pulls the correct digest automatically.

Start on Docker Engine 24 or newer, or Docker Desktop. Confirm the plugin with docker buildx version. Install QEMU binfmt handlers using the tonistiigi/binfmt image so non-native platforms can run inside build containers. Create a builder with the docker-container driver, bootstrap it, and select it with --use. Run docker buildx inspect --bootstrap and confirm linux/amd64 and linux/arm64 appear under platforms. On Ubuntu servers, pin the Docker package version and retest after kernel upgrades.

Standard docker build targets your host CPU only and cannot produce multi-platform output without extra tooling. Buildx talks to BuildKit with full cache, secrets, and attestation support. It accepts --platform for multiple architectures and pushes a manifest list with --push built in. Remote builders via the docker-container or kubernetes driver are available only through Buildx. Docker documentation treats Buildx as the recommended path for cross-compilation in modern pipelines.

Docker loads one image into the local daemon at a time. Multi-platform output must go to a registry with --push or to disk with --output type=oci,dest=.... Using --load with two platforms is one of the most common first-time mistakes and produces confusing errors in local and CI workflows. Push to your registry, then pull the variant you need for local testing with docker run --platform.

Yes. QEMU user-mode emulation through binfmt lets an amd64 host compile arm64 images and vice versa. Install binfmt before creating your builder on Linux CI runners. Emulation works but runs noticeably slower than native hardware, especially for compile-heavy Dockerfiles with PHP extensions, Node native modules, or Go CGO. Native per-arch runners on mixed GitLab or Kubernetes infrastructure remove most QEMU overhead when build times become painful.

A multi-stage Dockerfile separates build tools from runtime layers within one architecture to shrink image size. A manifest list is a registry object pointing to separate image digests per CPU architecture under one tag. Clients pull the variant matching their node automatically. Production Laravel and Node pipelines often combine both: multi-stage Dockerfiles keep images lean, and manifest lists let the same tag run on amd64 VPS nodes and arm64 Graviton instances without separate deploy scripts.

The container runtime requests the manifest list from your registry when a pod starts. It selects the digest matching the node CPU architecture automatically. No pod spec change is required if the image tag is truly multi-platform. If only one platform exists in the manifest, arm64 nodes on Graviton may fail while amd64 nodes succeed, which is why imagetools inspect before rollout is worth the extra minute in production pipelines.

Most failures fall into four buckets I have seen on real client infrastructure. Using --load with multiple platforms instead of --push. Missing binfmt on amd64 GitLab runners emulating arm64, which produces opaque QEMU errors. Architecture-specific base images where legacy tags publish amd64 only; pin multi-arch bases like php:8.3-fpm-bookworm. CGO and native extensions that must compile per platform and cannot be skipped even with cache mounts. Validate CI JSON config before pushing; a trailing comma can waste an hour of build time.

The default docker driver works for single-platform local builds only. Multi-platform builds need the docker-container driver because it runs BuildKit inside a container with full isolation and platform support. Remote teams sometimes use the kubernetes driver to schedule build pods across arm and amd64 nodes, which removes QEMU overhead for the non-native architecture. For a single VPS running Laravel queues and a registry mirror, container driver plus binfmt is enough until build times force native runners on each architecture.

Run docker buildx imagetools inspect against your tag, for example 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 the push failed silently mid-upload. Pair this check with Trivy scanning on the manifest digest or each child digest resolved through imagetools before pointing production deploy scripts or Kubernetes at a new tag.

Apply the same discipline as single-arch production images: slim or distroless runtime bases, build tools confined to earlier stages. Use explicit platform args when downloading binaries; BuildKit sets BUILDPLATFORM, TARGETOS, and TARGETARCH automatically. For PHP Laravel apps, compile extensions in the platform stage rather than copying libraries from a single-arch cache mount. Enable registry cache exporters in bake or build commands to cut rebuild time. Sign manifest lists with cosign. Schedule parallel platform builds off-peak on small VPS hosts because two emulated compiles can starve other services.

Not every container needs arm64 on day one. An internal admin tool running on a single amd64 VPS gains nothing from dual-arch builds, and CI cost doubles or triples under emulation. Add platforms only when deployment targets actually mix architectures, such as developers on Apple Silicon laptops deploying to amd64 cloud VMs, cost-conscious moves to Graviton or Ampere nodes, or edge deployments requiring arm64 exclusively. Single-arch saves CI minutes on constrained budgets until architecture diversity becomes a real deploy requirement.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: