SB 4: Multi-stage Builds — Part I — Docker-sizing Your Application
Docker has made the lives of DevOps easier, by providing a platform to run, build and deploy applications inside a virtual environment, aptly named container. A container has everything, software and libraries, to build and run the application.
Building a container image, for an application, requires use of Dockerfile. All instructions related to how to build and run the application, are written in this file.
But, there are two things to consider,
- Once the application has been built, do we need all the libraries which were installed?
- How big or small should our docker image be?
The Application
Let’s take an example of a small GoLang application, which handles a single incoming request.
Endpoint: GET /cars
Content-Type: application/json
The application tree structure is as follows,
docker-compose.yml
version: "2"
services:
app:
build:
context: ./src
dockerfile: Dockerfile.All
ports:
- "8080:8080"
volumes:
- ./src:/app
Dockerfile.All
FROM golang:1.13-alpine3.11RUN apk add git bashENV GO111MODULE=onENV GOPROXY=directEXPOSE 8080WORKDIR /appADD . .RUN go build -i -o /bin/api main.goCMD [ "/bin/api" ]
Building the docker image,
> docker-compose -f docker-compose.yml build app
Let’s check out the size of the docker image.
415MB!!! Wait! What?
That is a lot, considering the application handles just one incoming request.
Now, let us answer our original considerations,
- Once the application has been built, do we need all the libraries which were installed? Of course not.
- How big or small should our docker image be? Relatively small.
Introducing Multi-stage builds
Docker provides a solution. It allows us to break the application building process into stages, with each stage having its own purpose.
Consider our example. We can break down the build into 2 stages.
The first stage: Build the application
Docker provides us with an option to name our build stages. Let’s call the first stage builder. We have then added instructions to install all the required libraries, and build the application.
# first stageFROM golang:1.13-alpine3.11 AS builderRUN apk add git bashENV GO111MODULE=onENV GOPROXY=directWORKDIR /appADD . .RUN go build -i -o /bin/api main.go
The second stage: Run the executable
Here we just copy the executable from the first stage, i.e. builder, and run it.
# second stageFROM alpine:3.11COPY --from=builder /bin/api /bin/EXPOSE 8080CMD [ "/bin/api" ]
Let’s build the application, and check its size again.
Just 13.4MB. Wow!!!
That is a massive drop in size. We have successfully removed all the clutter, all that was not required, and we can run the application successfully.
References
- Use multi-stage builds — https://docs.docker.com/develop/develop-images/multistage-build/
- Docker Image Size — Does It Matter? — https://semaphoreci.com/blog/2018/03/14/docker-image-size.html
About the author
Subrata Halder is a Technical Lead at Sureify. He has 8 years of experience, working in back-end technology like PHP and GoLang. His interests include watching movies (he is a big Christopher Nolan fan), and cooking.