Post

Some notes about Docker multi stage build

Introduction

Containers are a staple in the cloud-native era, and they result from a series of instructions within a file, namely a Dockerfile.

Typically, to build our containers, we use an instruction in the Dockerfile called “FROM.” This instruction tells Docker which base image will be used to build our application, such as Golang, Java, Python, Linux, etc.

The problem arises when we provide suboptimal instructions to a Dockerfile, which may result in a large container image that is cumbersome to build, push, and load.

A feature that helps solve this issue is called multistage build.

Multistage docker

Multistage Build

Multistage builds are a Docker capability that allows the building of a container image in multiple stages. Each stage can use a different base image, and only the necessary artifacts from previous stages are carried over to the next stage.

This approach is quite useful for creating lightweight and clean images, optimizing resources like storage and network bandwidth.

Hands on

Consider a simple Golang “Hello World” application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
	"fmt"
	"net/http"
)

func helloWorld(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, World!")
}

func main() {
	http.HandleFunc("/", helloWorld)
	http.ListenAndServe(":8080", nil)
}

Here is a typical Dockerfile:

1
2
3
4
5
6
7
8
9
FROM golang:1.18

WORKDIR /app

COPY . .

RUN go build -o helloworld

CMD ["./helloworld"]

Let’s build it then:

Build a image with a typical Dockerfile

Now let’s take a look of image size:

1
2
3
➜  go-hello-world docker image ls hello_world
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
hello_world   latest    a8a66b650d58   45 minutes ago   972MB

Now a multistage build approach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM golang:1.18 as builder

WORKDIR /app

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o helloworld .

FROM alpine:latest

WORKDIR /root/

COPY --from=builder /app/helloworld .

CMD ["./helloworld"]

Build:

Build a image with a multistage Dockerfile

At first glance, comparing the two files, it might seem like the multistage approach combines two Dockerfiles into one, right? In truth, that’s exactly what it does, but there’s more to it.

In the FROM command, we use an alias, in this case ‘builder,’ which will be referenced later.

The beauty of multistage builds is highlighted in the COPY line. We copy a binary directly from the previous step (builder) to the current image. This process results in a much more lightweight image, as shown below:

1
2
3
➜  go-hello-world docker image ls hello_world
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
hello_world   latest    78199c3b8083   58 minutes ago   13.6MB

Reducing the size from 972MB to 13.6MB is a tremendous achievement!

Conclusion

Of course, there are many improvements that can be applied to a typical Dockerfile, but using a multistage approach is an excellent pattern for production. With this method, we don’t need to worry about build dependencies or any additional resources for building container images. Everything is contained within the Dockerfile, making it highly repeatable in a CI/CD workflow.

Any sugests or doubt?

Feel free to reach out to me on social media: twitter ,linkedin and github.

You can also email me directly at rmnobarra@gmail.com.

Support

Did you really enjoy my content? Consider buying me a coffee through my Bitcoins wallets:

Donate with Bitcoin

Bitcoin Wallet:

bc1quuv5hml9fjkf7azgwkt4xp867pzdwzyga33qmj

Bitcoin wallet QRCODE

Donate through Lightning Network

Lighting Address:

lnbc1pjue6mkpp5yj737e7fm6efhlj6sns42a875pmkencqmvdshf4ghlnntaet5llsdqqcqzzsxqrrsssp5d9hxl686w839qkwmkm0m30cf5gp4gnzxh68kss393xqzlsg0wr3q9qyyssqp3933zc3fg46nk3vafh63r3lqd0jn2p04w5xrz77h33rrr3xm7ckegg6s2ss64g4rf4jg87zdjzkl5tup7umqpghy2qnk65tqzsnplcpwv6z4c

Lighting wallet QRCODE

Bye!

This post is licensed under CC BY 4.0 by the author.