go run compiles and runs a program in one step and keeps no binary. go build compiles and leaves an executable in the current directory. go install compiles and puts the executable in $HOME/go/bin. All three compile to native machine code; none of them interprets anything.
| Command | Compiles | Runs | Keeps a binary | Where the binary goes |
|---|---|---|---|---|
go run . | yes | yes | no | temporary directory, deleted afterwards |
go build | yes | no | yes | current directory (or -o path) |
go install | yes | no | yes | $GOBIN, else $GOPATH/bin |
The program below is the one used in the examples on this page. The editor runs it the way go run would.
go run
go run takes a package (usually ., the current directory) or a list of .go files:
go run .
go run main.go
go run ./cmd/server
Everything after the package is passed to your program as arguments:
go run . --port 8080 verbose
version: dev
built for: linux/amd64
args: [--port 8080 verbose]
Prefer go run . over go run main.go. Naming a file compiles only that file, so as soon as the package has a second file, functions defined there are reported as undefined. The packages and imports page covers that error in detail.
go run also runs a remote program at a specific version without installing it, which is handy for code generators:
go run golang.org/x/tools/cmd/stringer@v0.30.0 -type=Color
go build
go build compiles the package in the current directory. For a main package it writes an executable; for a library package it compiles, reports errors, and discards the result.
go mod init example.com/hello
go build
ls
go.mod hello main.go
The binary's name follows these rules:
| You run | Binary name |
|---|---|
go build in module example.com/hello | hello |
go build ./cmd/server | server |
go build main.go | main (named after the first file) |
go build -o bin/app . | bin/app |
any of the above with GOOS=windows | same name plus .exe |
Two more forms you will use constantly:
go build ./... # compile every package in the module
go vet ./... # static checks, covered below
./... means "this directory and every directory below it". go build ./... with several main packages does not write any binaries; it is a fast "does everything compile" check.
Useful build flags
| Flag | What it does |
|---|---|
-o name | output file or directory |
-v | print package names as they compile |
-race | build with the data race detector (slower, larger binary; for testing) |
-trimpath | remove local file system paths from the binary, for reproducible builds |
-ldflags "-s -w" | strip the symbol table and DWARF debug info, making the binary smaller |
-ldflags "-X main.version=1.4.0" | set a string variable at link time |
-tags name | include files guarded by a //go:build name constraint |
The -X flag is how most Go projects stamp a version into the binary. It only works on package-level string variables (not constants):
go build -o app -ldflags "-X main.version=1.4.0" .
./app
version: 1.4.0
built for: linux/amd64
args: []
The binary also records its module versions and, since Go 1.18, the VCS commit it was built from. go version -m ./app prints them, and a program can read them with runtime/debug.ReadBuildInfo.
go install
go install builds exactly like go build and then moves the executable to $GOBIN, or $GOPATH/bin when GOBIN is unset (by default $HOME/go/bin):
go install .
go env GOPATH
Its most common use is installing tools written in Go. With @version it installs a program without touching your go.mod:
go install golang.org/x/tools/gopls@latest
go install honnef.co/go/tools/cmd/staticcheck@latest
If the shell cannot find a tool afterwards, $HOME/go/bin is not on your PATH. Add export PATH=$PATH:$(go env GOPATH)/bin to your shell profile.
Cross-Compiling with GOOS and GOARCH
Go can build for another operating system or CPU from any machine, with no extra toolchain. Set two environment variables for the build command:
GOOS=linux GOARCH=amd64 go build -o app-linux-amd64 .
GOOS=linux GOARCH=arm64 go build -o app-linux-arm64 .
GOOS=darwin GOARCH=arm64 go build -o app-macos .
GOOS=windows GOARCH=amd64 go build -o app.exe .
In PowerShell, environment variables are set differently:
$env:GOOS = "linux"; $env:GOARCH = "amd64"; go build -o app-linux .
The most common pairs:
| GOOS | GOARCH | Target |
|---|---|---|
linux | amd64 | most servers and containers |
linux | arm64 | AWS Graviton, Raspberry Pi with a 64-bit OS |
darwin | arm64 | Apple Silicon Macs |
darwin | amd64 | Intel Macs |
windows | amd64 | 64-bit Windows |
js | wasm | WebAssembly in a browser |
go tool dist list prints every supported pair (48 in Go 1.24).
Cross-compiling is this easy only for pure Go code. A package that uses cgo (C code, for example some SQLite drivers) needs a C cross-compiler for the target. When cross-compiling, cgo is disabled by default, and setting CGO_ENABLED=0 explicitly also gives you a fully static Linux binary, which is what you want in a minimal scratch or distroless container image.
go fmt and go vet
Two checks belong in every workflow, and in CI.
go fmt rewrites files in the one official style: tabs, aligned fields, normalized spacing. It is a wrapper around gofmt -l -w:
go fmt ./...
gofmt -l . # list files that are not formatted; empty output means clean
go vet reports code that compiles but is almost certainly wrong. Printf format mismatches are the classic case:
package main
import "fmt"
func main() {
count := 3
fmt.Printf("%s items\n", count)
}
go vet ./...
# example.com/hello
# [example.com/hello]
./main.go:7:2: fmt.Printf format %s has arg count of wrong type int
The program compiles and prints %!s(int=3) items, which is exactly the kind of bug vet exists to catch. Other vet checks include copying a sync.Mutex by value, unreachable code, struct tags with bad syntax, and a context.CancelFunc that is never called. go test runs a subset of these checks automatically; go build runs none.
Other go Subcommands
| Command | Purpose |
|---|---|
go test ./... | run tests |
go mod tidy | add missing and remove unused dependencies |
go get pkg@version | add or change a dependency |
go clean -cache | empty the build cache |
go env | print Go's configuration |
go doc fmt.Println | show documentation in the terminal |
go list -m all | list every module in the build |
Why Builds Are Fast the Second Time
Go caches compiled packages in the build cache (go env GOCACHE). A rebuild recompiles only packages whose source or dependencies changed, so go run . on an unchanged project starts almost instantly. If a build ever behaves strangely after changing Go versions or environment variables, go clean -cache clears it; you should rarely need to.
Frequently Asked Questions
What is the difference between go run and go build?
go run compiles the program into a temporary directory, runs it, and throws the binary away. go build compiles the program and writes the binary into the current directory so you can run it again or copy it elsewhere. Use go run while developing and go build when you need the executable.
How do I set the output file name of go build?
Use -o: go build -o myapp . writes myapp (on Windows, write -o myapp.exe yourself). Without -o, go build names the binary after the last element of the package's import path, or after the first file when you pass .go files, and adds .exe when building for Windows.
How do I cross-compile Go for Linux or Windows?
Set GOOS and GOARCH for the build: GOOS=linux GOARCH=amd64 go build -o app-linux . or GOOS=windows GOARCH=amd64 go build . (which produces a .exe). No extra toolchain is needed for pure Go code. go tool dist list prints every supported pair.
Where does go install put the binary?
In $GOBIN if it is set, otherwise in $GOPATH/bin, which is $HOME/go/bin by default. Add that directory to your PATH to run installed tools by name. go install example.com/tool@latest installs a tool without adding it to your module.
Does go build run go vet?
No. go build only compiles. go test runs a subset of go vet checks automatically, but for the full set run go vet ./... yourself, typically in CI next to gofmt -l ..