Dependency Management

Overview

Go modules, introduced in Go 1.11 and default since Go 1.16, provide built-in dependency management. This chapter covers everything about go.mod, go.sum, versioning, and managing external libraries.

Understanding Go Modules

A module is a collection of packages with a go.mod file that defines: - Module path (import path) - Go version - Dependencies and their versions

Creating a Module

$ mkdir myproject && cd myproject
$ go mod init github.com/username/myproject

This creates go.mod:

module github.com/username/myproject

go 1.26

The go.mod File

Anatomy of go.mod

module github.com/username/myproject

go 1.26

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/stretchr/testify v1.8.4
)

require (
    // indirect dependencies
    github.com/bytedance/sonic v1.9.1 // indirect
    golang.org/x/net v0.10.0 // indirect
)

exclude (
    github.com/broken/pkg v1.0.0
)

replace (
    github.com/old/module => github.com/new/module v2.0.0
    github.com/local/dev => ../local-dev
)

retract (
    v1.0.0 // Contains security vulnerability
    [v1.1.0, v1.2.0] // Accidental release
)

Directives Explained

Directive Purpose
module Module path (required)
go Minimum Go version
require Dependencies with versions
exclude Versions to ignore
replace Substitute modules
retract Mark versions as broken

The go.sum File

go.sum contains cryptographic checksums representing module dependencies:

github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tn+7r7IyQ4PGNmP1c42GGpvQ=

Each entry has: - Module path and version - Hash of module contents (h1:) - Hash of go.mod file

Never edit go.sum manually — it’s managed by Go tools.

Adding Dependencies

Using go get

# Add latest version
$ go get github.com/gin-gonic/gin

# Add specific version
$ go get github.com/gin-gonic/gin@v1.9.1

# Add latest minor version
$ go get github.com/gin-gonic/gin@v1.9

# Add specific commit
$ go get github.com/gin-gonic/gin@a1b2c3d

# Add from branch
$ go get github.com/gin-gonic/gin@main

Automatic Detection

Simply import and run:

import "github.com/gin-gonic/gin"
$ go mod tidy  # Downloads and records dependency

Updating Dependencies

# Update specific package (latest minor/patch)
$ go get -u github.com/gin-gonic/gin

# Update to specific version
$ go get github.com/gin-gonic/gin@v1.10.0

# Update all dependencies
$ go get -u ./...

# Update only patch versions (safer)
$ go get -u=patch ./...

Semantic Versioning

Go modules expect Semantic Versioning:

v1.2.3
│ │ │
│ │ └── Patch: bug fixes (backward compatible)
│ └──── Minor: new features (backward compatible)
└────── Major: breaking changes

Major Version Suffixes

For v2+, the module path includes the major version:

// go.mod
require github.com/user/project/v2 v2.1.0

// import
import "github.com/user/project/v2"

Managing go.mod

go mod tidy

The most important maintenance command:

$ go mod tidy

This: - Adds missing dependencies - Removes unused dependencies - Updates go.sum

Run this frequently, especially before commits.

go mod download

Pre-download all dependencies:

$ go mod download

Useful for CI caching or air-gapped environments.

go mod verify

Check that dependencies haven’t been modified:

$ go mod verify
all modules verified

go mod why

Explain why a dependency is needed:

$ go mod why golang.org/x/net

# github.com/username/myproject
github.com/username/myproject
github.com/gin-gonic/gin
golang.org/x/net/html

go mod graph

Show dependency graph:

$ go mod graph
github.com/username/myproject github.com/gin-gonic/gin@v1.9.1
github.com/gin-gonic/gin@v1.9.1 github.com/bytedance/sonic@v1.9.1
...

Vendoring

Copy dependencies into your repository:

# Create vendor directory
$ go mod vendor

# Build using vendor
$ go build -mod=vendor

Vendoring pros: - Reproducible builds - Works offline - Audit dependencies

Vendoring cons: - Bloated repository - Manual updates

Replace Directive

Local Development

Replace a module with a local path:

replace github.com/some/package => ../local-package

Fork Substitution

Use your fork instead of original:

replace github.com/original/pkg => github.com/yourfork/pkg v1.2.3

Version Override

Force a specific version:

replace github.com/vulnerable/pkg v1.0.0 => github.com/vulnerable/pkg v1.0.1

Private Modules

GOPRIVATE

Configure private repository patterns:

$ go env -w GOPRIVATE=github.com/mycompany/*,gitlab.internal.com/*

Git Credentials

For private repos, configure Git:

# Use SSH
$ git config --global url."git@github.com:".insteadOf "https://github.com/"

# Or use access token
$ git config --global url."https://token:${TOKEN}@github.com/".insteadOf "https://github.com/"

Dependency Management Best Practices

1. Pin Dependencies

Always use exact versions in production:

# Good: exact version
$ go get github.com/pkg/errors@v0.9.1

# Risky: floating version
$ go get github.com/pkg/errors@latest

2. Regular Updates

Check for updates periodically:

$ go list -m -u all

3. Minimal Dependencies

Fewer dependencies = fewer problems: - Check if you really need that package - Consider stdlib alternatives - Watch for transitive dependencies

4. Security Scanning

Use vulnerability scanning:

# Built-in (Go 1.18+)
$ go install golang.org/x/vuln/cmd/govulncheck@latest
$ govulncheck ./...

Common Issues

Module Not Found

# Ensure GOPROXY is set
$ go env GOPROXY
https://proxy.golang.org,direct

# Try direct fetch
$ GOPROXY=direct go get github.com/some/package

Version Conflicts

# See what's required
$ go mod graph | grep conflicting-package

# Force specific version
$ go get conflicting-package@v1.2.3

Checksum Mismatch

# Clear cache and retry
$ go clean -modcache
$ go mod download

Summary

Command Purpose
go mod init Create new module
go mod tidy Sync dependencies
go get pkg@version Add/update dependency
go mod download Pre-fetch dependencies
go mod verify Verify checksums
go mod vendor Create vendor directory