Right, let’s talk about the three amigos: GOROOT, GOPATH, and GOBIN. If you’re coming from a language like Python or Node.js, this setup might feel a bit… opinionated. That’s because it is. The Go team had a very specific, and initially very successful, idea about how to organize your entire universe of Go code. We’ll get into why that model started to creak and what’s changed, but first, you need to understand the original blueprint. Trust me, this historical baggage is still in your attic, and you need to know about it to avoid the cobwebs.

The Granddaddy: GOROOT

This one is simple: GOROOT is simply the directory where your Go language installation lives. It’s the root directory that contains the Go standard library packages (src/), the compiled standard library (pkg/), and all the core tooling like the go command itself.

You almost never need to set this yourself. The installers for macOS and Linux typically put it in /usr/local/go, and on Windows in C:\Go. The go tool knows how to find itself. The only time you’d mess with GOROOT is if you’re doing something weird like having multiple, parallel Go versions installed on a single machine and need to switch between them. For 99.9% of you, just forget this exists. If you echo $GOROOT and it’s empty, that’s fine. The tool figures it out. If you do set it incorrectly, you’ll break everything in hilarious and confusing ways. So, my advice? Leave it the hell alone.

The (Now Mostly Deprecated) Workspace: GOPATH

Here’s where the “opinionated” part comes in. Before Go 1.11 (2018), GOPATH wasn’t just a suggestion; it was the law. The go tool required it to be set. The idea was brilliant in its simplicity: you have one single workspace on your machine for all your Go code, both the libraries you write and the ones you download. This workspace had a very specific structure:

$GOPATH/
    src/    # Where your .go source files live
    pkg/    # Where compiled package files (.a) are stored
    bin/    # Where compiled executable binaries are placed

The magic was in the src directory. The go tool expected your code to be laid out according to its remote import path. Want to use github.com/you/project? You had to literally put your code in $GOPATH/src/github.com/you/project. This meant your filesystem mirrored the internet. It was a bit rigid, but it eliminated any ambiguity about where things were or should be.

You can see it in action. If you set an old-school GOPATH (e.g., export GOPATH=$HOME/my_go_workspace) and then run go get github.com/yourusername/hello, the tool will:

  1. Create $HOME/my_go_workspace/src/github.com/yourusername/hello/
  2. Download the source code into that folder.
  3. Compile it and put the executable in $HOME/my_go_workspace/bin/.

This is why you’ll see a million old blog posts telling you to add $GOPATH/bin to your PATH—so you can run the tools you go get without typing the full path.

The Pitfall: This “one workspace to rule them all” model falls apart the moment you need to work on two projects that require different versions of the same dependency. The GOPATH model had no built-in concept of versioning or project isolation, which became its fatal flaw. This is why the world moved on to Go Modules (which we’ll cover in a later section, don’t worry). While GOPATH is still supported for backwards compatibility, if you’re starting a new project today, you should be using modules. The default GOPATH is now ~/go (or %USERPROFILE%\go on Windows), which is where the tooling will put things if it needs to, but you largely don’t have to think about it anymore.

The Output Binaries: GOBIN

This is the easiest of the three. GOBIN tells the go tool exactly where to put the executable binaries it compiles when you run go install or go get.

If GOBIN is set, every tool you install goes there. If it’s not set, it defaults to $GOPATH/bin. Since GOPATH itself defaults to ~/go, this usually means binaries end up in ~/go/bin.

The best practice? Set your GOBIN and add it to your system’s PATH. This decouples your binary location from the ever-changing world of GOPATH and gives you one, consistent place to find all your Go tools. It’s one less thing to think about.

# Put this in your .bashrc, .zshrc, or equivalent
export GOBIN=$HOME/.local/bin # Or wherever you like to keep user-specific binaries
export PATH=$PATH:$GOBIN

Now, when you install a tool, it’s predictably in a known location that’s already in your PATH.

go install golang.org/x/tools/cmd/stringer@latest
# The `stringer` binary is now in $GOBIN, and you can just run it from any terminal
stringer -type=Pill

The main edge case to remember is that go install ./... (which installs all packages in the current directory and subdirectories) will respect GOBIN. If you’re inside a project and run that, it will try to move the compiled binaries for that project to your global GOBIN directory, which might not be what you want. For project-specific binaries, you’re usually better off using go build -o /some/path/inside/your/project to control the output location precisely.