21 Days of Docker-Day 7-Use multi-stage builds with Dockerfile

Welcome to Day 7 of 21 days of Docker. So far I discussed the basics of Dockerfile and we build few images using those Dockerfile.

Multi-stage builds are a new feature requiring Docker 17.05 or higher on the daemon and client. Multistage builds are useful to anyone who has struggled to optimize Dockerfiles while keeping them easy to read and maintain.

The simplest way to understand multi-stage build is the Dockerfile with multiple from instruction.

As our infrastructure grows, One of the most challenging things about building images is keeping the image size down.

Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image.

Let see with the help of example

  • Create a directory
mkdir single-stage
cd single-stage
  • This is how our source code looks like, it’s a simple go program which prints hello world
package main
import "fmt"
func main() {
fmt.Println("hello world")
  • Let test it locally
$ go run helloworld.go
hello world
  • This is how my Dockerfile looks like
FROM golang:1.13.1
COPY helloworld.go .
RUN GOOS=linux go build -a -installsuffix cgo -o helloworld .
CMD ["./helloworld"]
  • It’s self-explanatory, but basically I am trying to compile the helloworld and trying to create the helloworld binary
  • Let’s build the image
$ docker build -t single-stage .
Sending build context to Docker daemon  3.072kB
Step 1/5 : FROM golang:1.13.1
1.13.1: Pulling from library/golang
4a56a430b2ba: Pull complete
4b5cacb629f5: Pull complete
14408c8d4f9a: Pull complete
ea67eaa7dd42: Pull complete
a2a2197e145e: Pull complete
c805dbe65d37: Pull complete
e3dbca29210b: Pull complete
Digest: sha256:68f8870ee1723cafd45bed29414fbaa151e9bf2bba369c8b4436c08a18907012
Status: Downloaded newer image for golang:1.13.1
---> 52b59e9ead8e
Step 2/5 : WORKDIR /tmp
---> Running in 3167df72fbc2
Removing intermediate container 3167df72fbc2
---> f23ad3b0175a
Step 3/5 : COPY helloworld.go .
---> 3d9633eb783b
Step 4/5 : RUN GOOS=linux go build -a -installsuffix cgo -o helloworld .
---> Running in 35c51c8f9326
Removing intermediate container 35c51c8f9326
---> 5c20d8f64a63
Step 5/5 : CMD ["./helloworld"]
---> Running in 1565b589585e
Removing intermediate container 1565b589585e
---> 3b5798ed94c5
Successfully built 3b5798ed94c5
Successfully tagged single-stage:latest
  • Check it
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
single-stage        latest              3b5798ed94c5        6 seconds ago       814MB
  • Here is the bummer, just to execute this simple binary, I am creating a docker image of 814MB. This looks like a highly inefficient process.
  • If you look at the different layer of this image, we don’t require a bunch of things just to compile and execute the helloworld go binary