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.
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
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
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.
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
# 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"]
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.