Building and Running Programs

Compile, run, and install Go applications with go build, go run, and go install, including common flags, cross-compilation, and how to avoid typical beginner mistakes.

Every Go program begins as plain text in .go files. Before it can do anything useful, those files must be turned into a binary the operating system can execute. The go command provides three entry points for this transformation: go build creates an executable on disk, go run compiles and runs in one step, and go install builds the program and places it where your shell can find it from any directory. Each serves a distinct role in the development workflow.

How Go Turns Source Code into Executables

Go is a compiled language. The compiler (the gc toolchain) translates Go source into machine code for a specific operating system and architecture, and the linker packages everything into a single, statically linked binary. That binary contains not only your code but also all the library code it depends on, so you don't need a separate runtime or interpreter installed on the target machine.

The go build, go run, and go install commands are all built on the same compilation engine. They differ in what happens after compilation — whether the result is saved as a named file, executed immediately from a temporary location, or copied into a central bin directory.

For a beginner, the simplest mental model is:

  • go build: “Turn my source into a runnable file I can keep and share.”
  • go run: “Just show me what my code does, right now.”
  • go install: “Put the finished program somewhere my terminal can always find it.”

All three expect at least one file that declares package main and contains a func main(). Without those, there is no entry point for an executable. If you try to build a library package (one that does not declare package main), Go will compile the package to check for errors but will not produce a runnable binary.

Compiling with go build

go build compiles the package or packages named on the command line and, when the package is a main package, writes an executable binary to the current directory. If no packages are specified, it builds the package in the current directory.

The produced binary is named after the directory containing the code. On Windows, Go automatically appends a .exe extension.

go build

If the current directory contains a main package, this command creates an executable file — for example, myapp (or myapp.exe on Windows) — inside that same directory. You can then run it:

./myapp

You can also build a specific package by providing its import path:

go build github.com/yourname/yourproject/cmd/server

Missing main Package:

go build only produces an executable when the compiled package declares package main. If you run it in a directory that contains a library package (e.g., package utils), the command will verify the code compiles but will discard the result and print no output. There will be no binary to run.

Choosing the Output Name and Directory

The -o flag lets you control the output file name and location:

go build -o bin/server

This places the executable in a bin/ subdirectory with the name server. If the output path ends with a slash (or backslash), Go treats it as a directory and writes the binary there using the default name.

go build -o /usr/local/bin/

If /usr/local/bin is a directory, Go writes the executable inside it. This is especially useful in deployment scripts.

Watching the Build in Detail

Two flags expose what happens behind the scenes. -v prints the name of each package as it is compiled. -x prints every shell command the Go toolchain executes, including calls to the compiler, assembler, and linker.

go build -v -x

The output can be long, but it gives you a complete audit trail of the build. This is helpful when you need to understand why a particular package is being rebuilt or which flags are passed to the linker.

Common Build Flags You Will Use

FlagPurpose
-o <path>Set the output file name or directory
-vPrint package names as they are compiled
-xShow all commands executed during the build
-raceEnable the data race detector (slower, but catches concurrency bugs)
-ldflagsPass arguments to the linker, e.g., to inject version information
-workPrint the temporary work directory and do not delete it
-tagsBuild files that match the given build tags
-modControl module download behavior (readonly, vendor, mod)

Race Detector Overhead:

The -race flag instruments the compiled code to detect data races at runtime. The resulting binary is larger and noticeably slower. Use it only during testing and development, not in production builds.

Cross-Compilation

One of Go's most praised features is the ability to compile a program for a different operating system or architecture without a complex toolchain setup. You control the target with two environment variables: GOOS (operating system) and GOARCH (architecture).

GOOS=linux GOARCH=amd64 go build -o myapp-linux
GOOS=windows GOARCH=amd64 go build -o myapp.exe
GOOS=darwin GOARCH=arm64 go build -o myapp-mac

Valid GOOS values include linux, darwin, windows, freebsd, and more. GOARCH can be amd64, arm64, 386, etc. The Go documentation lists all supported combinations.

A beginner should think of this as: “I can build my Linux server program from my Mac or Windows machine just by setting two variables before go build.” It's a huge productivity gain and eliminates the need for per-platform build machines in many cases.

Real-World Usage

In practice, go build is the command you use to produce a binary for a staging environment, a CI pipeline, or a final release. You might run:

go build -ldflags="-X main.version=1.2.3" -o releases/server ./cmd/server

This injects a version string at compile time and writes the binary to a clean releases/ folder, ready for distribution.

Successful Build:

If go build completes without any messages (no errors printed), the compilation succeeded. You should find the binary in the expected location. Run ls -l (or dir on Windows) to confirm the file exists and is executable.

Running Programs with go run

go run compiles the listed source files or the package in the current directory and immediately executes the resulting binary. It is the fastest way to see your program in action.

go run main.go

If your program spans several files in the same package, list them all:

go run main.go config.go handler.go

Or, more commonly, use a package path:

go run .

Since Go 1.11, you can also run a package in a specific directory by giving its relative path, as in go run ./cmd/server.

Under the hood, go run builds a binary in a temporary directory (usually inside the system's temp folder), runs it, and then discards the binary. The program behaves exactly as if you had built it with go build and executed it, except that no permanent executable is left behind.

This design has two practical consequences. First, it's perfect for iterative development — you change code, hit go run, and get instant feedback without cluttering your project directory with binaries. Second, it means you should never use go run as a deployment command. There is no binary to copy to a server; the temporary file vanishes when the process ends.

Not a Deployment Tool:

go run creates a throwaway binary. If you need to run the program on a different machine or keep it for later, use go build or go install. Deploying with go run means you would need the Go toolchain installed on every target machine and would recompile on every launch, which is both slow and fragile.

Common Flags with go run

Most build flags work with go run as well because it essentially calls go build first.

go run -race main.go      # enable race detector
go run -v .               # verbose output during compilation
go run -work main.go      # print and keep the temporary work directory

The -work flag is especially useful for debugging: it tells you where Go placed the temporary binary so you can inspect it before it gets deleted.

Environment Variables on the Command Line:

You can pass environment variables before go run just as you would before go build. For example, GOOS=js GOARCH=wasm go run . would attempt to compile for WebAssembly and run it, though running WASM directly is not typical.

When You See Nothing Happen

A new Go learner often writes a main.go file, types go run main.go, and sees a blank terminal. The program probably compiled and ran — but finished instantly. Add a print statement, e.g., fmt.Println("hello"), and you'll see output confirming the run worked.

Unused Imports Cause Build Failure:

If you import a package and don't use it, go run will refuse to compile. Go treats unused imports as an error to keep builds fast and dependency graphs clean. Remove the unused import or use the blank identifier _ if you genuinely need a side effect.

Everyday Development Usage

The typical development loop is: write code, save the file, run go run . in the terminal, observe the output, fix bugs, repeat. Many editors automate this with shortcuts. Because Go compiles quickly, the feedback loop is short enough that go run feels nearly as responsive as an interpreted language — but you still get the safety of a compiler.

# after editing main.go
go run .
Hello, Gopher!

When the output looks correct, you move to go build to create a final binary you can share or deploy.

Installing Binaries with go install

go install compiles the package and then installs the resulting executable into your Go binary directory. That directory is either $GOBIN if you've explicitly set that environment variable, or $GOPATH/bin by default. Once a binary is installed there, you can run it from any terminal location simply by typing its name, provided your shell's PATH includes that directory.

go install

If run inside a main package directory, this command builds the package and copies the executable to $GOPATH/bin. After installation, you can call the program without specifying its full path:

myapp

If myapp is not recognized, verify that $GOPATH/bin (or $GOBIN) is in your PATH by running echo $PATH (Linux/macOS) or echo %PATH% (Windows). You can find the exact installation path with go env GOBIN or go env GOPATH.

How go install Differs from go build

The primary difference is what happens to the result. go build leaves the binary in the current directory. go install puts it in a well-known location that is shared across projects. This is useful when you are working on a command-line tool you want to use from multiple projects or when you need the tool to be globally available on your system.

In module-aware mode (Go 1.16 and later), running go install without arguments in a module directory installs the package in the current directory. You can also install a specific package at a specific version without changing your current module's dependencies:

go install github.com/yourname/yourproject/cmd/tool@v1.0.0

This fetches and compiles the package, then installs the binary, but does not add the dependency to your go.mod. This is the standard way to install developer tools like linters, code generators, or static analysis tools without polluting your project's dependency graph.

Installation Confirmed:

When go install completes without errors, the binary is now inside your GOBIN directory. Run ls $(go env GOBIN) (or dir on Windows) to see it listed. If the command runs from anywhere, your setup is correct.

Installing Tools Not Part of Your Project

A common pattern is to use go install to fetch and install standalone Go tools that help with development but are not imported by your code. For example:

go install honnef.co/go/tools/cmd/staticcheck@latest

This installs the staticcheck linter into GOBIN. The @latest suffix ensures you get the most recent tagged version.

Real-World Scenario

You are building a web server with a separate command-line client. During development, you might use go run for quick iteration, then run go install ./cmd/server and go install ./cmd/client to make both programs globally available on your machine. Once they are installed, you can start the server in one terminal and use the client in another without worrying about directory paths.

Module Mode and go install:

In module mode, running go install without a version suffix (e.g., go install github.com/foo/bar) may be ambiguous. The Go toolchain will try to resolve the latest version of the dependency if not already in go.mod. For explicit, reproducible installs of tools, always use @version or @latest.

Choosing the Right Command for Your Workflow

None of these three commands is “better” — each solves a specific stage in the development lifecycle. The table below offers a decision framework rather than a repetition of earlier descriptions.

SituationCommandWhy
I want to see my code run right nowgo run .Fast feedback loop, no leftover binary
I need a binary to test on another machinego build -o myappPortable artifact in one file
I'm creating a tool I'll use from anywherego installBinary lands in PATH, accessible globally
I'm building for a different OS (CI, deployment)GOOS=... go buildCross-compilation produces target-specific binary
I'm developing a shared librarygo build (no binary)Check that the package compiles, but no executable is expected

A beginner's first instinct is often to stick with go run for everything because it feels like an interpreter. As soon as you want to share your work with a colleague or deploy it, switch to go build. When you start writing tools that you want to keep available across projects, reach for go install.