35.7 Distributing CLI Tools with go install
Right, so you’ve built this magnificent CLI tool. It slices, it dices, it automates your most tedious tasks. But it’s sitting there on your machine like a fancy sports car in a private garage. What good is that? We need to get it into the hands of users, or at the very least, onto your other machines without a bunch of copy-paste nonsense. This is where go install shines. It’s arguably the single best feature of the Go toolchain for CLI developers. Forget tarballs, .deb files, or Homebrew taps for a moment (we’ll get to those later). For pure, unadulterated simplicity, go install is your best friend.
The Absolute Basics: It’s Just a URL
Here’s the beautiful part: go install works directly with a repository URL. A user doesn’t need to clone your repo, cd into it, and run go build. They can install the latest version directly from your remote source control. The magic incantation is:
go install github.com/yourusername/yourcooltool@latest
Let’s break that down. The github.com/yourusername/yourcooltool part is your module path, the same one declared at the top of your go.mod file. The @latest suffix tells the Go toolchain, “Get me the most recent stable release tagged with a semantic version.” This is crucial. It means you, the author, must tag your releases. If you don’t, @latest will just grab the latest commit on the default branch, which is a terrible experience for a user expecting stability. Always. Tag. Your. Releases.
The Importance of a Good go.mod
Your go.mod file isn’t just for tracking your own dependencies; it’s the manifest go install uses to understand your project. The most critical line is the module path. It must match the remote repository URL exactly. If your GitHub username is gopherking and your repo is magic-cmd, your go.mod must start with:
module github.com/gopherking/magic-cmd
If you get this wrong, go install will fail spectacularly with a confusing error. There’s no wiggle room here. The toolchain is brutally literal.
The Main Package Must Be in cmd/
This is less a hard rule of the go command and more a critical best practice that makes go install work predictably. Your main package—the one with func main()—should live in a directory under cmd. The standard structure is:
yourcooltool/
├── go.mod
├── go.sum
├── internal/
│ └── ... # your library code
├── cmd/
│ └── yourcooltool/
│ └── main.go
└── pkg/
└── ... # your public package code
Why? Because the name of the subdirectory inside cmd/ (in this case, yourcooltool) becomes the name of the compiled binary. When a user runs go install github.com/yourusername/yourcooltool/cmd/yourcooltool@latest, the toolchain finds the main package in that specific directory, compiles it, and names the output binary yourcooltool. It’s intuitive and keeps everything organized. If you just put a main.go at the root of your project, the binary will be named after the directory it’s in, which is often messy and unclear.
Installing a Local Version for Development
You’re iterating on your tool. You don’t want to tag a release every five minutes. You can install the version from your current working directory using a slightly different trick. From within your project directory, run:
go install ./cmd/yourcooltool
The ./ prefix is the key here. It tells the go tool to use the local source files instead of fetching from a remote. This is how you test that the installation process itself works before you push your code and tag a release. It’s also incredibly useful for making your tool available system-wide during development.
The Gritty Details: Where Does That Binary Go?
Ah, the classic “it worked but I can’t run it” moment. go install places the compiled binary in the directory specified by the GOBIN environment variable. If GOBIN is not set, it defaults to $GOPATH/bin. And if GOPATH isn’t set, it defaults to ~/go/bin on Unix systems or %USERPROFILE%\go\bin on Windows.
This is the number one pitfall for new users. They run go install, it succeeds silently, and then they get a command not found error because $GOBIN or $GOPATH/bin is not in their system’s PATH environment variable. So your installation instructions shouldn’t just be the go install command; they should include a friendly note: “Make sure your PATH includes your Go bin directory ($(go env GOBIN) or $(go env GOPATH)/bin).” This isn’t your fault; it’s just a rite of passage for every Go developer.
When go install Isn’t Enough
Let’s be honest: go install is fantastic for developers and technically-inclined users. But asking your less-technical users to install a Go toolchain just to run your app is a non-starter. For them, you’ll need to provide pre-compiled binaries. This is where you’d use Go’s brilliant cross-compilation to build for Linux, Windows, and macOS (GOOS=windows GOARCH=amd64 go build -o tool.exe ./cmd/tool) and then distribute those files via GitHub Releases. Tools like goreleaser automate this process beautifully. But for the core audience of fellow developers? go install is often all you need. It’s simple, elegant, and leverages the entire Go ecosystem. Use it.