Go Packages & Modules

go.mod, versioning, dependency management, workspaces, and how Go resolves and fetches code.

go.mod go.sum go get Versioning replace Workspaces Private modules go install
๐Ÿ“ฆ

Module Basics

  A module is a collection of packages with a shared module path.
  Every module has a go.mod file at its root.

  myproject/
  โ”œโ”€โ”€ go.mod          โ† defines module path and Go version
  โ”œโ”€โ”€ go.sum          โ† cryptographic hashes of all dependencies
  โ”œโ”€โ”€ main.go
  โ””โ”€โ”€ internal/
      โ””โ”€โ”€ config/
          โ””โ”€โ”€ config.go   โ† package path: github.com/you/myproject/internal/config
go.mod โ€” anatomy go.mod
// go.mod
module github.com/you/myproject

go 1.22

require (
    github.com/gin-gonic/gin v1.9.1
    golang.org/x/sync v0.7.0
)

require (
    // indirect dependencies โ€” pulled in transitively
    github.com/bytedance/sonic v1.11.3 // indirect
)
Create a new module go mod init
# Create go.mod in current directory
go mod init github.com/you/myproject

# Tidy: add missing, remove unused dependencies
go mod tidy

# Download all dependencies to module cache
go mod download

# Vendor all dependencies into ./vendor/
go mod vendor

# Print module graph
go mod graph
go.sum โ€” content hashing go.sum
# go.sum records SHA-256 hashes for every
# dependency version used, plus its go.mod.
# It is machine-generated โ€” do not edit by hand.
# Commit it to source control.

# Verify all modules against go.sum
go mod verify

# If go.sum is out of date after editing go.mod:
go mod tidy
โฌ‡๏ธ

Managing Dependencies

go get โ€” add and upgrade dependencies go get
# Add a dependency (latest version)
go get github.com/pkg/errors

# Add a specific version
go get github.com/pkg/errors@v0.9.1

# Upgrade to latest patch release
go get github.com/pkg/errors@patch

# Upgrade to latest minor release
go get github.com/pkg/errors@latest

# Upgrade all dependencies to latest minor/patch
go get -u ./...

# Remove a dependency (then go mod tidy to clean up)
go get github.com/pkg/errors@none
go install โ€” install CLI tools go install
# Install a tool to $GOPATH/bin
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# Install a specific version
go install golang.org/x/tools/cmd/goimports@v0.20.0

# go install vs go get:
# go install โ€” for tools you run from the CLI
# go get     โ€” for library dependencies in a module
Listing dependencies go list
# List all direct and indirect dependencies
go list -m all

# List with JSON output
go list -m -json all

# Show why a package is needed
go mod why github.com/pkg/errors

# List outdated dependencies
go list -m -u all
๐Ÿท๏ธ

Versioning

โ„น๏ธ
Go modules use Minimum Version Selection (MVS): the build uses the minimum version of each dependency that satisfies all requirements. This makes builds reproducible without a lock file mechanism separate from go.mod.
  Semantic versioning:   v MAJOR . MINOR . PATCH
                            โ”‚       โ”‚       โ””โ”€โ”€ Bug fixes, no API change
                            โ”‚       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ New features, backwards compatible
                            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Breaking API changes

  Go rule: v2+ modules must change their import path:
    v1:  github.com/you/pkg
    v2:  github.com/you/pkg/v2      โ† new import path
    v3:  github.com/you/pkg/v3
Importing a v2+ module Major versions
// go.mod
require github.com/foo/bar/v2 v2.3.0

// source code โ€” import path includes /v2
import "github.com/foo/bar/v2"

// v1 and v2 can coexist in the same build
import (
    barv1 "github.com/foo/bar"
    barv2 "github.com/foo/bar/v2"
)
Pre-release & pseudo-versions Versions
# Pre-release tag
go get github.com/foo/bar@v1.0.0-beta.1

# Pseudo-version: commit hash before first tag
# v0.0.0-20240115123456-abcdefabcdef
# Generated automatically when pointing to a commit:
go get github.com/foo/bar@main

# Point to a specific commit
go get github.com/foo/bar@abc1234
๐Ÿ”€

replace Directives

replace โ€” local development and forks replace
// go.mod โ€” replace a dependency with a local path
replace github.com/you/shared => ../shared

// Replace with a fork (pinned to a specific version)
replace github.com/original/pkg => github.com/yourfork/pkg v1.2.3

// Replace with a fork at a commit
replace github.com/original/pkg => github.com/yourfork/pkg v0.0.0-20240101000000-abc123
โš ๏ธ
replace directives only apply to the module that declares them โ€” they do not propagate to users of your module. Do not publish a library with local-path replace directives; remove them before tagging a release.
retract โ€” un-publish a bad version retract
// go.mod โ€” retract a version you released by mistake
retract v1.3.0  // critical bug introduced

// Retract a range
retract [v1.3.0, v1.3.2]

// go get will warn about retracted versions
// Existing users are not auto-upgraded
exclude โ€” ban a specific version exclude
// go.mod โ€” prevent a known-bad version from being selected
exclude github.com/foo/bar v1.2.3

// MVS will skip to the next available version
// Useful if a dependency published a broken release
// and you can't wait for them to retract it
๐Ÿ—‚๏ธ

Workspace Mode

โ„น๏ธ
Go workspaces (Go 1.18+) let you work on multiple modules simultaneously without editing go.mod replace directives. A go.work file at the repo root overrides dependency resolution for the listed modules.
  myrepo/
  โ”œโ”€โ”€ go.work           โ† workspace file; not committed to library repos
  โ”œโ”€โ”€ service/
  โ”‚   โ”œโ”€โ”€ go.mod        (module github.com/you/service)
  โ”‚   โ””โ”€โ”€ main.go
  โ””โ”€โ”€ shared/
      โ”œโ”€โ”€ go.mod        (module github.com/you/shared)
      โ””โ”€โ”€ util.go

  service/ imports github.com/you/shared โ€” resolved from ../shared/ locally
go work commands go work
# Create go.work and add current module
go work init ./service ./shared

# Add another module to an existing workspace
go work use ./newmodule

# Sync workspace โ€” update go.work.sum
go work sync

# Disable workspace for a single command
GOWORK=off go test ./...
go.work file anatomy go.work
go 1.22

use (
    ./service
    ./shared
)

// Optional: replace directives here override
// all modules in the workspace
replace github.com/external/dep => ../local-dep
๐Ÿ“

Packages

Package rules and naming package
// Package name is the last element of its import path
// import "github.com/you/proj/config"  โ†’  package config

// All files in a directory share one package name
// Exception: _test.go files may use package foo_test

// Package names: short, lowercase, no underscores
// Good: config, httputil, timeutil
// Bad:  myConfig, http_util, util (too generic)

// Exported identifiers start with a capital letter
func DoWork() {}      // exported
func doWork() {}      // unexported
internal โ€” restrict visibility internal
// Packages under internal/ can only be imported by
// code rooted at the parent of the internal/ directory

myproject/
โ”œโ”€โ”€ internal/
โ”‚   โ””โ”€โ”€ auth/      // importable by myproject/...
โ”‚       โ””โ”€โ”€ auth.go
โ””โ”€โ”€ api/
    โ””โ”€โ”€ api.go     // can import internal/auth

// External packages cannot import internal/auth
// The compiler enforces this โ€” no runtime check
init functions & blank imports init ยท _
// init() runs automatically after package-level vars are initialized
// A package can have multiple init() functions, even in different files
func init() {
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}

// Blank import: import solely for init() side effects
// (e.g., registering a database driver)
import _ "github.com/lib/pq"          // registers postgres driver
import _ "github.com/mattn/go-sqlite3" // registers sqlite3 driver
import _ "image/png"                   // registers PNG decoder

// init() order: dependencies first, then importing package
// Within a package: source file order (alphabetical by filename)
๐Ÿ”’

Private Modules

GONOSUMDB / GONOSUMCHECK GONOSUMDB
# Tell Go not to use the checksum database for private modules
GONOSUMDB=github.com/yourorg/*

# Or set permanently in go env
go env -w GONOSUMDB=github.com/yourorg/*

# Skip sum verification entirely for a pattern
go env -w GONOSUMCHECK=github.com/yourorg/*

# Multiple patterns (comma-separated)
go env -w GONOSUMDB=github.com/yourorg/*,gitlab.yourcompany.com/*
GOPRIVATE & GOPROXY GOPRIVATE
# GOPRIVATE sets both GONOSUMDB and GONOPROXY
go env -w GOPRIVATE=github.com/yourorg/*

# Use a private proxy, fall back to direct
go env -w GOPROXY=https://proxy.company.com,direct

# Bypass proxy for private modules
go env -w GONOPROXY=github.com/yourorg/*

# Direct fetch only (no proxy)
GOPROXY=direct go get github.com/yourorg/secret
๐Ÿ’ก
The simplest setup for a private org on GitHub: go env -w GOPRIVATE=github.com/yourorg/*. This skips both the proxy and the checksum database for all modules under that org. Make sure your SSH or HTTPS credentials are configured for git.
๐Ÿ“‹

Quick Reference

Command / Concept Syntax Notes
Init modulego mod init github.com/you/projCreates go.mod
Tidygo mod tidyAdd missing, remove unused
Downloadgo mod downloadFill module cache
Vendorgo mod vendorCopy deps to ./vendor
Verifygo mod verifyCheck hashes against go.sum
Add dependencygo get github.com/pkg/fooLatest version
Pin versiongo get github.com/pkg/foo@v1.2.3Exact version
Upgrade allgo get -u ./...Latest minor/patch
Remove dependencygo get github.com/pkg/foo@noneThen go mod tidy
Install toolgo install tool/cmd@latestInstalls to $GOPATH/bin
List modulesgo list -m allAll direct + indirect
Why neededgo mod why github.com/pkg/fooExplains dependency chain
Replace (local)replace mod => ../localdirin go.mod; don't publish
Replace (fork)replace orig => fork@v1.2.3in go.mod
Retract versionretract v1.3.0Warn users; don't auto-upgrade
Init workspacego work init ./mod1 ./mod2Creates go.work
Add to workspacego work use ./mod3Appends to go.work
Sync workspacego work syncUpdates go.work.sum
Disable workspaceGOWORK=off go buildOne-off; ignores go.work
internal/ packagepackage internal/authOnly parent tree can import
Blank importimport _ "pkg"Runs init() only; registers drivers
v2 import pathgithub.com/you/pkg/v2Required for breaking changes
Private modulesgo env -w GOPRIVATE=org/*Skips proxy and sum DB