Build a Go Binary Safely with go build
go build turns a module into a binary, but leave the destination to chance and the file lands wherever the default naming rules decide. This walkthrough compiles a small Go module, writes the executable to an explicit path, runs it, and checks the result, all without installing anything system-wide. Allow 10 to 20 minutes for a first build, including time to read module or compiler errors.
The route
Jump straight to the step you need, or tick off Done means at the end.
The examples use an ordinary user account. No command here needs sudo.
1. Check the toolchain and project shape
Run these from the directory containing the project you intend to build. A module-aware project normally has a go.mod file at its module root:
$ command -v go
/usr/local/go/bin/go
$ go version
go version go1.26.1 linux/amd64
$ go env GOMOD
/path/to/project/go.mod
Your path and version may differ. The installed command on this machine is Go 1.26.1 on Linux amd64. The local Debian package is only version 1.22, and its go-build(1) page is dated 2 August 2022, so always check the command you will actually run when a flag or output name matters.
Checkpoint
If command -v go prints nothing, install Go through your normal distribution or organisation process before continuing. If go env GOMOD prints /dev/null, you are outside a module; you can still build local source files or standard-library packages, but a project with third-party dependencies needs its own module context.
2. Compile without choosing an output file
From the module root, use ./... to select the packages below the current directory:
$ go build ./...
A successful build normally prints nothing and returns status 0. It compiles the named packages and their dependencies but does not install them. For a non-main package, the compiled object is discarded once the build has checked it. Files ending in _test.go are ignored by this command.
Do not mistake silence for a missing build. Verify the status explicitly when a script or checklist needs evidence:
$ go build ./... && printf 'build status: ok\n'
build status: ok
Tip
A common trap is running go build in a parent directory and expecting it to discover every project below. Package patterns are interpreted from the current module, so change to the intended module root, or use the command's -C option on toolchains that provide it.
3. Write the binary to a deliberate destination
For an executable, make the destination part of the command rather than relying on the default name:
$ mkdir -p ./bin
$ go build -o ./bin/greeter ./cmd/greeter
$ file ./bin/greeter
./bin/greeter: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, ...
The final line varies by Go version and platform. The useful check is that file identifies an executable for the target system. Run it from the project directory:
$ ./bin/greeter
build-ok
The -o flag can name an output file or a directory. If the destination is an existing directory, or ends with a slash, Go writes each resulting executable there. An explicit filename avoids confusion when several commands produce similarly named programs.
The manual page shipped with the local Debian package describes the default executable name as the source directory or first source file. The installed Go 1.26.1 help is more specific: for a package path it uses the last non-major-version path component, while a list of source files uses the first source file. Another reason to prefer -o in scripts and release jobs.
4. Make a safe release-style build
Go can embed build paths and, inside a repository, version-control information. For an artefact that should not expose local paths, add -trimpath. To make VCS stamping fail closed or stay out of the binary entirely, choose the policy explicitly:
$ go build -trimpath -buildvcs=false -o ./bin/greeter ./cmd/greeter
$ ./bin/greeter
build-ok
-trimpath removes file-system paths from recorded file names. -buildvcs=false omits version-control information. Both change binary metadata, not source-level behaviour. If you need traceability, keep the commit and toolchain version in your build record instead of assuming the binary carries them.
For a module with dependencies, use the mode that matches your supply-chain policy. -mod=vendor builds from a repository's vendor directory when it is complete; -mod=readonly refuses changes to go.mod and go.sum while resolving packages. A useful CI check is:
$ go build -mod=readonly -trimpath -buildvcs=false -o ./bin/greeter ./cmd/greeter
Warning
Do not use -mod=mod casually in a review or release job. It permits the command to update module files, a state change you may not have intended. If you deliberately use it while repairing dependency metadata, review the resulting diff before committing it.
5. Add diagnostics before touching source
When a build fails, first ask Go to show package names or the commands it would run:
$ go build -v ./...
$ go build -n ./...
-v prints package names as they compile. -n prints the commands without running them. Both help separate a source error from a wrong directory, a selected build tag, a missing compiler, or unexpected module resolution. Keep the first error and its package path; later errors are often just consequences of it.
For a failure involving a changed dependency, check the module root and dependency mode:
$ go env GOMOD GOPATH GOMODCACHE
$ go list -m all
If the project needs network access, a proxy or a private repository may be involved. Do not paste credentials into flags or commit generated module-cache files. Fix authentication and dependency policy outside the build command, then rerun with the same explicit mode.
6. Use optional checks with clear boundaries
On Linux amd64, the installed toolchain supports the race detector:
$ go build -race -o ./bin/greeter-race ./cmd/greeter
$ ./bin/greeter-race
build-ok
The race detector earns its overhead when the program exercises concurrent code; it is not a replacement for tests. -asan and -msan have stricter compiler and platform requirements, so check go help build and the host C compiler before reaching for them.
Do not add -a to every build out of habit. It forces packages that are already current to rebuild, which can make a normal edit-test cycle much slower. The build cache is normally safe to rely on and does not turn go build into an install operation.
Done means
- Toolchain confirmed: you checked the
gobinary and version used by the shell. - Right scope built: the intended package pattern was built from the correct module root.
- Binary placed deliberately: the executable went to a known path via
-o, and you ran it. - Release hygiene applied:
-trimpathand an explicit VCS policy were used for a distributable binary. - Dependency policy chosen:
-mod=readonlyor-mod=vendorwas set when dependency changes or network access were not acceptable. - Diagnostics kept separate: troubleshooting flags were not mixed with state-changing dependency repairs.