Packaging a Golang Application Using Multi-Stage Docker Builds

Packaging a Golang Application Using Multi-Stage Docker Builds

Go Small: Multi-Stage Builds for Tiny Golang Docker Images

Packaging a Golang Application Using Multi-Stage Docker Builds is a technique that allows developers to optimize Docker images of Golang applications by reducing the final image size. This approach is particularly useful for Go applications because it eliminates the need for the compiler and dependencies at runtime, resulting in smaller and more efficient Docker images.

Single Build Stage

Traditionally, a Dockerfile for a Go application would look something like this:

FROM golang:1.21.0-alpine3.17

WORKDIR /app
COPY . .

RUN go build -o goApp .

CMD ["/app/goApp"]

In this approach, the Dockerfile uses the golang:alpine image as the base image to compile the Go application. The code is copied into the image, and then the go build command is executed to build the application. Finally, the CMD instruction specifies the command to run the application.

However, this approach has a downside. It includes unnecessary bloat from the Go compiler, build tools, and dependencies in the final image, resulting in a larger image size. For a simple Go application, the image size can be over 300MB, which is not ideal for production deployments.

Multi-Stage Build

To address this issue, multi-stage Docker builds can be used. With multi-stage builds, the Dockerfile is divided into multiple stages, each with its own base image and set of instructions. The final image only includes the necessary artifacts from the build stage, resulting in a smaller and more optimized image.

Here is an example of a multi-stage Dockerfile for a Go application:

# Build stage
FROM golang:1.21.0-alpine3.17 AS build
WORKDIR /app
COPY . .
RUN go build -o main .

# Run stage  
FROM alpine:3.18.3
WORKDIR /app
COPY --from=build /app/main .
CMD ["/app/main"]

In this example, the Dockerfile consists of two stages Build Stage and Run stage

Build stage

The build stage uses the golang alpine image as the base image and performs the compilation of the Go application, If some external dependencies are required for build, it can be added in the build stage

Run stage

The resulting binary is then copied into the run stage, which uses the alpine image as the base image. The final image only includes the built binary and its dependencies, resulting in a significantly smaller image size.

Benefits of Multi-stage docker build

With multi-stage builds, the runtime image size can be reduced to as little as 15MB, compared to over 300MB in the traditional approach. This reduction in image size has several benefits, including faster image pull and deployment times, reduced storage requirements, and improved overall performance.

Further Optimization

For even further optimization, the Alpine runtime stage can be replaced with a scratch image. The scratch image is a special Docker image that is completely empty, with no operating system or libraries included. This approach further reduces the image size to around 10MB. However, it comes at the cost of losing the tools and package manager provided by the Alpine base image.

Here is an example of a Dockerfile using the scratch image:

# Build stage
FROM golang:1.21.0-alpine3.17 AS build
WORKDIR /app
COPY . .
RUN go build -o main .

# Run stage  
FROM scratch
WORKDIR /app
COPY --from=build /app/main .
CMD ["/app/main"]

Summary

In summary, multi-stage Docker builds are a powerful technique for optimizing Dockerfiles, especially for Go applications. By separating the build tools and dependencies from the runtime content, multi-stage builds significantly reduce the final image size. This reduction in image size has numerous benefits, including improved performance, faster deployment times, and reduced storage requirements.

When using multi-stage builds for Go applications, it is possible to achieve a final image size that is over 90% smaller compared to a single-stage build. Additionally, by using the scratch image as the base image, developers can further minimize the container footprint, resulting in a truly minimal runtime image.

Overall, multi-stage Docker builds are a valuable tool in the developer's toolbox for creating efficient and optimized Docker images. They are particularly beneficial for compiled languages like Go, where the elimination of unnecessary build artifacts can have a significant impact on the final image size. Give multi-stage builds a try for your next Go application and experience the benefits of smaller and more efficient Docker images.

Did you find this article valuable?

Support The Bug Shots by becoming a sponsor. Any amount is appreciated!