Copy go.mod and go.sum into the build stage first, run go mod download and go mod verify in their own layer, then copy the source and build. The dependency layer is cached until the module files change, and verify checks that downloaded modules match their recorded checksums.
The pattern
FROM golang:1.25 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian13:nonroot
COPY --from=build /out/app /app
CMD ["/app"]
Use the Go version your project requires, and pin the build image more tightly than a bare tag in real use.
What each command is documented to do
The Go command documentation describes go mod download as downloading the named modules, and with no arguments, the modules needed to build and test the main module. It adds that the go command downloads modules automatically during ordinary execution, and that the download command is useful mainly for pre-filling the local cache.
That is exactly the Docker use: pre-fill the cache in a layer that only changes when go.mod or go.sum change.
The documentation describes go mod verify as checking that the dependencies of the current module, stored in the local downloaded source cache, have not been modified since being downloaded. If all modules are unmodified, it prints all modules verified. Otherwise it reports which modules changed and exits with a non-zero status, which fails the Docker build.
Why go.sum matters
The Go project's module guidance says that when you add dependencies, Go tools create a go.sum file containing checksums of the modules you depend on. Go uses this to verify the integrity of downloaded module files. The guidance also says to include go.mod and go.sum in your repository with your code.
In a container build, that means a missing or stale go.sum is a signal to fix, not to work around. A build that regenerates go.sum inside the image silently accepts whatever it downloads.
Settings worth knowing
| Setting | What the Go docs say it does | Docker use |
|---|---|---|
| GOPROXY | URL of the module proxy | Point at an internal mirror if you run one |
| GOSUMDB | Name of the checksum database | Leave the default unless you have a reason |
| mod flag set to readonly | Do not let the build update go.mod | Catch drift in CI |
| mod flag set to vendor | Build from a vendor directory | Offline or hermetic builds |
The build flag documentation notes that if a vendor directory is present and go.mod declares Go 1.14 or higher, the go command acts as if the vendor mode were set. That matters if someone commits a vendor directory and forgets to refresh it.
Pitfalls
- Copying the whole source before go mod download defeats the cache, since any code change invalidates the layer.
- Private modules need credentials. Do not bake tokens into a layer, use a build secret mount.
- A verify step proves the cache matches recorded checksums, not that the modules are safe. A malicious version with a valid checksum passes. That is a job for dependency scanning.
Where Safeguard fits
Safeguard reads go.mod and go.sum to scan Go dependencies and produce an SBOM, and policies can stop a pull request that introduces a blocked module. Try the free scan, or see how a gate behaves in the demo below.
Try it yourself
Open the demo, trigger the gate on a pull request and see how a new dependency is evaluated before merge.
Click through it at your own pace, or open it full screen.
Quick check
Does go mod verify detect a malicious dependency?
No. It detects modification of a module after download compared with recorded checksums. A module that was malicious when first published still verifies.
Should I commit a vendor directory for Docker builds?
It is an option for hermetic builds. If you do, keep it in sync with go.mod, because the go command uses it by default under the conditions in the documentation.
Sources
Last reviewed: 19 October 2026.