How I Reduced the Size of My GoLang App by 99.38% Using Docker Multi-Stage Builds
How I Reduced the Size of My GoLang App from 652 MB to 4.02 MB Using Docker Multi-Stage Builds
Optimizing Docker image sizes is crucial for fast deployments, minimal resource usage, and efficient scaling. When I first packaged my GoLang application into a Docker container, the resulting image was a massive 652 MB—far too large for a Go application that could be packaged more efficiently. In this blog, I'll walk you through how I reduced the image size by 99.38%, bringing it down to just 4.02 MB using Docker multi-stage builds.
The Problem
Initially, my Docker image contained everything from the Go development environment to dependencies and build tools, which added unnecessary bulk to the final image. The large size not only increased deployment time but also used up more storage, making it inefficient for continuous integration and deployment pipelines.
Why Multi-Stage Builds?
Docker multi-stage builds allow you to optimize the Dockerfile by using multiple FROM
instructions. Each stage of the build uses a separate base image, enabling you to separate the build environment from the runtime environment. This results in much smaller, more efficient Docker images.
The Solution: Multi-Stage Dockerfile
Here’s how I refactored my Dockerfile to take advantage of multi-stage builds and reduce the size of my image:
###########################################
# BASE IMAGE
###########################################
FROM ubuntu AS build
# Install Go dependencies
RUN apt-get update && apt-get install -y golang-go
# Disable Go Modules to ensure dependencies are fetched globally
ENV GO111MODULE=off
# Copy application code to the container
COPY . .
# Build the Go application statically
RUN CGO_ENABLED=0 go build -o /app .
############################################
# HERE STARTS THE MAGIC OF MULTI STAGE BUILD
############################################
# Use the 'scratch' image as the base for the final stage
FROM scratch
# Copy the compiled binary from the build stage
COPY --from=build /app /app
# Set the entrypoint for the container to run the binary
ENTRYPOINT ["/app"]
Explanation of the Dockerfile
Base Image (Build Stage):
The first stage uses the
ubuntu
image, which includes the necessary tools and dependencies to build the Go application.The Go application is built with the
go build
command, producing a statically linked binary located at/app
.
Final Image (Minimal Runtime Environment):
The second stage uses the
scratch
image, which is an empty image with no OS, libraries, or dependencies.Only the compiled binary is copied from the build stage into this minimal image.
The
ENTRYPOINT
sets the binary as the default command for the container.
The Results
Before optimization, my Docker image was 652 MB, but after implementing multi-stage builds, the image size was reduced to a mere 4.02 MB. Here's the breakdown of the size reduction:
Why This Works
Build Stage (Base Image): In this stage, I use the
ubuntu
image to compile the Go application. The Go development environment is needed here to download dependencies and compile the code.Final Stage (Minimal Runtime): By using the
scratch
image in the second stage, I keep the final image as minimal as possible. Only the Go binary is copied over, and no extra dependencies or tools are included. This is why the final image size is so small.
Benefits of Using Multi-Stage Builds
Smaller Image Size: By excluding the unnecessary build environment, we keep the image size small and efficient. In my case, the reduction was 99.38%, which is a significant saving in storage and deployment time.
Improved Deployment Time: Smaller images are faster to pull, meaning the application can start faster and require fewer resources.
Cleaner and More Secure: A smaller image means fewer potential attack vectors and a more secure container for production use.
Conclusion
By switching to a multi-stage Docker build, I was able to optimize my GoLang application’s Docker image from 652 MB to just 4.02 MB, achieving a 99.38% reduction in size. This has not only made my app more efficient but also demonstrates the power of Docker multi-stage builds in reducing bloat and improving overall performance.
If you're looking to optimize your own Docker images, I highly recommend using multi-stage builds. They're an essential tool for maintaining clean, efficient, and secure containerized applications!
~Thank you