Go Package Management: Go Modules, go install & get Through the Versions
Why
I used to mix go get
and go install
, not really understanding the differences, even when being suggested to use one over the other. Based on growing uncertainty and the fact that modules weren't there from the beginning, I'm documenting this for clarity.
Go Module Era (Go 1.11~)
We can call the time before 1.11 the "GOPATH era" since everything was placed there without version control.
The confusion comes from using JavaScript with npm, which always had version control. So when Go modules appeared, I wondered why this needed special explanation.
If you've only used Go after version 1.11, you probably aren't confused because modules are the normal way to control package versions.
Module evolution timeline:
- 1.11-12: Required manual setting of
GO111MODULE=on
111
inGO111MODULE
represents a variable supported after version 1.11
- During Go 1.13-1.15: the default is
GO111MODULE=auto
- 1.16+: Default setting changed to
GO111MODULE=on
- Related commands
- go mod init
- go mod vendor
- go mod download
Go Install
- Before 1.11:
- Installed binaries to
$GOPATH/bin
- Also installed packages to
$GOPATH/pkg
- Installed binaries to
- 1.11-15 (transition period with modules):
- Still installed binaries to
$GOPATH/bin
or$GOBIN
- No longer used
$GOPATH/pkg
for packages - you had to run go install in your project for version dependencies
- Still installed binaries to
- 1.16+: Supports version specification and became independent from project dependencies:
- Still installs binaries to
$GOPATH/bin
or$GOBIN
- No longer manages project package dependencies (not related to go.mod now)
- Similar to
npm install --global xxx
- Still installs binaries to
Go Get
- After 1.16,
go install
stopped managing packages and dependencies - that job went togo get
- 1.11-1.15: Transition period where it downloaded packages and installed binaries
- Starting 1.16: Not recommended for installing binaries
- 1.17: Officially not recommended for binary installation, shows warnings but doesn't block it
- 1.18: The
-d
flag (download only, no installation) became the default behavior, completing the transition
Version Migration of Go Install and Go Get
Originally, these commands had clearly defined separate purposes. Over time, features were added for convenience, which led to overlapping responsibilities and confused users. This issue was finally addressed starting in Go 1.16.
Go 1.16 began separating the commands conceptually. go install
could specify versions and didn't affect go.mod files.
https://go.dev/doc/go1.16#modules
From Go docs 1.16:
go
install
now accepts arguments with version suffixes (for example,go installexample.com/cmd@v1.0.0
). This causesgoinstall
to build and install packages in module-aware mode, ignoring thego.mod
file in the current directory or any parent directory, if there is one. This is useful for installing executables without affecting the dependencies of the main module.
go
install
, with or without a version suffix (as described above), is now the recommended way to build and install packages in module mode.go
get
should be used with the-d
flag to adjust the current module’s dependencies without building packages, and use ofgo
get
to build and install packages is deprecated. In a future release, the-d
flag will always be enabled.
Go 1.17 reinforced this separation but still allowed go get
for binary installation with warnings:
https://go.dev/doc/go1.17#module-deprecation-comments
go
get
prints a deprecation warning when installing commands outside the main module (without the-d
flag).go install cmd@version
should be used instead to install a command at a specific version, using a suffix like@latest
or@v1.2.3
.
In Go 1.18, the-d
flag will always be enabled, andgo
get
will only be used to change dependencies ingo.mod
.
Go 1.18 completed the separation - go get
no longer supports binary installation and uses -d
by default.
https://go.dev/doc/go1.18#go-command
Official summary:
Since Go 1.16,go install
is the recommended command for building and installing programs. When used with a version suffix (like@latest
or@v1.4.6
),go install
builds packages in module-aware mode, ignoring thego.mod
file in the current directory or any parent directory, if there is one.
go get
is more focused on managing requirements ingo.mod
. The-d
flag is deprecated, and in Go 1.18, it will always be enabled.
Evolution of $GOPATH/pkg, bin, src Folders
The src, pkg, bin folders were core components in the GOPATH era:
src/
: Where you put cloned projects (yours or third-party)- Then: Seemed intrusive but was necessary to resolve import paths
- Now: Less important with modules; I don't even have this folder anymore
pkg/
- Then: Stored compiled artifacts (
*.a
)
- Now: Changed to
pkg/mod/
as a cache (note that it's read-only) for multiple projects; like a repository that can be copied to a project usinggo mod vendor
- Then: Stored compiled artifacts (
bin/
: Hasn't changed - always stored executables/binaries
GOPATH role change: From core dependency path to mainly storing cache and binary files
For Node/npm Users
- Think of
go install
asnpm i --global
- Think of
go get
asnpm i --save
(updates go.mod/go.sum like package.json/package-lock.json)- npm local installs to
node_modules/
, similar togo mod vendor
go get
installs to$GOPATH/pkg/mod/..
go mod vendor
first downloads to$GOPATH/pkg/mod/
, then copies to project's/vendor/..
- npm local installs to
How to Run a Cloned Project?
When cloning someone's project, you usually don't need to run go mod tidy
if they followed best practices (running go mod tidy
before commits).
To be safe, you can run go mod tidy
yourself.
Based on trust, you can directly:
go build
: Downloads and compilesif you need an executable For CRUD servers,
go run main.go
is more common
go mod download
: Downloads without compilingUseful for debugging in Docker by separating download from build
go mod vendor
: Needed when:- There's a
vendor/
directory in version control
- You want npm-like local package control
Vendor vs
node_modules
: vendor should be in version control; when cloning, Go still caches in $GOPATH/pkg/mod then copies to /vendor
- You need to work offline (must go with
go build -mod=vendor
)
- You need to modify dependency code (must go with
go build -mod=vendor
)
- In air-gapped env or enterprise, vendor mode remains an important dependency management approach. In vendor mode, Go doesn't download dependencies from the network but uses code from the vendor directory
go mod vendor
is an active choice, useful for customizing packages- There's a
Go Install vs Go Build
The main difference: go install
puts executables in $GOPATH/bin
or $GOBIN
directory.
go build
just builds in the current directory.
Global Cache vs Project-Local Storage
When you use go get
to download a package used in several different projects, the file structure looks like this:
$GOPATH/pkg/mod/
└── github.com/
└── gin-gonic/
└── gin@v1.6.3/ # a version
├── LICENSE
├── gin.go
├── ...else files
└── gin@v1.7.7/ # b
├── LICENSE
├── gin.go
├── ...else files
└── gin@v1.9.1/ # c
├── LICENSE
├── gin.go
├── ...else files
go mod vendor: has only one version:
Your-project/
├── go.mod
├── go.sum
├── vendor/
│ ├── github.com/
│ │ └── gin-gonic/
│ │ └── gin/ # only one version (specified in go.mod)
│ │ ├── LICENSE
│ │ ├── gin.go
│ │ └── ...else
│ └── modules.txt # records all vendored modules
References
https://go.dev/doc/go1.16#modules
https://go.dev/doc/go1.17#module-deprecation-comments
https://go.dev/doc/go1.18#go-command