A Guide to Docker Multi-Stage Builds
Tags: docker, devops, sre, containerapps
Published: 2022-12-22 05:36:15

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)
        }
    }

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"]                               

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

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
    ...

We can see that after building the application we have **app**artifact 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

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"]

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  # <---

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

**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](https://medium.com/@shamimice03/list/1339e15e3304)

### 👉All Articles on Kubernetes
[**All Articles on Kubernetes**](https://medium.com/@shamimice03/list/7ae1a0f96f3b)