Optimizing the docker image size for compiled language applications can be done using multi-stage docker build.

This article is written based on an example application written in Golang. This multi-stage build can be applied to any other language such as C#, Java, etc.

Why should we reduce docker image size

By optimizing and reducing the size of docker image, we can speed up the build and deployment of our containers. A simple way to understand this concept is to compare the speed of downloading a 1GB file vs a 10MB file while ignoring the network speed as a variable element.

A docker image containing only what we require also make the image more secure as there are lesser packages in the image that actually reduces the attack surface due to vulnerabilities in the packages.

Creating a helloworld application and using normal docker build

Let's start by creating a hello world application in Go.

package main

import (
        "fmt"
)

func main() {
        fmt.Println("Hello World")
}

Next, let's create a dockerfile to build the above application.

FROM golang:1.18.4-bullseye

ADD . /src
WORKDIR /src

RUN go build helloworld.go

Build the docker image.

docker build -t helloworld:dev .

Check the image size of the built image. You will be surprised a small application like this takes 822MB of space!

~# docker images | grep helloworld
helloworld          dev               ae548d70a93b   13 minutes ago   822MB

Using multi-stage build to optimize image size

Edit your dockerfile above to the following:

FROM golang:1.18.4-bullseye as builder

ADD . /src
WORKDIR /src

RUN go build helloworld.go

FROM debian:bookworm-slim

WORKDIR /app

COPY --from=builder /src/helloworld /app

ENTRYPOINT ["/app/helloworld"]

What was changed above is we introduce a builder stage and a final stage (for deployment) to the docker build process. This is because we do not require the Go SDK in the final stage as Go will compile to an executable file once built. As such, we would only require the executable file to be deployed to the final stage.

To further optimize the space usage, we also chose the slim image for debian bookworm which is the codename for Debian 12. A slim image includes only the minimal packages required to run the operating system, allowing us to reduce the image size significantly while also reducing the attack surface for the packages included.

An debian slim base image is only 72.5MB in size.

~# docker images | grep debian | grep bookworm
debian              bookworm-slim     34cb971e12b4   4 days ago          72.5MB

Build the docker image.

docker build -t helloworld:dev .

Check the image size of the built image.

~# docker images | grep helloworld
helloworld          dev               5b901b9d578d   4 seconds ago    74.3MB

Run the application.

~# docker run -it helloworld:dev
Hello World

There you go, you have successfully created a small docker image to run your application with the usage of multi-stage docker build.

Usage of alpine linux

The choice of operating system for your final stage is highly dependent on your requirements. To further reduce the image size, we can also use alpine linux which is a even slimmer version of linux designed to run on a floppy disk.

An alpine linux base docker image is only 5.27MB in size.

~# docker images | grep alpine
alpine              latest            6e30ab57aeee   7 weeks ago         5.27M

Edit your dockerfile above to the following:

FROM golang:1.18.4-bullseye as builder

ADD . /src
WORKDIR /src

RUN go build helloworld.go

FROM alpine:latest

WORKDIR /app

COPY --from=builder /src/helloworld /app

ENTRYPOINT ["/app/helloworld"]

Build the docker image.

docker build -t helloworld:dev .

Check the image size of the built image.

~# docker images | grep helloworld
helloworld          dev               f323e5a8547e   3 seconds ago       7.07MB

Run the application:

~# docker run -it helloworld:dev
Hello World

Here we have it, a docker image consisting of 7.07MB of space to run our application!

You can refer to the files in GitHub for reference (https://github.com/alexlogy/multi-stage-docker-build-example).