Build, Test and Run a Go Module Safely

The go command builds, tests and runs code in one tool, and it is easy to skip a step and end up shipping a binary nobody rebuilt. This walkthrough creates a small module, formats it, tests it, then produces a named executable, entirely as an ordinary user. Allow about 15 minutes for the first run, including a possible module download.

You need a shell, a writable working directory, the Go toolchain, and network access only if the module needs to fetch dependencies. None of the steps below needs root. Do not use sudo for a project build or for go install.

1. Confirm the toolchain

Check which executable your shell will actually run before you start anything. A package-managed Go and a manually installed Go can both exist on the same machine, and they do not always agree.

$ command -v go
$ go version
$ go env GOROOT GOPATH GOMOD GOPROXY

The reference machine reports go version go1.22.2 linux/amd64 for /usr/bin/go. The installed go(1) manpage is dated 2022-03-15, so treat go help as the current source for flags. The GOMOD value is /dev/null outside a module. If the version or path is not what you expect, fix your PATH before continuing.

2. Create the module

Work in a new directory so the generated files are easy to spot and remove later. The module path is just an identifier used by imports; it does not need to be a real hosted repository for a local example like this.

$ mkdir -p "$HOME/tmp/go-command-demo"
$ cd "$HOME/tmp/go-command-demo"
$ go mod init example.invalid/greeter
go: creating new go.mod: module example.invalid/greeter

go mod init writes go.mod and refuses to overwrite an existing one. Treat that file as source control material: review it, commit it with the program, and resist editing it casually just to silence a build error.

Create main.go with a deliberately small program. The redirection below changes project state, so check the target directory first if you are adapting this to an existing checkout.

$ test -f main.go && { printf '%s\n' 'main.go already exists; stop and inspect it' >&2; exit 1; }
$ printf '%s\n' \
  'package main' \
  '' \
  'import "fmt"' \
  '' \
  'func main() {' \
  '    fmt.Println("hello from Go")' \
  '}' > main.go

Verify the module and source are both present:

$ ls -l go.mod main.go
$ go list -m
example.invalid/greeter

3. Format and run it

Run the formatter before reviewing anything else. go fmt invokes gofmt -l -w on the selected packages and prints the files it changes. Using . makes the intended package explicit instead of relying on the current-directory default.

$ go fmt ./
main.go
$ go run .
hello from Go

go run compiles a temporary executable, runs it, and does not leave the binary behind in your project directory. Its exit status reflects the go command, not necessarily your program, so reach for go build when you need something you can check separately.

4. Test before you build

There are no test files in this tiny example, but the command still checks that the package builds. In a real project, go test ./... walks every package in the module and runs the files ending in _test.go.

$ go test ./...
?   example.invalid/greeter   [no test files]

Tip: a cached result is not a failure. (cached) just means the inputs and flags matched an earlier run; use go test -count=1 ./... when you need to force a fresh one. Run go vet ./... separately if you want its diagnostics on their own.

5. Build a named binary

Build into a project-local output directory so the result cannot land on top of a system executable. The -o flag chooses the output path.

$ mkdir -p bin
$ go build -o bin/greeter .
$ ./bin/greeter
hello from Go
$ file bin/greeter
bin/greeter: ELF 64-bit LSB pie executable

The exact file description varies by platform and build settings; what matters is that the file exists, is executable, and prints the expected line. go build compiles packages and their dependencies but does not install them anywhere.

Dependencies and installation boundaries

Importing a package outside the standard library can make module-aware commands update go.mod and go.sum. Review those changes, and the dependency's source and licence, before accepting them. go mod tidy adds missing modules and removes unused requirements, so run it only once you understand the dependency graph it is about to change.

go get changes the current module's dependency requirements; it is not a general install command. For a command published as a versioned module, go install example.com/tool/cmd/[email protected] installs its executable into GOBIN, or normally GOPATH/bin, without touching the current module. Prefer an explicit version over @latest when repeatability matters, and check the destination with go env GOBIN GOPATH.

Warning: installation here is a user-level change. If a build instruction tells you to write under /usr/local/bin, stop and check ownership, provenance and rollback first: a privileged copy hands every user on the box an unreviewed binary. Undo a user-level install by removing the exact executable from the reported GOBIN or GOPATH/bin directory, and never remove a shared executable just because one project build failed.

Common traps

Done means