Go Modules & Dependencies Intermediate¶
Introduction¶
Go Modules (introduced in Go 1.11, default since Go 1.16) is Go's official dependency management system. It replaces the old GOPATH-based workflow with versioned, reproducible builds. Every Go project is a module defined by a go.mod file. Go's approach to versioning is unique: it uses Minimum Version Selection (MVS) instead of the "latest compatible" strategy used by npm, pip, and Cargo. Understanding modules, semantic versioning, and the go.sum lockfile is essential for any Go developer.
Syntax & Usage¶
Creating a Module¶
This creates a go.mod file:
go.mod File Structure¶
module github.com/user/myproject
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/lib/pq v1.10.9
golang.org/x/sync v0.6.0
)
require (
// indirect dependencies (required by your direct dependencies)
github.com/bytedance/sonic v1.10.2 // indirect
golang.org/x/net v0.20.0 // indirect
)
replace github.com/broken/pkg => github.com/fixed/pkg v1.2.0
retract v1.0.0 // published with critical bug
| Directive | Purpose |
|---|---|
module |
Module path (import path for your module) |
go |
Minimum Go version required |
require |
Direct and indirect dependencies with versions |
replace |
Override module source (local path or alternate module) |
exclude |
Skip specific versions |
retract |
Mark versions of your own module as broken |
go.sum — The Integrity Lockfile¶
go.sum contains cryptographic hashes of every dependency (and its go.mod). It ensures reproducible builds and detects tampering.
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKFbGRn7nbIqu9HhEDnDfBIJo=
Always Commit go.sum
go.sum must be committed to version control. It's your guarantee that every developer and CI system builds with exactly the same dependency bytes.
Essential go mod Commands¶
go get github.com/lib/pq@v1.10.9 # add/update a dependency
go get github.com/lib/pq@latest # get latest version
go get github.com/lib/pq@none # remove a dependency
go mod tidy # add missing, remove unused deps
go mod download # download deps to local cache
go mod verify # verify go.sum hashes match
go mod vendor # copy deps into vendor/ directory
go mod graph # print dependency graph
go mod why github.com/lib/pq # explain why a dependency is needed
go get vs go install
go get manages dependencies in your module's go.mod. go install builds and installs a binary. Since Go 1.17, go get no longer builds binaries — use go install pkg@version instead.
Semantic Versioning in Go¶
Go enforces Semantic Versioning strictly:
v1.2.3
│ │ │
│ │ └── Patch: bug fixes (backward compatible)
│ └──── Minor: new features (backward compatible)
└────── Major: breaking changes
go get github.com/lib/pq@v1.10.9 # exact version
go get github.com/lib/pq@v1.10 # latest patch in v1.10.x
go get github.com/lib/pq@latest # latest stable
go get github.com/lib/pq@master # branch name
go get github.com/lib/pq@abc1234 # commit hash (pseudo-version)
Major Version Suffixes¶
Breaking change? The import path must change.
import "github.com/user/pkg" // v0.x or v1.x
import "github.com/user/pkg/v2" // v2.x
import "github.com/user/pkg/v3" // v3.x
Major Version = New Import Path
This is Go's import compatibility rule: if an old import path works, it must remain backward compatible. Breaking changes require a new major version suffix in the module path. v0.x and v1.x share the same path; v2+ must use /v2, /v3, etc.
Minimum Version Selection (MVS)¶
Go's dependency resolution algorithm is fundamentally different from other package managers.
Your module requires:
- pkg A v1.2.0
- pkg B v1.1.0
pkg A requires:
- pkg C v1.3.0
pkg B requires:
- pkg C v1.5.0
MVS selects: pkg C v1.5.0 (the MINIMUM version that satisfies all constraints)
| Other Package Managers | Go (MVS) |
|---|---|
| Select latest compatible version | Select minimum version satisfying all constraints |
| Lockfile needed to freeze versions | go.mod IS the source of truth |
| Resolver can have conflicts | No conflicts possible — always picks the minimum |
| Non-deterministic without lockfile | Deterministic by design |
MVS means that adding a dependency to your project never downgrades an existing dependency and the build is reproducible without a separate lockfile.
replace Directive¶
Override where Go fetches a module — useful for local development, forks, and patching.
// Use a local copy during development
replace github.com/user/pkg => ../pkg
// Use a fork
replace github.com/original/pkg => github.com/fork/pkg v1.2.3
// Pin to specific commit
replace github.com/user/pkg => github.com/user/pkg v0.0.0-20240101000000-abcdef123456
replace Is Not Transitive
replace directives only apply in the main module (the one you're building). If module A replaces module C, but you depend on module A, the replace does NOT affect your build.
retract Directive¶
Mark versions of your own module as broken or accidentally published.
// go.mod of your module
retract (
v1.0.0 // accidental publish with broken API
[v1.1.0, v1.2.0] // range of broken versions
)
Retracted versions still exist but go get @latest skips them and go list warns about them.
Vendoring¶
go mod vendor # copies all dependencies into vendor/
go build -mod=vendor # build using vendored deps (not module cache)
The vendor/ directory is useful for:
- Air-gapped builds (no network access)
- Auditing dependencies (all code in your repo)
- Faster CI (no download step)
Since Go 1.14, go mod vendor is well-supported. The -mod=vendor flag is auto-detected if a vendor/ directory exists.
Module Proxies (GOPROXY)¶
# Default proxy chain (most Go setups)
GOPROXY=https://proxy.golang.org,direct
# Common configurations
GOPROXY=https://proxy.golang.org,direct # default: Google's proxy, then direct
GOPROXY=off # no network access
GOPROXY=direct # always fetch from source
GOPROXY=https://goproxy.io,https://proxy.golang.org,direct # multiple proxies
| Variable | Purpose |
|---|---|
GOPROXY |
Comma-separated list of module proxies |
GONOSUMDB |
Skip checksum verification for matching modules |
GONOSUMCHECK |
Skip checksum database for matching modules |
GOPRIVATE |
Modules matching this pattern skip proxy AND sumdb |
Private Modules (GOPRIVATE)¶
# Tell Go not to use proxy/sumdb for your private repos
GOPRIVATE=github.com/mycompany/*,gitlab.mycompany.com/*
# Or set individually
GONOSUMDB=github.com/mycompany/*
GONOPROXY=github.com/mycompany/*
Workspaces (go.work) — Multi-Module Development¶
Go 1.18+ workspaces let you work on multiple related modules simultaneously without replace directives.
project/
├── go.work
├── api/
│ ├── go.mod (module github.com/user/api)
│ └── main.go
├── shared/
│ ├── go.mod (module github.com/user/shared)
│ └── utils.go
└── worker/
├── go.mod (module github.com/user/worker)
└── main.go
Changes in shared/ are immediately visible to api/ and worker/ without publishing or replace directives.
Don't Commit go.work
go.work is typically for local development only. Add it to .gitignore. Each module should work independently with its own go.mod.
Quick Reference¶
| Command / Concept | Usage | Notes |
|---|---|---|
| Initialize module | go mod init path |
Creates go.mod |
| Add dependency | go get pkg@version |
Updates go.mod and go.sum |
| Remove unused | go mod tidy |
Cleans up go.mod and go.sum |
| Vendor deps | go mod vendor |
Copies to vendor/ |
| Verify integrity | go mod verify |
Checks against go.sum |
| Dependency graph | go mod graph |
Text-based dep tree |
| Major version | import "pkg/v2" |
Path changes at v2+ |
| Replace | replace old => new |
Local dev, forks |
| Retract | retract v1.0.0 |
Mark your version as broken |
| Workspace | go work init ./a ./b |
Multi-module local dev |
| Proxy | GOPROXY=proxy,direct |
Module download sources |
| Private | GOPRIVATE=pattern |
Skip proxy and sumdb |
Best Practices¶
- Run
go mod tidybefore committing — removes unused dependencies and adds missing ones - Always commit
go.sum— it's your build integrity guarantee - Use semantic versioning for your modules — it's enforced by the ecosystem
- Set
GOPRIVATEfor internal/corporate modules — avoids proxy leaks and sumdb errors - Prefer
go mod tidyover manualgo getfor cleanup — it's idempotent and thorough - Use workspaces for multi-module repos instead of
replacedirectives - Don't commit
go.work— it's a local development convenience - Vendor for reproducibility in CI/CD if your environment lacks reliable network access
- Use
retractwhen you accidentally publish a broken version — don't delete tags
Common Pitfalls¶
Forgetting the /v2 Suffix
// WRONG -- importing v2 without path suffix
import "github.com/user/pkg" // this gets v1!
// RIGHT
import "github.com/user/pkg/v2"
go.mod must also include /v2. Both the import path and module declaration must match.
Using replace for Published Modules
replace is great for local development but don't commit it for published modules. It only applies to the main module, so downstream consumers won't see it.
Not Running go mod tidy
Manually editing go.mod can leave it in an inconsistent state — missing indirect dependencies or stale entries. Always run go mod tidy after making changes.
GOPRIVATE Not Set for Internal Repos
SetGOPRIVATE=github.com/mycompany/* in your environment or CI configuration.
Pseudo-Versions Pinned to Commits
Pseudo-versions are valid but fragile. Prefer tagged releases. If a dependency doesn't tag releases, consider whether it's stable enough to use.Performance Considerations¶
| Scenario | Recommendation |
|---|---|
| Slow CI builds | Use go mod vendor + -mod=vendor to avoid downloads |
| Module proxy latency | Use a closer proxy (GOPROXY=https://goproxy.io,...) or run Athens locally |
| Large dependency tree | go mod graph to audit; remove unused deps with go mod tidy |
| Docker builds | Copy go.mod and go.sum first, run go mod download, then copy source — leverages Docker layer caching |
| Monorepo | Use workspaces (go.work) to avoid multiple replace directives |
| Download cache | GOMODCACHE (default $GOPATH/pkg/mod) is shared across all projects |
Interview Tips¶
Interview Tip
"What is Minimum Version Selection?" MVS is Go's dependency resolution algorithm. Unlike npm or pip that select the latest compatible version, Go selects the minimum version that satisfies all constraints. This makes resolution deterministic, reproducible, and conflict-free — no need for a separate lockfile beyond go.mod.
Interview Tip
"What's the difference between go.mod and go.sum?" go.mod declares your module's path, Go version, and dependency requirements. go.sum stores cryptographic checksums of every dependency to ensure integrity. Both should be committed, but go.sum is a verification file, not a lockfile — MVS makes go.mod itself deterministic.
Interview Tip
"What happens at v2?" Go's import compatibility rule requires that breaking changes change the import path. At v2+, the module path must include a /v2 suffix (e.g., github.com/user/pkg/v2), and all imports must use this path. This allows v1 and v2 to coexist in the same build.
Interview Tip
"How do you handle private modules?" Set the GOPRIVATE environment variable to match your private module paths. This tells Go to skip the public proxy (proxy.golang.org) and checksum database (sum.golang.org) for those modules, fetching directly from the source.
Interview Tip
"What are Go workspaces?" Introduced in Go 1.18, go.work files let you develop multiple modules simultaneously in a single workspace. Changes to one module are immediately visible to others without replace directives or publishing. Ideal for monorepos or cross-module development.
Key Takeaways¶
- Every Go project is a module defined by
go.mod— no moreGOPATHworkflow go.sumensures build integrity with cryptographic checksums — always commit it- Go uses Minimum Version Selection (MVS) — deterministic, conflict-free resolution
go mod tidyis your best friend — run it before every commit- Major versions (v2+) require a path suffix (
/v2) in both module path and imports replaceis for local development;retractmarks your own broken versionsGOPRIVATEis essential for private/corporate modules — prevents proxy and sumdb errors- Workspaces (
go.work) simplify multi-module development withoutreplacehacks - Vendoring (
go mod vendor) provides offline, reproducible builds for CI/CD