Build Multi-Architecture Docker Images

Back in 2018, I wrote about Multi-Architecture Docker Builds. My main aim then was to run the occasional container image on a Raspberry Pi. Apple’s transition to M1 based machines has increased demand for multi-architecture container images. In this post, I document an improved approach to building multi-architecture images.

I’ll use a Go application to show the build process. It prints the current runtime OS and CPU architecture to the terminal and then exits.

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println("Hello, 世界!")
	fmt.Println("GOOS:", runtime.GOOS)
	fmt.Println("GOARCH", runtime.GOARCH)
}

In the past I needed to use a separate Dockerfile for each architecture. It is now possible to use a single Dockerfile to build multi-architecture images. This is a minimal example of a multi-stage build.

FROM golang:1.18-alpine AS build
LABEL maintainer="Bill Glover"
RUN apk --no-cache add ca-certificates
COPY . /app/
WORKDIR /app
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o /app/app

FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app/app /app
ENTRYPOINT ["/app"]

The command to build multi-architecture images is:

docker buildx build . --platform linux/arm64,linux/amd64 --tag bglovervmw/m1go:latest --push

Running this for the fist time on a default Docker installation will produce an error.

error: multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use")

To fix this, create a new driver. You can name this anything, but I’ve called mine multiarch to remind me why I created it.

docker buildx create --name multiarch --use

Now go ahead and build your multi-architecture image.

docker buildx build . --platform linux/arm64,linux/amd64 --tag bglovervmw/m1go:latest --push

This will build a single manifest for both a linux/arm64 and a linux/amd64 image. Note: This build command will push the image to the final image repository. It is not currently possible to sepate the build from the push.

Running this container on my Apple Silicon machine:

docker run --rm bglovervmw/m1go                                                             
Hello, 世界!
GOOS: linux
GOARCH arm64

Running the same image on my Intel machine:

docker run --rm bglovervmw/m1go
Hello, 世界!
GOOS: linux
GOARCH amd64

I’ll look at building multi-architecture images as part of CI/CD pipelines in a future post.