How To Minimize Docker Container Sizes
Some of the most important assets a developer may have are time, money, computing power, and memory space. In this post, I’ll focus on the importance of multi-stage builds to minimize Docker container sizes.
In a previous project, the Credit Card Validator API, I created a Dockerfile to be able to run a web application using Go. Although it’s not shown in the post, it can be accessed from the GitHub repository. Today I’ll expand on that project and minimize the size of the Docker container using multi-stage builds.
Previously the Dockerfile had the whole project copied into the container, run a build, and serve the app. Here is the state of the Dockerfile. Even though this project is small compared to many other projects, the gain you’ll have by reworking the Dockerfile will be massive. Now let’s minimize docker container sizes!
FROM golang:1.19
ENV GIN_MODE=release
ENV PORT=3005
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
COPY validator ./validator
RUN CGO_ENABLED=0 GOOS=linux go build -o /creditcard-validator
EXPOSE $PORT
CMD ["/creditcard-validator"]
Creating a Multi-stage Dockerfile
It is very straightforward as the built binary is placed as an executable and then run immediately. To minimize the content size as I did in deploying a React application in the previous post, we will create a multi-stage build where the first stage is responsible for building the application and the second is the portion where the server is run and served to the outside world.
Unlike the previous post, I will not use Nginx but use the same two images at different stages. The target image is “golang:1.19”. To split it up, let’s take the section needed for the build of a Go application.
FROM golang:1.19 as builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
COPY validator ./validator
RUN CGO_ENABLED=0 GOOS=linux go build -o /creditcard-validator
You will notice that next to the name of the image we as using I gave it a name “builder”. This is important to be able to copy files from this stage to the next. The build binary is what will be passed on to the next stage. Now let’s build the second stage where this executable will be running.
FROM golang:1.19
ENV GIN_MODE=release
ENV PORT=3005
COPY --from=builder /creditcard-validator /creditcard-validator
EXPOSE $PORT
CMD ["/creditcard-validator"]
From the initial stage the binary is copied to the new container environment and a port is exposed. Finally, it is run and it can be accessed as an app.
Analysis of Size Difference
The initial Docker image, tagged as version 1.0, is of size 1.24GB. The second, revised version 1.1, has gone down to 959MB which is a reduction of about 269.8MB! If you had only 5 images standing on your host, this is a reduction of ~1.32GB. When scaled out, removing code not used in production can save you significant memory space. This is the reason why you should use a multi-stage build and minimize docker container sizes.
Thanks for reading this post!
If you’d like to contact me about anything, send feedback, or want to chat feel free to:
Send an email: andy@serra.us
Leave a Reply