Go Modules in Practice: Pinning, Vendoring, and Verifying Dependencies
A practical guide to initializing Go modules, pinning versions, vendoring dependencies, and verifying integrity when builds must be reproducible.
16 Jul 2026, 22:15 UTC

The problem: builds that only work on one machine
A Go project that compiles locally but fails in continuous integration is often a dependency problem, not a code problem. When module versions drift or the download source changes, the same source tree can produce different binaries. The practical fix is to treat the module manifest as part of the source: initialize it, pin versions, record integrity hashes, and verify the set before every build.
Prerequisites
- Go 1.16 or newer installed. Modules are the default dependency mechanism in these versions; older releases require
GO111MODULE=on. - A terminal open in the project root directory.
- Network access to a module proxy or direct version-control hosts, unless you are working entirely from a populated module cache or vendor directory.
- Source control initialized so that
go.modandgo.sumchanges are tracked.
Initialize the module
Run the following command in the project root:
go mod init example.com/myservice
Replace example.com/myservice with the import path your consumers will use. This command creates go.mod with the module path and a go directive that records the language version. It does not download anything yet.
Add and pin dependencies
Add a dependency with an explicit version:
go get github.com/gorilla/mux@v1.8.1
The @v1.8.1 suffix pins a semantic version. go.mod gains a require line, and go.sum stores cryptographic hashes for the downloaded module and its dependencies. Those hashes let the toolchain detect tampering or corruption later.
After adding or removing imports, clean the manifest:
go mod tidy
go mod tidy removes unused requirements and adds missing ones. Avoid go get -u unless you intend to upgrade every dependency; in a module-aware context it can also update the go directive in go.mod.
Override a dependency with replace
When you need a local fork or an unreleased fix, use a replace directive instead of editing go.mod requirements manually:
replace github.com/gorilla/mux => ../mux-fork
This tells the toolchain to load the module from the local path. It is useful for development, but it masks the upstream module: security patches and releases from the original source will not appear until you remove the directive. Verify any replaced source against a trusted copy before committing.
Vendor dependencies for locked builds
Vendoring copies every module needed for the build into a vendor directory:
go mod vendor
When vendor/modules.txt is consistent with go.mod, Go 1.14 and later automatically use the vendor directory. This makes builds independent of the network and the module proxy. Commit the vendor tree if you want fully self-contained source control, but remember that it increases repository size and becomes stale after any dependency change until you rerun go mod vendor.
Expected checks
Run these commands from the module root to confirm the dependency set is healthy.
| Check | Command | What it confirms |
|---|---|---|
| Integrity | go mod verify | Downloaded modules match the hashes in go.sum |
| Compilation | go build ./... | All packages build with the current module set |
| Tests | go test ./... | Dependencies resolve correctly during test execution |
| Manifest hygiene | go mod tidy -v | No unused or missing requirements remain |
| Download source | go env GOPROXY | The proxy setting matches your expected module source |
| Version inventory | go list -m all | Expected module versions are recorded in the build list |
Recovery options
When go mod verify fails
A failure means a downloaded module does not match its recorded hash. This can indicate tampering or a corrupted local cache. If you trust the upstream source, clear the cache and retry:
go clean -modcache
go mod download
If the failure repeats, treat the module as suspect and investigate before proceeding.
When the build breaks after editing dependencies
Run go mod tidy to reconcile imports with the manifest, then go build ./... again. If you use vendoring, rerun go mod vendor so the copied tree matches the updated go.mod.
When the module proxy is unreachable
Check the configured source with go env GOPROXY. If your network blocks proxy.golang.org, you can bypass it:
GOPROXY=direct go build ./...
This fetches modules directly from version-control hosts. It avoids the proxy but also skips the proxy's caching and audit trail, so rely on go.sum and go mod verify for integrity.
When vendoring goes stale
If go build reports inconsistencies between vendor/modules.txt and go.mod, either rerun go mod vendor or delete the vendor directory to fall back to the module cache and proxy.
Limitations to keep in mind
go.sumdetects changes after download; it does not prove the upstream author is trustworthy.- Vendoring trades network independence for repository size and manual synchronization.
replacedirectives are easy to forget and can hide important upstream fixes.- Proxy availability and behavior vary by network and Go version; confirm
go env GOPROXYin every new environment.
A practical final check is to clone the repository into a clean directory, run go mod verify, and execute go build ./... and go test ./.... If those pass without network access to the original proxy, your module setup is reproducible.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.