Polyglot
Languages Go modules
Go § modules

Modules and packaging

The Go module is the unit of versioned dependency management. A module is declared by a go.mod file at its root; the file specifies the module’s import path, the Go version it targets, and its dependencies (with explicit versions). The go.sum file records cryptographic hashes of dependency modules — admitting reproducible builds. The go command (introduced as the modular successor to the older GOPATH workspace model in Go 1.11) handles module operations: go mod init, go get, go mod tidy, go build, go test, go install. The combination — declarative go.mod, hash-locked go.sum, semver-based dependencies, the central proxy at proxy.golang.org — is the substance of Go’s package management.

A module

A module is a directory tree containing a go.mod file at its root:

myproject/
├── go.mod
├── go.sum
├── main.go
├── internal/
│   ├── auth/
│   │   └── auth.go
│   └── store/
│       └── store.go
└── pkg/
    └── api/
        └── api.go
// go.mod
module github.com/user/myproject

go 1.24

require (
    github.com/some/dep v1.2.3
    golang.org/x/sync v0.5.0
)

The module line declares the module’s import path; subpackages are imported as <module>/subpath:

import "github.com/user/myproject/pkg/api"
import "github.com/user/myproject/internal/auth"

Creating a module

mkdir myproject
cd myproject
go mod init github.com/user/myproject

The conventional module path is the URL where the source is hosted (github.com/user/..., gitlab.com/user/..., custom domains). The path admits Go’s tooling fetching the module directly from the source.

For local-only modules, any path admits initialisation:

go mod init example.com/myproject
go mod init example/myproject                     # also OK

go.mod

The principal directives:

module github.com/user/myproject

go 1.24                                           // language version

require (
    github.com/foo/bar v1.2.3
    golang.org/x/sync v0.5.0
    example.com/dep v0.1.0-alpha.1
)

// Replace a dependency (typically for local development):
replace github.com/foo/bar => ../local-bar

// Replace at a specific version:
replace github.com/foo/bar v1.2.3 => github.com/forked/bar v1.2.3-fix1

// Exclude a specific version:
exclude github.com/buggy/lib v0.5.0

The replace directive is conventional for:

  • Local development — substituting an in-progress local copy.
  • Forks — using a forked version with bug fixes.
  • Vendoring overrides — pointing to a known-good version.

go.sum

The go.sum file records cryptographic hashes of dependency modules:

github.com/foo/bar v1.2.3 h1:Abc123...
github.com/foo/bar v1.2.3/go.mod h1:Xyz789...

The mechanism admits reproducible buildsgo build verifies that each downloaded dependency matches the recorded hash. If a malicious actor modifies a dependency, the hash mismatch is detected.

The go.sum is conventionally committed to version control alongside go.mod.

Adding dependencies

go get github.com/foo/bar                         # latest
go get github.com/foo/bar@v1.2.3                  # specific version
go get github.com/foo/bar@latest                  # explicit latest
go get github.com/foo/bar@master                  # branch
go get github.com/foo/bar@abc1234                 # specific commit

go get -u                                         # update all
go get -u github.com/foo/bar                      # update one

The conventional workflow:

# Edit code to import the new package:
import "github.com/foo/bar"

# Then run:
go mod tidy

go mod tidy adds missing dependencies and removes unused ones; the conventional one-step “make go.mod consistent with the source”.

Removing dependencies

Remove the import from the source, then:

go mod tidy

The dependency is removed from go.mod and go.sum.

Versioning

Go modules use semantic versioning:

v1.2.3
│ │ │
│ │ └─ patch: bug fixes (backward compatible)
│ └─── minor: new features (backward compatible)
└───── major: breaking changes

The v prefix is required. Pre-1.0 versions (v0.x.y) admit breaking changes in any release.

For major versions ≥ 2, the major version becomes part of the import path:

github.com/foo/bar           (v0.x.y or v1.x.y)
github.com/foo/bar/v2        (v2.x.y)
github.com/foo/bar/v3        (v3.x.y)

The mechanism admits two major versions of the same library coexisting in one program — useful during gradual migrations. The discipline is sometimes called semantic import versioning.

The module cache

go get downloads dependencies to a local cache:

$GOPATH/pkg/mod/                                  (module sources)
$GOPATH/pkg/mod/cache/download/                   (download cache)

The cache is shared across projects. Dependencies are typically immutable — once downloaded, a specific module version is never re-downloaded.

To clear the cache:

go clean -modcache

The proxy

Module downloads conventionally route through a module proxy:

GOPROXY=https://proxy.golang.org,direct           # default

The proxy:

  • Caches modules — reduces dependency on the original source.
  • Provides immutability — once downloaded, modules cannot disappear.
  • Admits private modules — the GOPRIVATE variable bypasses the proxy for specific paths.
GOPRIVATE=*.corp.example.com,github.com/myorg/*

The conventional public proxy is proxy.golang.org, run by Google.

Internal packages

Subpackages under internal/ are visible only to packages whose import path is rooted at the parent of internal/:

github.com/user/myproject/
├── pkg/api/                                      (importable from anywhere)
└── internal/auth/                                (importable only by myproject)

The mechanism admits exposing helpers without making them part of the public API.

Vendoring

The go mod vendor command produces a vendor/ directory containing the source of all dependencies:

go mod vendor                                     # produces vendor/
go build -mod=vendor                              # build using vendor (default in vendored projects)

The mechanism admits:

  • Reproducibility — the source is committed to the project.
  • Offline builds — no network access required.
  • Dependency review — visible in version control.

The conventional discipline:

  • For libraries — do not vendor.
  • For applications — vendor or rely on the proxy; either is acceptable.

Workspace mode (Go 1.18+)

The go.work file admits multi-module workspaces:

workspace/
├── go.work
├── module1/
│   ├── go.mod
│   └── ...
├── module2/
│   ├── go.mod
│   └── ...
// go.work
go 1.24

use (
    ./module1
    ./module2
)

The go.work admits developing multiple modules side-by-side without replace directives. The mechanism is conventional for monorepos with multiple Go modules.

go work init                                      # create a workspace
go work use ./module1                              # add a module
go work sync                                       # sync go.work with go.mod files

The go.work is conventionally not committed to version control — it is local to the developer.

Common patterns

Standard project layout

myproject/
├── go.mod
├── go.sum
├── README.md
├── cmd/
│   └── myserver/
│       └── main.go                               (the entry point)
├── internal/
│   ├── auth/
│   ├── store/
│   └── api/
├── pkg/                                           (public, optional)
│   └── client/
└── tests/
    └── integration/

The conventional structure:

  • cmd/ — entry-point binaries; one subdirectory per binary.
  • internal/ — private packages.
  • pkg/ — public packages (use sparingly).
  • Module-root files (go.mod, go.sum, README.md).

Adding a dependency

go get github.com/google/uuid
import "github.com/google/uuid"

func newID() string {
    return uuid.NewString()
}

Pinning a version

// go.mod
require github.com/foo/bar v1.2.3

The go.mod records the exact version; subsequent go build uses precisely that version.

Updating dependencies

go get -u                                         # update all to latest minor/patch
go get -u=patch                                   # update to latest patch only
go get github.com/foo/bar@v1.3.0                  # specific update
go mod tidy                                       # clean up

Local development with replace

// go.mod (during development)
replace github.com/myorg/lib => ../lib

require github.com/myorg/lib v0.0.0

The replace admits using a local checkout instead of the published version.

The conventional discipline: remove the replace before pushing the consuming module.

Running tests across modules

go test ./...                                     # all packages in the current module
go test ./internal/auth/...                       # subset

Building binaries

go build .                                        # build the current package
go build -o myserver ./cmd/myserver               # specific binary
go install ./cmd/myserver                         # install to $GOPATH/bin

# Cross-compilation:
GOOS=linux GOARCH=amd64 go build -o myserver-linux ./cmd/myserver
GOOS=darwin GOARCH=arm64 go build -o myserver-mac ./cmd/myserver
GOOS=windows GOARCH=amd64 go build -o myserver.exe ./cmd/myserver

Build tags

The //go:build directive admits per-build-target source files:

//go:build linux

package mypkg

func init() { /* linux-specific */ }
//go:build linux && amd64

func sysCall() { /* ... */ }

The _linux_amd64 filename suffix is an alternative form (without explicit directives):

config_linux.go
config_darwin.go
config_windows.go

Inspecting modules

go list -m all                                    # all modules in the build
go list -m -versions github.com/foo/bar           # available versions
go mod why github.com/foo/bar                     # why is this dependency here
go mod graph                                       # dependency graph

The commands admit substantial visibility into the module graph.

Publishing

To publish a module:

  1. Push the code to a public repository (e.g., GitHub).
  2. Tag a release: git tag v0.1.0 && git push --tags.
  3. The module is now go get-able by anyone.

The proxy fetches the module on demand; no explicit “publish” step is required.

A note on the conventional discipline

The contemporary Go modules advice:

  • One go.mod per module; commit it and go.sum.
  • Use semver-style tags (v1.2.3) for releases.
  • Use internal/ for non-public code.
  • Use cmd/ for binaries; one subdirectory per binary.
  • Use go mod tidy after editing imports.
  • Use the proxy (default) for public modules.
  • Use replace for local development; remove before pushing.
  • Pin major versions ≥ 2 in the import path (/v2, /v3).
  • Use go vet, go test, go build in CI.

The combination — declarative go.mod, hash-locked go.sum, semver-based versioning, the proxy, the internal/ boundary, build tags for platform-specific code — is the substance of Go’s package management. The discipline produces reproducible, traceable, maintainable dependencies.