DEV Community

Andrea Mancuso
Andrea Mancuso

Posted on • Edited on

Multi-Arch Docker Builds at €14/Month - Just 2 VPSs

You don't need QEMU, GitHub Actions, or expensive CI services to build multi-architecture Docker images. With just two VPSs (one cheap ARM machine and one cheap x86 machine) you can build and push native linux/amd64 and linux/arm64 containers without emulation, without performance penalties, and without a monthly surprise bill. My setup costs just €14/month in total and reliably builds for both architectures using Docker's buildx feature.

The key is combining docker buildx with SSH-based Docker contexts: each VPS becomes a node in a custom builder instance. The x86 VPS builds amd64 images locally, while the ARM VPS handles arm64 images natively. BuildKit takes care of the rest: parallel builds, multi-platform manifests, and direct pushes to the registry. It's clean, fast, and yours. No cloud CI, no opaque runners; just you, your servers, and full control over your builds.

Set up the local amd64 docker node:

docker buildx create --name multiarch --use --platform linux/amd64

Set up the remote arm64 docker node:

docker context create arm-vps --docker "host=ssh://user@your.arm.vps.ip"

Append the arm64 remote builder:

docker buildx create --append --name multiarch --platform linux/arm64 arm-vps

Inspect:

docker buildx inspect multiarch --bootstrap

Name: multiarch Driver: docker-container Last Activity: 2025-04-26 16:31:34 +0000 UTC Nodes: Name: multiarch0 Endpoint: unix:///var/run/docker.sock Status: running BuildKit daemon flags: --allow-insecure-entitlement=network.host BuildKit version: v0.20.2 Platforms: linux/amd64*, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386 Labels: org.mobyproject.buildkit.worker.executor: oci org.mobyproject.buildkit.worker.hostname: baab70c53c2d org.mobyproject.buildkit.worker.network: host org.mobyproject.buildkit.worker.oci.process-mode: sandbox org.mobyproject.buildkit.worker.selinux.enabled: false org.mobyproject.buildkit.worker.snapshotter: overlayfs GC Policy rule#0: All: false Filters: type==source.local,type==exec.cachemount,type==source.git.checkout Keep Duration: 48h0m0s Max Used Space: 488.3MiB GC Policy rule#1: All: false Keep Duration: 1440h0m0s Reserved Space: 9.313GiB Max Used Space: 93.13GiB Min Free Space: 47.5GiB GC Policy rule#2: All: false Reserved Space: 9.313GiB Max Used Space: 93.13GiB Min Free Space: 47.5GiB GC Policy rule#3: All: true Reserved Space: 9.313GiB Max Used Space: 93.13GiB Min Free Space: 47.5GiB Name: multiarch1 Endpoint: arm-vps Status: running BuildKit daemon flags: --allow-insecure-entitlement=network.host BuildKit version: v0.20.2 Platforms: linux/arm64*, linux/arm/v7, linux/arm/v6 Labels: org.mobyproject.buildkit.worker.executor: oci org.mobyproject.buildkit.worker.hostname: aba4522bbd7d org.mobyproject.buildkit.worker.network: host org.mobyproject.buildkit.worker.oci.process-mode: sandbox org.mobyproject.buildkit.worker.selinux.enabled: false org.mobyproject.buildkit.worker.snapshotter: overlayfs GC Policy rule#0: All: false Filters: type==source.local,type==exec.cachemount,type==source.git.checkout Keep Duration: 48h0m0s Max Used Space: 488.3MiB GC Policy rule#1: All: false Keep Duration: 1440h0m0s Reserved Space: 9.313GiB Max Used Space: 93.13GiB Min Free Space: 47.5GiB GC Policy rule#2: All: false Reserved Space: 9.313GiB Max Used Space: 93.13GiB Min Free Space: 47.5GiB GC Policy rule#3: All: true Reserved Space: 9.313GiB Max Used Space: 93.13GiB Min Free Space: 47.5GiB 
Enter fullscreen mode Exit fullscreen mode

As you can see, the local node builds amd64 images, the remote node builds arm64 images.

I hope you find this useful.

Top comments (0)