Cross building ARM images on Docker Desktop

Just recently on Dockercon 2019, Docker announced a great feature from their ARM partnership. Now it's possible to cross-build Docker images for multiple architectures using Docker Desktop.

Docker desktop is currently the best option for developers to build, test and run their applications with portability. This new feature brings the possibility of building container images for ARM and ARM64 architectures in a transparent way with lots of possibilities like running on Amazon A1 instances that can be up to 45% cheaper than Intel, running on Raspberry Pi's or even more powerful ARM SBCs like I used before.

In this article, I will demonstrate using a simple Go application, a Hello World web server, how to leverage Docker desktop with multi-stage Dockerfiles to build your application dynamically inside a container and then generating the multi-arch images for it.

To use the features demonstrated here, you need the edge version of Docker (19.03-beta3) / Docker desktop available here.

package mainimport (
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there %s, I'm a Go webserver!", r.URL.Path[1:])
func main() {
fmt.Println("Go webserver running on port :8080")
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))

As can be seen, the app is just a webserver listening on port 8080 that prints a hello-world phrase, classical.

To build this application, we will use a multi-stage Dockerfile that is a way to build your app inside a “fat” container, with all build pre-reqs and then just copying your files (or file in this case) to a “leaner” container with just the absolute minimum.

# This is the builder container
FROM golang:1.12-alpine AS builder
WORKDIR $GOPATH/src/appADD . $GOPATH/src/app/# RUN go get . # In case your application has dependenciesRUN CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o main .
RUN mv $GOPATH/src/app/main /
# This is the application container
FROM alpine
WORKDIR /RUN apk add --no-cache file && \
rm -rf /var/cache/apk/*
COPY --from=builder /main /mainEXPOSE 8080CMD ["/main"]

In this Dockerfile, I create a container based on Alpine with Go 1.12, copy the source app into it and generate a static build. This is good if you want to use a “scratch” container image instead of Alpine like I used here (just change “FROM alpine” to “FROM scratch” and remove the apk add line). The reason for using Alpine is to have a shell inside the container where I can demonstrate the binaries and architecture.

This just need to be done once after install since Docker will use this builder from now on and have all multi-architecture features enabled and keep the standard ones too.

First list your builders:

default * docker
default default running linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6

Create a new builder:

➜ docker buildx create --name newbuilder
➜ docker buildx ls
newbuilder * docker-container
newbuilder0 unix:///var/run/docker.sock inactive
default docker
default default running linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6

Enable and start it:

➜ docker buildx use newbuilder➜ docker buildx inspect --bootstrap[+] Building 4.9s (1/1) FINISHED
=> [internal] booting buildkit 4.9s
=> => pulling image moby/buildkit:master 4.1s
=> => creating container buildx_buildkit_newbuilder0 0.7s
Name: newbuilder
Driver: docker-container
Name: newbuilder0
Endpoint: unix:///var/run/docker.sock
Status: running
Platforms: linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6

➜ docker buildx ls
newbuilder * docker-container
newbuilder0 unix:///var/run/docker.sock running linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6
default docker
default default running linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6

And there it is, a new builder waiting for jobs.

In fact, this is the easier part since Docker does it all for us. Just run the command:

docker buildx build --platform linux/arm64,linux/amd64 --push -t carlosedp/test:v1  .

And a capture of the fantastic interface:

As can be seen, Docker builds both images in parallel, for AMD64 and ARM64, pushes them to Dockerhub (by using the --push argument) and even builds the Manifest pointing to both images. This way if you docker pull carlosedp/test:v1 from an Intel or ARM64 machine, the correct image will be pulled.

You could also add 32bit ARM image to the build by just changing the --platform parameter to: --platform linux/arm64,linux/amd64,linux/arm/v7.

➜ docker run --rm -p 8080:8080 carlosedp/test:v1

And now browse to http://localhost:8080

There it is, our application!

Digging deeper into the images, let’s run and check the architecture and binary inside them.

docker buildx imagetools inspect carlosedp/test:v1

Our manifest point to both images, an AMD64 and an ARM64.

Lets run a shell inside the AMD64, the native one for my computer:

docker run --rm -it -p 8080:8080 carlosedp/test:v1 sh

Here we see that our container is built for a x86_64 architecture (Intel) and our binary is also a x86–64 format. Also Docker pulled the correct image by just using the manifest (carlosedp/test:v1).

Now lets see the ARM64 one. In this case we need to point the exact image (that we got in the inspect command) since using the manifest would point to our native architecture image.

docker run --rm -it -p 8080:8080 sh

Docker pulled the ARM64 image and we can see it’s an aarch64 architecture and our app binary.

Great that we can build and run ARM images on a Intel machine.

This features demonstrate that Docker brings to the developer the complete arsenal to build, test and run applications for multiple architectures with practically no showstoppers to use the best technology available.

Get yourself a nice ARM SBC like Rock64 or RockPro64 from Pine64, a NanoPC-T4 from FriendlyARM or the new Odroid N2 or Nvidia Jetson Nano to run your personal workloads in a lean and economic platform.

If you need bigger iron or run production workloads, launch some Amazon AWS A1 instances or the fantastic Packet c1.large or c2.large instances. They are cheap and powerful!

Hope you enjoyed the article and please, contact me by email or Twitter for followups!

Writing everything cloud and all the tech behind it. If you like my projects and would like to support me, check my Patreon on

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store