Bite-Sized Dev Tips

Bite-Sized Dev Tips

The goal is to help you, the developer, by turning complex things into understandable, bite-sized pieces.

How to Get Small-Sized Go (Golang) Docker Images

Learn how to get small-sized Go (Golang) Docker images

Geoff Safcik

3-Minute Read

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!

comments powered by Disqus

Recent Posts

categories