In the world of containerization, optimizing Docker image size is a constant pursuit. One powerful technique that caught my attention is multi-stage builds. This approach involves breaking down the build process into distinct stages, enabling us to efficiently utilize resources and create smaller images.
Consider the case of Go (golang). By compiling the binary in the first stage and passing it to the second stage with the smallest base image possible, we can significantly reduce the overall image size. This two-stage approach allows us to separate resource-intensive tasks from the final lightweight image, resulting in a more efficient and streamlined container deployment.
Dockerfile
Stage 1
We take the official go image
Set work directory to app
copy necessary artifacts and download Go modules
The entire local directory (all files) is copied to the working directory
Pay attention here, to how we are creating the binary. We have used several flags which are necessary for its execution in stage 2.
CGO_ENABLED - This allows the go function to call C from the go code. We are telling go compiler to build such that all the dependencies are here in the binary only, no external dependencies are needed during runtime.
GOOS - Build should be executable with the given OS.
Stage 2
We start the next stage with a very basic image - scratch.
Scratch is a very minimalistic image.
Copy the binary from stage 1 to stage 2
We are using --from=0, here to mention that copy from stage1
We can also use --from=build as we have initiated 1st stage as build
And finally /main which will run the desired process.
Image size ranges from 5M to 30M.
FROM golang:1.20.12-bookworm as build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
# Build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM scratch
COPY --from=0 /job/main /
CMD [ "/main" ]
Happy Coding