How to Get Small-Sized Go (Golang) Docker Images
Learn how to get small-sized Go (Golang) Docker images
NOTE: this post will be more for Docker Desktop for Mac but this is applicable across devices.
Go (Golang) - a seriously bloated Docker image. Before we optimize your Go image, we’ll first explore the official Go Docker image (well, actually, there’s several of them). Here’s the official Docker page: https://hub.docker.com/_/golang
Let’s look at the default (latest) image (as of this writing it’s 1.14.7
), using docker pull golang
.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
golang latest baaca3151cdb 4 days ago 810MB
Holy crap! 810MB
just for the base image.
Let’s pull the Alpine version (they’re notoriously smaller after all), using docker pull golang:1.14-alpine
.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
golang alpine cda74ca0cbba 4 days ago 370MB
Well, that’s better I guess. Hmmm, is there any way to get our image smaller? Let’s start with the Alpine version so we can at least start with a smaller base image size.
First, let’s create the most interesting Go program ever.
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
Yep, that’s it!
Now, let’s create our very simple Dockerfile
.
FROM golang:1.14-alpine
RUN apk update
RUN apk add --no-cache git ca-certificates tzdata && update-ca-certificates
COPY . .
RUN go get -d -v ./...
RUN go build -o /bin/my-service
CMD ["/bin/my-service"]
And just to prove it works (for the fun of it):
$ docker run --rm hello-test
Hello, world
Here’s the result you’ve all been waiting for…
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-test latest cf8d78b289e1 10 seconds ago 392MB
Or…maybe not. This is the simplest program we can make and it’s 392MB…yowzer!
Docker Multistage Builds (and Scratch) to the rescue!
I think it’s time to introduce you to Docker’s Multistage Builds. Also, we’ll need to use a Scratch container (yes, a real thing!). Since our goal is to make a lean Docker image, we’ll definitely need to take this approach!
We’re going to rework our Dockerfile a bit…
########################################################
# STEP 1: Build your binary
########################################################
FROM golang:1.14-alpine AS builder
RUN apk update
RUN apk add --no-cache git ca-certificates tzdata && update-ca-certificates
COPY . .
RUN go get -d -v ./...
RUN go build -o /bin/my-service
########################################################
# STEP 2: Use Scratch to build your smallest image
########################################################
FROM scratch
COPY --from=builder /etc/ssl/certs/* /etc/ssl/certs/
COPY --from=builder /bin/ /bin/
CMD ["/bin/my-service"]
Now, if we build it, we get our result of:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-test-multistage latest 0754ca958532 4 seconds ago 3.56MB
“Can I get an Amen?!” (you know the emphasis haha)
Just in case you missed that, it went down from hundreds of MB to under 4MB!
And just to prove it works again…
$ docker run --rm hello-test-multistage
Hello, world
There you have it! The power of those Multistage Builds (with the Scratch container and Go).
These examples are just to show you the options. Of course, you can tweak your own service and get your own varied results…try it!
We’ll stop there and hope this helps you!