With the rise of ARM-based cloud computing resources and Apple’s switch from Intel-based CPUs to Apple Silicon, building Docker Images for multiple architectures has become increasingly important. We, as developers, must ensure that our applications can be executed on every potential architecture. We can easily achieve that by using Docker CLI and the buildx
plugin. This article will look at building Docker Images for multiple architectures by containerizing a simple Go application. Before we dive into building the multi-arch Docker Images, let’s quickly revisit what buildx
is.
- What is buildx
- The Go sample application
- Build for multiple architectures
- Verify multiple architectures in Docker Hub
- What we’ve covered in this article
- Conclusion
What is buildx
buildx
is an open-source Docker CLI plugin that brings enhanced build capabilities based Moby BuildKit to the Docker CLI. It’s worth mentioning that the x
in buildx
stands for experimental, meaning that features and capabilities provided by buildx
are not yet stable. Keep in mind that actual features and command line interface may change. Once a feature provided by buildx
has matured and become stable, it will be integrated into regular Docker CLI commands such as docker build
and others.
buildx
requires at least Docker 19.03
or later. buildx
is already installed on your system, If you’re using Docker for Desktop on Windows or macOS. Also, on Linux, it’s part of your Docker installation if you’ve installed Docker using the DEP or RPM packages. Otherwise, you have to install buildx
manually (following the instructions provided in the GitHub repository).
The Go sample application
As mentioned earlier, we will use a simple HTTP API written in Go for demonstration purposes. You can find the sample application in this GitHub repository. All application code resists in main.go
:
package main
import "github.com/gin-gonic/gin"
type Response struct {
Message string `json:"message"`
}
func main() {
r := gin.Default()
r.GET("/ping", func(ctx *gin.Context) {
ctx.JSON(200, Response{
Message: "pong",
})
})
r.Run(":8080")
}
If we take a look at the Dockerfile
, we will see that there is nothing special. A regular multi-stage Dockerfile
for building Go applications and running them on alpine:latest
:
#build stage
FROM golang:alpine AS builder
RUN apk add --no-cache git
WORKDIR /go/src/app
COPY . .
RUN go get -d -v ./...
RUN go build -o /go/bin/app -v ./...
#final stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /go/bin/app /app
ENTRYPOINT /app
LABEL Name=multiarchdocker Version=0.0.1
EXPOSE 8080
Upon building multi-arch Docker Images, Docker sets a bunch of build arguments (ARG
) that we could use in our Dockerfile
when you need to. Those arguments include things like TARGETPLATFORM
, TARGETOS
, and TARGETARCH
. Check out the documentation here to see the entire list of predefined arguments.
For the scope of this article, we will ignore that our application is running with root
-privileges. However, for production usage and real-world scenarios, you should run your applications using a dedicated, non-privileged user.
Build for multiple architectures
To build multi-arch Docker Images with docker buildx
, we need a builder. The builder is responsible for building our Docker Images for the desired architectures. We can easily create and bootstrap a new builder, as shown here:
# Create a new builder
docker buildx create --name mybuilder --bootstrap --use
Optionally, we can specify which platforms we want our builder to support using the --platform
flag. If we omit the platform
flag - as we did in the example above - we will get a builder to build Docker Images for all supported platforms. We can inspect existing builders to get a list of the supported platforms:
# Inspect our custom builder
docker buildx inspect mybuilder
Name: mybuilder
Driver: docker-container
Nodes:
Name: mybuilder0
Endpoint: unix:///var/run/docker.sock
Status: running
Platforms: linux/amd64, linux/amd64/v2, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
Having the builder in place and knowing all supported platforms, we can use docker buildx build
to build our Docker Image for the architectures we’re interested in. For now, let’s build our Docker Images for linux/arm64/v8
and linux/amd64
:
# build the Docker Image for arm64 and amd64
docker buildx build --push \
--platform linux/arm64/v8,linux/amd64 \
--tag thorstenhans/multi-arch-go:latest \
.
Because we provide the --push
flag, Docker will automatically push our Docker Images to Docker Hub.
Verify multiple architectures in Docker Hub
We can verify if our Docker Image has been pushed correctly to Docker Hub by simply browsing Docker Hub and looking for our new Docker Image (thorstenhans/multi-arch-go
). If we look at Tags, we’ll see the latest
tag persisted for both architectures.
Alternatively, we can use docker buildx imagetools
to inspect the Docker Image (still being persisted only in Docker Hub) directly from the terminal:
# Inspect our multi-arch Image directly from the terminal
docker buildx imagetools inspect thorstenhans/multi-arch-go:latest
Name: docker.io/thorstenhans/multi-arch-go:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:d1bf1e0354a2f3a3087032e23a7194731f9b10d432a088235bf129987f277c6d
Manifests:
Name: docker.io/thorstenhans/multi-arch-go:latest@sha256:7a075078ea365e0d30ccea21bbab5995925ea3f7cd85113195d07821ab4ef5c2
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm64
Name: docker.io/thorstenhans/multi-arch-go:latest@sha256:d176bc796c9823e2778f7df16a0586be1ef009c4d5a4e4ebae60882640585aa6
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/amd64
When we pull the image, Docker will automatically pull the Docker Image matching the host platform from which the docker pull
command was issued.
# Pull the Docker Image from an intel-based mac
docker pull thorstenhans/multi-arch-go:latest
latest: Pulling from thorstenhans/multi-arch-go
213ec9aee27d: Already exists
e10dc8b62d66: Pull complete
776133c6b0e8: Pull complete
Digest: sha256:d1bf1e0354a2f3a3087032e23a7194731f9b10d432a088235bf129987f277c6d
Status: Downloaded newer image for thorstenhans/multi-arch-go:latest
docker.io/thorstenhans/multi-arch-go:latest
To verify which platform was pulled, use docker image inspect
as shown in the following snippet:
# Inspect the pulled Docker Image
docker image inspect thorstenhans/multi-arch-go:latest -f "{{ .Architecture }}"
amd64
Overwrite platform when pulling Docker Images
Although Docker automatically pulls the correct architecture for the host, there are use cases in which we want to pull a Docker Image for a particular platform. We can do so by adding the --platform
flag with the corresponding platform identifier to docker pull
:
# Explicitly pull the linux/arm64 image
docker pull thorstenhans/multi-arch-go:latest --platform linux/arm64
latest: Pulling from thorstenhans/multi-arch-go
9b18e9b68314: Pull complete
3de262401255: Pull complete
e89d527d0602: Pull complete
Digest: sha256:d1bf1e0354a2f3a3087032e23a7194731f9b10d432a088235bf129987f277c6d
Status: Downloaded newer image for thorstenhans/multi-arch-go:latest
docker.io/thorstenhans/multi-arch-go:latest
Again, let’s verify that the image was pulled on our machine with the desired platform:
# Inspect the pulled Docker Image
docker image inspect thorstenhans/multi-arch-go:latest -f "{{ .Architecture }}"
arm64
What we’ve covered in this article
Throughout the article, we covered quite some topics:
- 💡Understood why we need multi-arch Docker Images
- 👋🏻Learned what
buildx
is and how we can get access to it - ⚙️Created a custom builder to create Docker Images for multiple architectures
- 📦Containerized a sample application for
arm64
andamd64
- ✅Verified that a multi-arch Docker Image is correctly persisted in Docker Hub
- ⬇️ Verified that Docker pulled the Docker Image for the correct platform
- ❗️Overwrite the
platform
when pulling a Docker Image
Conclusion
Providing Docker Images for various architectures and platforms has become more and more important in recent years. By leveraging docker buildx build
, we can easily build multi-arch Docker Images and distribute them using either Docker Hub or any other Docker Registry v2 compliant artifact registry.