Write a multi-stage Dockerfile that produces a minimal final image for a Go app.
Build a Go web service into a minimal production image. The build needs the Go toolchain, but the final image must ship only the compiled binary — no compiler, no source, no package manager.
Constraints:
- Two stages: a builder on the Go SDK, and a tiny runtime stage.
- Copy the module files and run
go mod downloadbefore copying source, for cache reuse. - The final stage must NOT contain the Go toolchain.
- Run as a non-root user.
# Fill in a multi-stage build.
FROM golang:1.22
# ... builder stage ...
# ... minimal final stage ...
Write the complete multi-stage Dockerfile.
A builder on golang:1.22 copies go.mod/go.sum, runs go mod download, then builds a static binary. The final stage is FROM gcr.io/distroless/static and does COPY --from=builder of only the binary, running as non-root. The image holds no compiler or source, so it is a few megabytes.
- ✗Removing the toolchain in a later layer instead of using a separate final stage
- ✗Copying source before go.mod/go.sum, busting the module-download cache on code edits
- ✗Thinking a Go binary needs the toolchain present at run time to execute
- →Why can a statically linked Go binary run on
scratchor distroless with no libc? - →How would the equivalent Node multi-stage build differ, given it has no static binary?
Two stages: a fat builder on the Go SDK and a tiny runtime. Module files are copied and downloaded before the source, so edits do not bust the go mod download cache. The distroless/static final stage ships only the binary — no compiler, no source — and runs as non-root.
FROM golang:1.22 AS builder
WORKDIR /src
# Modules before source: this layer survives source edits.
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# A static binary with no external dependencies.
RUN CGO_ENABLED=0 go build -o /app ./cmd/server
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /app /app
USER nonroot
ENTRYPOINT ["/app"]