Go: Docker Multistage Build

Hello Gophers! Nowadays, using containers like Docker for application development is very popular because it helps manage environments and libraries consistently, no matter where you are. However, the issue of "bloating" (Docker image bloating), where the image size becomes unnecessarily large, can occur if not managed properly. Some solutions to this problem include:
- Using
.dockerignore
to exclude unnecessary files. - Choosing Distroless/Minimal base images to minimize size.
It's important to understand that the tools used during the build process are often not needed when the application is running. Including everything in the final image can lead to bloating. This article introduces another method to separate build tools, leaving only what is necessary for running the application. This method is called Multi-stage builds.
Using Multi-stage builds helps reduce the size of the container by breaking the process into multiple smaller stages, where each stage passes its output to the next.
Demo
We can write a Go application using the Gin framework.
package main
import "github.com/gin-gonic/gin"
func main()
To clearly demonstrate the difference, we will test building the application using two approaches.
Building with a Regular Dockerfile
FROM golang:1.23-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /api
EXPOSE 8080
CMD [ "/api" ]
Then, build the image using the command docker build -t my-go-app .
.
Building with Multi-stage Builds
We will separate the application build process into two stages: the build stage and the release stage, as follows:
# Build stage
FROM golang:1.23-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /api
# Final stage
FROM gcr.io/distroless/base-debian11 AS release
WORKDIR /
COPY --from=build /api /api
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT [ "/api" ]
Now, build the image using command docker build -t my-go-app:multistage -f Dockerfile.multistage .
. Once both images are built, let's compare the results:
As you can see, using Multi-stage builds significantly reduces the image size compared to the regular approach. A smaller image is easier to use and takes up less space.
Reference: