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 rawpair-builder --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 As you can see, the local node builds amd64 images, the remote node builds arm64 images. I hope you find this useful.

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 rawpair-builder --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
As you can see, the local node builds amd64 images, the remote node builds arm64 images.
I hope you find this useful.