This post is a companion piece to a talk I gave at GopherCon 2023.
You don’t need a Linux distro in your Docker image to run a
statically linked Go binary. Docker has a special base image called
scratch
that is empty.
It allows your binary to run directly against the kernel while remaining
isolated from the host system.
While we still need a distro to build the binary, we can use scratch
as the base image for the final stage.
Docker only includes the files that are in the final stage in the image. This means that we can make our
image much smaller, allowing them to be shipped and downloaded faster. It also reduces the attack surface
of the image, because there is no shell available to run commands and exploits.
Let’s take a standard Dockerfile for a Go web app and see how we can use scratch
to make it
smaller, easier to ship, and more secure.
FROM goland:1.21-alpine
COPY ./webapp/* .
RUN go build -o /webapp
EXPOSE 8080
CMD ["/webapp"]
The image size of the generated Docker image is 298 MB, even for a simple 16 line Go program.
We can split our Dockerfile into two stages: one for building the binary and one for running it.
# STAGE 1: Build
FROM goland:1.21-alpine AS build
COPY ./webapp/* .
RUN go build -o /webapp
# STAGE 2: Run
FROM scratch
COPY --from=build /webapp /webapp
EXPOSE 8080
CMD ["/webapp"]
This Docker file uses the Alpine distro in the first stage to build the binary. Then the binary
is copied into the second stage which uses scratch
as the base image. The resulting image is 6.72 MB.
That is a 98% reduction in size.