I'm building a Docker image for multiple architectures (amd64 and arm64 - both on Linux). I'm using a multistage build - this means using go-alpine image as a builder (to compile my Go source code into an executable), and then copying the executable to a smaller secondary image.
Initially, I was copying the compiled program into an Alpine image, but I decided to switch to a scratch image (because of the security benefit) - this involved changing my build command so that the resultant executable is statically linked. My Dockerfile is now:
FROM golang:1.16 AS builder
WORKDIR /go/src/
RUN go get -d -v ./...
COPY . .
RUN env ${opts} go build -a -installsuffix cgo -o app .
FROM scratch
WORKDIR /root/
COPY --from=builder /go/src/app .
CMD ["./app"]
When I build for amd64, I run
docker build --build-arg opts="CGO_ENABLED=0 GOOS=linux GOARCH=amd64" -t myuser/example:amd64 . - if I inspect this image, it shows "Architecture": "amd64" as expected.
When I build for arm64, I run
docker build --build-arg opts="CGO_ENABLED=0 GOOS=linux GOARCH=arm64" -t myuser/example:arm64 . - if I inspect this image, it shows "Architecture": "amd64" - which isn't what I want.
If I compile the Go code locally by running CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build, and then I run file on the compiler output, it shows that the compiled output is arm64. I assume that the issue is therefore a result of Docker pulling an amd64 base image (since my PC is x86), but I can't figure out how to fix the issue.
Because you stand on AMD machine and docker will default pull AMD golang image to build your project.
There's several way to fix it, but this case I suggest use --platform=$BUILDPLATFORM
https://docs.docker.com/buildx/working-with-buildx/
So, your Dockerfile look like:
FROM --platform=$BUILDPLATFORM golang:1.16 AS builder
ARG BUILDPLATFORM
WORKDIR /go/src/
RUN go get -d -v ./...
COPY . .
RUN env ${opts} go build -a -installsuffix cgo -o app .
FROM --platform=$BUILDPLATFORM scratch
WORKDIR /root/
COPY --from=builder /go/src/app .
CMD ["./app"]
And the build command in your local:
docker build --build-arg BUILDPLATFORM=linux/arm64 --build-arg opts="CGO_ENABLED=0 GOOS=linux GOARCH=arm64" -t myuser/example:arm64 .
But if you're building your Golang image with Github Action or CircleCI, please checkout these blog post below
Golang multi-arch image with Github Action https://namiops.medium.com/golang-multi-arch-docker-image-with-github-action-b59a62c8d2bd
Golang arm image with CircleCI https://namiops.medium.com/golang-arm64-docker-image-with-circleci-arm-machine-8bebf2151b92
The snippets you shared are cross-compiling your Go binary, but like you pointed out, are still using an amd64 base image. To do this, you will need to build a docker container for multiple platforms:
https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/
The example is something like:
docker buildx build \
--platform linux/arm64/v8,linux/amd64 \
--tag your-username/multiarch-example:buildx-latest .
Using the docker platform route, you shouldn't need to use go-multiarch building since the go tool should infer the platform its building for.
As an alternative, you could cross-compile your binary on your host, and then have a docker image for each platform that is based off of the scratch image for that architecture and just copies your executable in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With