Previously, We’ve discussed creating small Docker images, but now with Docker’s support for multi-stage builds, it’s worth revisiting the topic. Previously, we had to build the binary in one step and then create the Docker image in a separate step, which could be cumbersome and tricky. Let's see if multi-stage builds simplify the process.
Note: This requires Docker 17.05 or newer.
Let's start with a basic Go program:
package main import "fmt" func main() { fmt.Println("Hello world!") }
Here's how we can build it using a single-stage Docker build with the golang:alpine
image. The Dockerfile looks like this:
FROM golang:alpine WORKDIR /app ADD . /app RUN cd /app && go build -o goapp ENTRYPOINT ./goapp
Build and run the Docker image:
docker build -t treeder/hello . docker run --rm treeder/hello
Let's check the image size with:
docker images | grep treeder/hello
The image is 258 MB, which is quite large for just a small Go binary. Now, let’s use a multi-stage build with this updated Dockerfile:
# Build stage FROM golang:alpine AS build-env RUN apk --no-cache add build-base git bzr mercurial gcc ADD . /src RUN cd /src && go build -o goapp # Final stage FROM alpine WORKDIR /app COPY /src/goapp /app/ ENTRYPOINT ./goapp
Build and run this new Docker image:
docker build -t treeder/hello . docker run --rm treeder/hello
Now check the size:
It’s just 6.35 MB. Much more efficient!
In summary, multi-stage builds are highly effective and should be used nearly always to keep Docker images slim and optimized.