Build a Go binary safely with go build
You will compile a small Go module, write its executable to an explicit path, run it, and check the result without installing anything system-wide. Allow 10 to 20 minutes for a first build, including time to inspect module and compiler errors. The examples use an ordinary user account. No command here needs sudo.
The route
Jump straight to the step you need, or tick off Done means at the end.
1. Check the toolchain and project shape
Run these commands 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 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 a list of local source files or standard-library packages, but a project with third-party dependencies needs its own module context.
2. Compile the packages 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 after 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
Common trap: running go build in a parent directory and expecting it to discover every project below. Package patterns are interpreted from the current module. Change to the intended module root, or use the command's -C option on toolchains that provide it.
3. Write a 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 component, while a list of source files uses the first source file. This is another reason to prefer -o in scripts and release jobs.
4. Make a safe release-style build
Go may embed build paths and, when the project is in 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, 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. These flags change the binary metadata, not the program's source-level behaviour. If you need traceability, keep the commit and toolchain version in your build record instead of assuming the binary contains 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
Do not use -mod=mod casually in a review or release job. It permits the command to update module files, which is 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 changing 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 are compiled. -n prints the commands without running them. These options help separate a source error from a wrong directory, selected build tag, missing compiler, or unexpected module resolution. Keep the first error and its package path; later errors are often consequences.
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 requires network access, a proxy or 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 is most useful when the program exercises concurrent code. It adds overhead and is not a replacement for tests. -asan and -msan have stricter compiler and platform requirements; use them only after checking go help build and the host C compiler.
Do not add -a to every build by habit. It forces packages that are already current to rebuild and can make a normal edit-test cycle much slower. The build cache is normally safe to use and does not turn go build into an install operation.
Done means
- You confirmed the
gobinary and version used by the shell. - You built the intended package pattern from the correct module root.
- You wrote the executable to a known path with
-oand ran it. - You used
-trimpathand an explicit VCS policy when producing a distributable binary. - You chose
-mod=readonlyor-mod=vendorwhen dependency changes or network access were not acceptable. - You kept diagnostics separate from state-changing dependency repairs.