DEV Community

Cover image for A Guide to Docker Multi-Stage Builds
Md Shamim
Md Shamim

Posted on

A Guide to Docker Multi-Stage Builds

A Docker image is built up from a series of layers. Each layer represents an instruction in the image’s Dockerfile. Each layer except the very last one is read-only.

One of the most challenging things about building images is decreasing image size. In this article, we will discuss how we can optimize a docker image size.

Let’s create a custom docker image for a simple golang application.

# app.go

package main

import (
    "fmt"
    "time"
    "os/user"
)

func main () {
    user, err := user.Current()
    if err != nil {
        panic(err)
    }

    for {
        fmt.Println("user: " + user.Username + " id: " + user.Uid)
        time.Sleep(1 * time.Second)
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, let’s write a **Dockerfile **to package the golang application :

# Dockerfile

FROM ubuntu   # Base image 
ARG DEBIAN_FRONTEND=noninteractive   
RUN apt-get update && apt-get install -y golang-go    # Install golang
COPY app.go .                                         # Copy source code                 
RUN CGO_ENABLED=0 go build app.go                     
CMD ["./app"]                               
Enter fullscreen mode Exit fullscreen mode

Next, Create a **docker image **and run a container from that image :

# Create image from the Dockerfile
>>  docker build -t goapp .
...
Successfully built 0f51e92fe409
Successfully tagged goapp:latest

# Run a container from the image created above
>> docker run -d goapp

04eb7e2f8dd2ade3723af386f80c61bdf6f5d9afe6671011b60f3a61756bdab6
Enter fullscreen mode Exit fullscreen mode

Now, ‘exec’ into the container we created earlier :

# exec into the container
>> docker exec -it 04eb7e2f8dd sh

# list the files
~ ls
app  app.go  bin  boot  dev  etc  home  ...

# run the application 
~ ./app
user: root id: 0
user: root id: 0
user: root id: 0
user: root id: 0
user: root id: 0
...
Enter fullscreen mode Exit fullscreen mode

We can see that after building the application we have appartifact inside the container. If we check the image size which helped us to build our application artifact :

>> docker images goapp

REPOSITORY                  TAG       IMAGE ID       CREATED        SIZE
goapp                       latest    0f51e92fe409   16 hours ago   870MB
Enter fullscreen mode Exit fullscreen mode

The image size is ‘870MB’, but we can slim this down using multi-stage builds. With multi-stage builds, we will use multiple **FROM **statements in our Dockerfile. Each **FROM **instruction can use a different base, and each of them begins a new stage of the build. We can selectively copy artifacts from one stage to another by leaving everything that we don’t want in the final image. To show how this works, let’s adapt the **Dockerfile **from the previous section to use multi-stage build.

We will divide our Dockerfile into two stages. One will be the build stage , which will help us to build our application and generate the artifact. And then we will only copy the artifact from the build stage to another stage and create a tiny production image.

# Dockerfile
# named this stage as builder ----------------------
FROM ubuntu AS builder         
ARG DEBIAN_FRONTEND=noninteractive   
# Install golang
RUN apt-get update && apt-get install -y golang-go   
# Copy source code
COPY app.go .                                             
RUN CGO_ENABLED=0 go build app.go

# new stage -------------------
FROM alpine
# Copy artifact from builder stage                   
COPY --from=builder /app .   
CMD ["./app"]
Enter fullscreen mode Exit fullscreen mode

Now, build the image and check the image size :

>> docker build -t goapp-prod  .

Successfully built 61627d74f8b8
Successfully tagged goapp-prod:latest

>> docker images goapp-prod

REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
goapp-prod   latest    61627d74f8b8   5 minutes ago   8.92MB  # <---
Enter fullscreen mode Exit fullscreen mode

As we can see image size has been reduced significantly. It’s time to check if we can run a container from the image we created.

# create docker container
>> docker run goapp-prod

user: root id: 0
user: root id: 0
Enter fullscreen mode Exit fullscreen mode

Great! We were able to use the tiny production image we created and it is working perfectly.

👉All Articles on Linux

All Articles on Linux

👉All Articles on Kubernetes

All Articles on Kubernetes

Top comments (0)