A multi-stage build compiles in one stage and copies only the result into a small final image.
FROM golang:1.15 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app .
FROM alpine:3.12
RUN adduser -D app
COPY --from=build /out/app /usr/local/bin/app
USER app
ENTRYPOINT ["app"]
The Go toolchain (hundreds of MB) stays in the build stage; the final image is a few MB with a smaller attack surface.
BuildKit, the newer builder shipped with Docker 18.09 and later, builds independent stages in parallel and caches better. Enable it per build:
DOCKER_BUILDKIT=1 docker build -t app:1.0 .
docker images app
On 19.03 BuildKit is opt-in with that variable (or "features": {"buildkit": true} in /etc/docker/daemon.json). Also supported in 19.03: RUN --mount=type=cache for package caches, under the # syntax=docker/dockerfile:1.1-experimental header.