The go run, go build, and go install Commands
Every Go program eventually needs to become something you can run, share, or ship. The Go toolchain gives you three commands for that: go run, which compiles your code and immediately executes it in one step, perfect for quick iteration; go build, which compiles your code into a standalone executable file you keep on disk; and go install, which does what go build does but then places the resulting binary somewhere on your PATH so you can run it like any other command-line tool. Knowing which one to reach for, and what each is actually doing under the hood, is one of the first practical skills every Go developer needs.
Overview / How it works
Go is a compiled, statically-typed language: unlike Python or JavaScript, there is no interpreter reading your source line by line at run time. Every go command that “runs” your code first turns it into real machine code for your CPU and operating system. That compilation step is the same underlying machinery for all three commands; the difference is only what happens to the result afterward.
All three commands are module-aware: they expect to be run inside a directory tree that has a go.mod file at its root (created once with go mod init module/path). The module file tells the toolchain the module’s import path and which Go version and dependencies it needs, so the compiler knows how to resolve every import in your source.
go run
go run compiles the named package into a temporary executable in a scratch directory (under your system’s temp folder or the Go build cache), executes that temporary binary immediately, streams its stdout/stderr back to your terminal, and then deletes the temporary file once the program exits. Nothing is left behind in your project directory. This makes it the natural choice while you are actively writing and testing code — there is no separate binary to think about, clean up, or accidentally commit.
go build
go build compiles the named package (and everything it imports) and writes a persistent executable file to disk, by default in your current directory, named after the module or the last path element of the package. It does not run the program. This is what you use to produce an artifact you can copy to a server, hand to a teammate, or attach to a release. Because Go binaries are statically linked by default, the output file bundles the Go runtime and all dependencies into one self-contained executable; the target machine does not need Go, or any runtime, installed to run it.
go install
go install does exactly what go build does, except instead of leaving the binary in your current directory, it copies it into $GOBIN (if set) or the bin subdirectory of $GOPATH (which defaults to ~/go/bin on Linux/macOS). If that directory is on your shell’s PATH, the tool becomes available everywhere, just by typing its name. go install also understands version-suffixed package paths, like go install golang.org/x/tools/cmd/goimports@latest, which lets you install a specific released version of someone else’s command-line tool directly from its module path, without cloning the repository or worrying about your current directory’s go.mod at all.
Underneath all three commands sits the build cache ($GOCACHE), which stores the compiled object code for every package you’ve built. The first build of a package is the slow one; after that, as long as the source and build flags haven’t changed, the toolchain reuses the cached object code instead of recompiling, which is why repeated go run invocations during development feel almost instant even though a full compile happens every time.
Syntax
go run [build flags] package [arguments...]
go build [-o output] [build flags] [packages]
go install [build flags] [packages]
| Part | Meaning |
|---|---|
package / packages |
What to compile: . for the package in the current directory, ./... for every package under the current directory, a list of .go files, or (for go install) a full module path optionally suffixed with @version. |
arguments... |
Only for go run: anything after the package name is passed straight through to your program’s own os.Args, not interpreted by the go tool. |
-o output |
For go build: sets the name and/or directory of the resulting binary. Without it, the binary is named after the module/directory. |
| build flags | Shared by all three: -v (verbose, lists packages as they compile), -race (build with the data race detector), -ldflags (pass flags to the linker, e.g. to inject a version string), -a (force rebuilding of everything, ignoring the cache). |
Cross-compilation is controlled with the GOOS and GOARCH environment variables rather than a flag; set them before go build to target a different operating system or CPU architecture than the one you’re building on.
Examples
Example 1: go run for quick iteration
package main
import "fmt"
func main() {
fmt.Println("Hello from go run!")
}
Output:
Hello from go run!
Save this as hello.go inside a module (run go mod init example.com/hello once, first) and run go run hello.go or, equivalently, go run . from that directory. Go compiles the file into a temporary binary, runs it, prints the greeting, and cleans up after itself; no hello executable appears in your file listing afterward. This is the fastest feedback loop for trying out a change.
Example 2: go build with flags and a named output
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "World", "name to greet")
flag.Parse()
fmt.Printf("Hello, %s!\n", *name)
}
Output (running go build -o greet . then ./greet):
Hello, World!
Here go build -o greet . compiles the program and writes a persistent executable named greet into the current directory; nothing runs yet. Running ./greet afterward executes that file directly, which is why it starts instantly compared to go run: there is no compilation step, just the operating system loading an already-compiled binary. Passing ./greet -name=Gopher instead prints Hello, Gopher!, because the flag package parses arguments from the running process’s own os.Args, exactly as it would for go run . -name=Gopher.
Example 3: go install for a CLI tool you use everywhere
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("usage: hello-cli <name>")
os.Exit(1)
}
fmt.Printf("Hello, %s! Welcome aboard.\n", os.Args[1])
}
Output (running go install . from a module named example.com/hello-cli, then hello-cli Gopher from anywhere on your PATH):
Hello, Gopher! Welcome aboard.
go install . compiles this program and copies the binary, named after the module's last path element, hello-cli, into $GOBIN or ~/go/bin. Because that directory is (once you've added it) on your shell's PATH, you can now type hello-cli Gopher from any directory on your machine, not just the project folder. This is exactly how you'd install a personal script, or how go install some/tool@latest installs someone else's published command-line tool.
How it works step by step
All three commands share the same first phase and only diverge at the end:
- Resolve. The tool reads
go.modto determine the module path and Go version, then walks the package's imports to build a full dependency graph. - Check the cache. For each package in that graph, the tool checks
$GOCACHEfor a previous compilation with matching source and flags; unchanged packages are reused instead of recompiled. - Compile. Anything not already cached is parsed, type-checked, and compiled to machine code for the target
GOOS/GOARCH(your current platform, unless overridden). - Link. The compiled packages and the Go runtime are statically linked into a single executable.
- Place the result.
go runwrites it to a temporary directory, executes it, streams its output, and deletes it on exit.go buildwrites it to the current directory (or wherever-osays).go installwrites it into$GOBIN/$GOPATH/bininstead.
Common Mistakes
Mistake 1: Running one file when the package has several
// main.go
package main
import "fmt"
func main() {
fmt.Println(greet("Gopher"))
}
// helper.go
package main
func greet(name string) string {
return "Hello, " + name + "!"
}
$ go run main.go
# command-line-arguments
./main.go:6:14: undefined: greet
When a package spans multiple files, go run main.go only compiles main.go; it has no idea helper.go exists, so greet is undefined. The fix is to tell the tool about the whole package instead of one file:
$ go run .
Hello, Gopher!
go run . (and the same goes for go build . and go install .) compiles every file that belongs to the package in the current directory, which is almost always what you want.
Mistake 2: Expecting go install to build a non-main package
package mathutil
func Add(a, b int) int {
return a + b
}
$ go install .
$ ls $(go env GOBIN)
# nothing new appears
This isn't an error; go install ran successfully. But mathutil is a library package (package mathutil, no func main), and only packages named main with a func main produce a runnable command. Installing a library package just compiles it into the build cache silently; there is nothing to copy into $GOBIN. If you wanted an executable, the package needs to be package main with a func main.
Mistake 3: Running any of these commands outside a module
$ go build .
go: go.mod file not found in current directory or any parent directory; see 'go help modules'
All three commands are module-aware and need a go.mod somewhere at or above the current directory. The fix is a one-time setup step per project:
$ go mod init example.com/myapp
$ go build .
Best Practices
- Use
go runonly for local development and quick checks, never as the way a deployed service starts, since it re-resolves the build cache every single time it's invoked. - Use
-owithgo buildin scripts and CI pipelines so the binary's name and location are explicit and predictable, rather than relying on the default module-name-based output. - Add build output (e.g. the binary named after your module) to
.gitignore; compiled binaries don't belong in version control. - Before relying on
go installfor personal tools, confirm$GOBIN(or~/go/bin) is actually on yourPATH; rungo env GOBIN GOPATHto check. - Pin an explicit version when installing third-party tools in scripts or CI, e.g.
go install tool@v1.4.0, rather than@latest, so builds stay reproducible. - Use
GOOS/GOARCHwithgo buildto cross-compile for a deployment target instead of building on that target machine. - Run
go build ./...(orgo vet ./...) before committing to make sure every package in the module still compiles, not just the one you were editing.
Practice Exercises
- Create a new module with
go mod init, write amain.gothat prints a greeting, and run it withgo run .. Confirm no binary is left in the directory afterward. - Extend that program to read a name from
os.Args, then build it withgo build -o greet .and run the resulting binary directly with an argument. Compare how fast it starts versusgo run. - Set
GOOS=linuxandGOARCH=amd64as environment variables before runninggo build -o app-linux .on a non-Linux machine. Inspect the resulting file (for example with thefilecommand) and note that it can't be executed directly on your own OS, even though it compiled without errors.
Summary
go runcompiles and executes in one step using a temporary binary that is deleted afterward; best for quick iteration during development.go buildcompiles to a persistent executable left in the current directory (or wherever-opoints), without running it; best for producing an artifact to ship.go installbuilds and then copies the binary into$GOBINor$GOPATH/binso it's available on yourPATH; best for personal or third-party command-line tools, and supports@versionsuffixes for installing specific releases directly from a module path.- All three are module-aware, share the same build cache, and only produce a runnable command from a
package mainwith afunc main. - Go binaries are statically linked by default, so the machine running them needs no separate Go installation; cross-compile with
GOOS/GOARCHto target other platforms.
