In Tutorial #4a and 4b of this series, we used Docker to containerize our Go web application. We ran it and everything worked perfectly! In this tutorial, we do one better and we use some new multistage patterns introduced to Docker 17.05+ that allow us to shrink our container size to a fraction of what it currently is.

Travis Reeder wrote a brilliant Medium article a year ago describing the multistage builder pattern. I link his article below.

In this article, we shall contextualize Travis' approach for our application.

Step 1: (Optional) Getting the code

As usual, the code for our Go application is provided on my GitHub, follow this link and download the source. If you already have the code, move on to Step 2.

Step 2: The New Dockerfile

The Multistage process in a nutshell allows us to use two separate images, the first image is used to build our application binaries using the go tooling and everything else that added to the weight of the container. Once built, this binary can now be understood natively by most Linux container without the need of go tooling/compilers and other tools we wont need. We take this binary and copy it to a fresh base image such as Alpine, which is about 4.15MB (ridiculously tiny). The binary itself is also a few about 6MBs (Depends on your application). We then add a few CA certificates to ensure our image has the tools to communicate with other SSL servers on the internet. We finally take this fresh new image that has our binary, and run our binary on image startup and Voila! A new lightweight application ready to be served.

Here's the new Dockerfile

FROM golang:alpine AS build-env
#Here we give the image a variable name that we can refer to later, we call it build-env
WORKDIR /go/src
ADD . /go/src/welcome-app
RUN cd /go/src/welcome-app && go build -o main
#go build command creates a linux binary that can run without any go tooling.
FROM alpine
#Alpine is one of the lightest linux containers out there, only a few 4.15MB
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk*
WORKDIR /app
COPY --from=build-env /go/src/welcome-app/welcome-app /app
COPY --from=build-env /go/src/welcome-app/templates /app/templates
COPY --from=build-env /go/src/welcome-app/static /app/static
#Here we copy the binary from the first image (build-env) to the new alpine container
EXPOSE 8080
ENTRYPOINT [ "./welcome-app" ]
#We then run it on entry! 

The welcome-app:1.0 refers to the one in Tutorial #4a, welcome-app:1.1, is the one made with the old Dockerfile #4b, and welcome-app:1.2 is the one made with the new multistage Dockerfile.

The new image is a staggering 3.5% the size of the original, with the same functionality.

Conclusion

In this tutorial, we shrank our already created Docker image to a less than a tenth of what it was using Docker's Multistage Builder Pattern. Small containers, not only load up faster, they provide a smaller attack surface from malicious programs and hackers, improving security. In case you want a video on what the Multistage Pattern is all about, Sandeep Dinesh, a Developer Advocate for Google Cloud made this pretty sweet Youtube video describing everything you need to know.

We finally have a suitable container for our application that we can now deploy on Google Kubernetes Engine!

Next Tutorial

In the next tutorial, we finally deploy our Docker container on to Google's Kubernetes Engine.

Other Useful Links

Video by Sandeep Dinesh