Modules
Go modules, dependency management, versioning, workspaces, and private module configuration
You are an expert in Go modules and dependency management, helping developers structure projects, manage dependencies, and handle versioning correctly. ## Key Points - Always commit both `go.mod` and `go.sum`. - Run `go mod tidy` before committing to remove unused dependencies and add missing ones. - Use the `internal/` directory for packages that should not be importable outside your module. - Pin major dependency upgrades deliberately; review changelogs. - Use `go mod vendor` if you need hermetic builds or work in an environment without network access. - Set `GOPRIVATE` for organization-internal modules. - Use `go.work` for local multi-module development; do not commit `go.work` to shared repos unless the repo is a monorepo. - **Forgetting `/v2` in import paths**: importing `github.com/foo/bar` when you need `v2` gets the wrong major version. - **Committing `replace` directives for local paths**: breaks builds for everyone else. Use `go.work` instead. - **Running `go get` without `./...`**: `go get` in module mode updates `go.mod` but doesn't build; use `go get ./...` to update and check compilation. - **Not running `go mod tidy`**: `go.mod` may list unused or missing indirect dependencies. - **Circular module dependencies**: Go does not allow circular imports between packages, and circular module dependencies should also be avoided. ## Quick Example ```bash mkdir myapp && cd myapp go mod init github.com/myorg/myapp ``` ```bash # Initialize a workspace go work init ./app ./lib # Add a module to the workspace go work use ./another-module ```
skilldb get go-skills/ModulesFull skill: 217 linesModules — Go Programming
You are an expert in Go modules and dependency management, helping developers structure projects, manage dependencies, and handle versioning correctly.
Core Philosophy
Go modules exist to make builds reproducible, dependencies explicit, and version upgrades deliberate. Unlike package managers that resolve "latest compatible" versions at install time, Go uses Minimum Version Selection (MVS): it picks the minimum version that satisfies all requirements, producing the same build everywhere without a lock file. The go.mod file is both the dependency declaration and the version lock, and go.sum provides cryptographic integrity verification. This design means that two developers running go build on the same commit get identical binaries, period.
The module system enforces semantic versioning as a first-class contract. A major version bump (v1 to v2) changes the import path, which means v1 and v2 can coexist in the same binary without conflict. This is not a convention — it is enforced by the toolchain. The consequence is that breaking changes are impossible to introduce accidentally: if you change a public API, you must bump the major version and update every import path. This makes upgrading dependencies an explicit, reviewable decision rather than a surprise.
Good module hygiene centers on a few disciplines: run go mod tidy before every commit to keep dependencies minimal and accurate, use the internal/ directory to prevent downstream packages from importing implementation details, and treat replace directives as temporary development tools that never ship to production. The goal is a go.mod that any developer can read and understand — every dependency justified, every version intentional.
Anti-Patterns
-
Committing
replacedirectives that point to local paths: Areplace github.com/foo/bar => ../my-local-forkingo.modbreaks the build for every other developer who does not have that directory. Usego.workfor local multi-module development and keepreplaceout of committedgo.modfiles. -
Running
go get -u ./...without reviewing changes: Blindly updating all dependencies to their latest versions pulls in untested changes and potential breaking behavior. Update dependencies individually, read changelogs, and run your test suite after each upgrade. -
Ignoring
go.sumor leaving it out of version control: Thego.sumfile ensures that the exact bytes of every dependency are verified. Without it, builds can silently fetch tampered or different content. Always commitgo.sum. -
Using
GOPATHmode or vendoring without understanding the trade-offs: Go modules are the standard since Go 1.16. Falling back toGOPATHor vendoring without a specific need (air-gapped builds, compliance requirements) adds complexity and confuses contributors who expect standard module tooling. -
Circular module dependencies: While Go prevents circular package imports within a module, it is possible to create circular dependencies between modules (A depends on B, B depends on A). This makes versioning and upgrades painful. Refactor shared code into a third module that both can depend on.
Overview
Go modules (introduced in Go 1.11, default since Go 1.16) are the standard dependency management system. A module is a collection of packages versioned together, defined by a go.mod file at the repository root. The go.sum file provides cryptographic verification of dependencies.
Core Concepts
go.mod
Declares the module path, Go version, and dependencies.
module github.com/myorg/myapp
go 1.22
require (
github.com/go-chi/chi/v5 v5.0.12
golang.org/x/sync v0.6.0
)
Semantic Versioning
Go modules enforce semver: vMAJOR.MINOR.PATCH. Major version 2+ requires a /v2 suffix in the module path.
Minimum Version Selection (MVS)
Go selects the minimum version that satisfies all requirements, producing reproducible builds without a lock file.
go.sum
Contains cryptographic hashes of module content for integrity verification. Always commit this file.
Implementation Patterns
Starting a New Module
mkdir myapp && cd myapp
go mod init github.com/myorg/myapp
Common Commands
# Add a dependency (or just import it and run go mod tidy)
go get github.com/go-chi/chi/v5@latest
# Add a specific version
go get github.com/lib/pq@v1.10.9
# Update all dependencies
go get -u ./...
# Update only patch versions
go get -u=patch ./...
# Remove unused dependencies
go mod tidy
# Vendor dependencies
go mod vendor
# Verify dependency integrity
go mod verify
# Show why a dependency is needed
go mod why github.com/some/dep
# Show dependency graph
go mod graph
Project Layout
myapp/
├── go.mod
├── go.sum
├── main.go # or cmd/myapp/main.go
├── cmd/
│ ├── myapp/
│ │ └── main.go
│ └── mytool/
│ └── main.go
├── internal/ # private packages
│ ├── config/
│ └── database/
├── pkg/ # public library packages (optional convention)
│ └── api/
├── handler/
├── service/
└── repository/
Multi-Module Workspaces (Go 1.18+)
# Initialize a workspace
go work init ./app ./lib
# Add a module to the workspace
go work use ./another-module
go.work file:
go 1.22
use (
./app
./lib
)
This lets you develop multiple modules locally without replace directives.
Replace and Exclude Directives
// Use a local fork during development
replace github.com/broken/lib => ../my-fixed-lib
// Pin to a specific commit
replace github.com/some/dep => github.com/some/dep v0.0.0-20240101120000-abcdef123456
// Exclude a known-bad version
exclude github.com/some/dep v1.2.3
Major Version Upgrades
// go.mod for v2
module github.com/myorg/mylib/v2
// Import path includes /v2
import "github.com/myorg/mylib/v2/pkg"
Private Modules
# Tell Go to fetch directly (skip proxy) for private repos
export GONOSUMCHECK="github.com/myorg/*"
export GONOSUMDB="github.com/myorg/*"
export GOPRIVATE="github.com/myorg/*"
# Or configure .netrc / git credential helper for authentication
Tool Dependencies
// tools.go (or tools/tools.go)
//go:build tools
package tools
import (
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "google.golang.org/protobuf/cmd/protoc-gen-go"
)
Starting with Go 1.24, you can use the tool directive in go.mod instead:
tool (
github.com/golangci/golangci-lint/cmd/golangci-lint
google.golang.org/protobuf/cmd/protoc-gen-go
)
Best Practices
- Always commit both
go.modandgo.sum. - Run
go mod tidybefore committing to remove unused dependencies and add missing ones. - Use the
internal/directory for packages that should not be importable outside your module. - Pin major dependency upgrades deliberately; review changelogs.
- Use
go mod vendorif you need hermetic builds or work in an environment without network access. - Set
GOPRIVATEfor organization-internal modules. - Use
go.workfor local multi-module development; do not commitgo.workto shared repos unless the repo is a monorepo.
Common Pitfalls
- Forgetting
/v2in import paths: importinggithub.com/foo/barwhen you needv2gets the wrong major version. - Committing
replacedirectives for local paths: breaks builds for everyone else. Usego.workinstead. - Running
go getwithout./...:go getin module mode updatesgo.modbut doesn't build; usego get ./...to update and check compilation. - Not running
go mod tidy:go.modmay list unused or missing indirect dependencies. - Circular module dependencies: Go does not allow circular imports between packages, and circular module dependencies should also be avoided.
- Ignoring
go.sumin version control: without it, builds are not verifiable and may fetch tampered content.
Install this skill directly: skilldb add go-skills
Related Skills
Context Patterns
Context usage for cancellation, timeouts, deadlines, and value propagation in Go
Error Handling
Error handling patterns including wrapping, sentinel errors, custom types, and error groups in Go
Generics
Go generics including type parameters, constraints, and generic data structure patterns (Go 1.18+)
Goroutines Channels
Concurrency patterns using goroutines, channels, select, and sync primitives in Go
HTTP Servers
HTTP server patterns using net/http, chi, and gin including middleware, routing, and graceful shutdown
Interfaces
Interface design principles, implicit satisfaction, and composition patterns in Go