Module & Dependency Rules
Semver, minimal dependencies, and internal package boundaries.
Search across all documentation pages
Semver, minimal dependencies, and internal package boundaries.
Quick reference for module authors and platform teams governing go.mod hygiene across services and libraries.
go.mod, go.sum, or import paths.cmd/ binaries.govulncheck and dependency update automation.go.work still need per-module tidiness.| Rule | Command / pattern | Fail signal |
|---|---|---|
| Tidy before merge | go mod tidy | Non-empty diff on go.mod/go.sum |
| Pin Go version | go 1.26 matches CI image | Local/CI toolchain mismatch |
| One module path per repo root (default) | module example.com/foo | Accidental nested modules without go.work |
| Require intentional replaces | replace with comment + issue | Permanent replace to forks |
| Retract bad tags | retract directive in go.mod | Consumers resolve broken pseudo-versions |
| Vendor only with policy | go mod vendor + commit policy | Surprise vendor drift |
| Rule | Library | Application / cmd |
|---|---|---|
| Semver git tags | v1.4.2 required | Optional internal tags |
| Breaking API change | Major bump / v2 module path | Refactor freely inside deploy boundary |
| Pre-release | v0.y.z or internal/ | Feature branches |
| Changelog | CHANGELOG.md or release notes | Deploy notes |
| Consumer upgrade | go get example.com/foo@v1.4.2 | Build image pins commit |
| Rule | Do | Avoid |
|---|---|---|
| Direct deps | Only what package imports | Copy-paste go get stacks from blog posts |
| Indirect awareness | Review go mod why -m | Ignoring transitive bloat |
| Stdlib first | net/http, encoding/json | Heavy framework for one handler |
| Interface boundaries | Small adapters at edges | Importing ORM into domain packages |
| Test deps | require test-only in separate module or build tags | Production imports of testify in library code |
| License scan | Allow-list licenses in platform policy | Unreviewed AGPL transitive deps |
| Rule | Pattern | Enforcement |
|---|---|---|
| internal/ | example.com/foo/internal/bar | Compiler rejects external importers |
| cmd/ | Thin main only | No business logic in main |
| Public API root | Stable packages at module root or pkg/ (team choice) | Document in README |
| No util catch-alls | Split by domain | Review nits on util package growth |
| Import cycles | Refactor via interfaces | go build failure |
| Blank imports | Side-effect registration only | Comment explains driver/plugin |
| Rule | Tool | Cadence |
|---|---|---|
| Vulnerability scan | govulncheck ./... | Every PR touching deps |
| Sum integrity | Commit go.sum | Never .gitignore go.sum |
| Private modules | GOPRIVATE, .netrc/token | Document in onboarding |
| SBOM export | Build pipeline artifact | Release tags |
| Pin CI linter/action versions | SHA or semver tags | Supply-chain policy |
| Rule | Do | Avoid |
|---|---|---|
| Local replace | go.work lists modules | Checked-in replace ../ hacks |
| CI builds each module | Matrix or go work sync | One module green, others broken |
| Publishable modules | Release from module dir without work-only replaces | Publishing with local paths |
module example.com/widget
go 1.26
require (
golang.org/x/sync v0.10.0
)widget/
go.mod
widget.go // exported API
internal/
store/
store.go // not importable outside module
cmd/
widgetctl/
main.go // thin mainTemporary fork fixes with issue link and removal date.
Not a permanent substitute for upstream releases.
Optional.
Libraries publishing for go get must tag semver releases.
Import path includes /v2 suffix; major behavior changes ship there.
Run from module root; commit atomic tidy with the import that caused it.
Investigate if tidy churn is constant - often signals floating versions.
Every direct dependency needs an owner and removal plan.
Prefer stdlib and small focused modules over mega-frameworks for libraries.
Yes with GOPRIVATE and documented versioning.
Treat them like external semver consumers.
Air-gapped builds, reproducible deploys, or policy requiring checked-in sources.
Adds maintenance cost on upgrades.
Mark bad tags so go get avoids them without deleting git history.
Yes in the main module file; use build tags to keep them out of production binaries.
Some teams split integration into separate modules.
Use for pre-release commits; prefer tagged releases for production pins.
Document in Pseudo-versions, replace, and retract.
go mod graph, go mod why -m, and govulncheck reachable analysis.
Block merges on reachable critical CVEs without plan.
Target and stdlib subset differ; keep module tidy but verify imports build on device toolchain.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026