Expert · CLI tools and Go in cloud and DevOps · topic 40 of 46 · 30 min
Cross-compiling and build flags
Explanation
What cross-compiling means
Normally a compiler builds for the machine it runs on. Cross-compiling means building on one system for another: on your Windows laptop you build a Linux binary for the server, or an ARM binary for a Raspberry Pi or an AWS Graviton machine. With Go this needs no extra tools for pure Go code.
| GOOS / GOARCH | Runs on |
|---|---|
| linux / amd64 | Most cloud servers and containers (Intel/AMD) |
| linux / arm64 | AWS Graviton, many newer cloud VMs, Raspberry Pi (64-bit OS) |
| windows / amd64 | Windows PCs and servers |
| darwin / arm64 | Apple Silicon Macs (M1 and later) |
| darwin / amd64 | Older Intel Macs |
# Linux / macOS / Git Bash
GOOS=linux GOARCH=arm64 go build -o bin/api-linux-arm64 .
GOOS=windows GOARCH=amd64 go build -o bin/api.exe .
go tool dist list # every supported GOOS/GOARCH pair# Windows PowerShell
$env:GOOS = "linux"; $env:GOARCH = "amd64"
go build -o bin/api-linux-amd64 .
Remove-Item Env:GOOS, Env:GOARCH # back to normal buildscgo and static binaries
cgo lets Go code call C libraries. When cgo is used, the binary may depend on the system C library (libc) and cross-compiling needs a C cross-compiler. Most backend services do not need cgo. Setting CGO_ENABLED=0 forces a pure-Go, fully static binary: it runs on any Linux of that architecture, including empty "scratch" and "distroless" container images. Go already disables cgo by default when cross-compiling.
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o api .Build flags worth knowing
| Flag | Effect |
|---|---|
| -o NAME | Output file name |
| -trimpath | Removes your local folder paths from the binary (cleaner, more reproducible) |
| -ldflags="-s -w" | Strips the symbol table and debug info: smaller binary, but harder to debug with a debugger |
| -ldflags="-X main.version=1.4.0" | Sets a string variable at build time |
| -race | Builds with the race detector (testing only) |
| -tags NAME | Includes files with a matching //go:build NAME constraint |
package main
import "fmt"
var version = "dev" // overwritten at build time
func main() {
fmt.Println("expense-api", version)
}
// go build -ldflags="-X main.version=1.4.0" -o api .
// ./api → expense-api 1.4.0Code for one OS only
Files ending in _linux.go, _windows.go or _darwin.go are only compiled for that system. A //go:build line at the top of a file gives finer control, such as //go:build linux && arm64. Use this for small OS-specific pieces, like file paths or system calls.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Cross-compiling and build flags · 1/7
Go can build programs for other systems from your own machine. That is cross-compiling.
Prefer a video by a teacher? Find free videos on “golang cross compile GOOS GOARCH CGO_ENABLED ldflags version” on YouTube ↗
In real life: Shipping a support tool to every laptop
An IT team builds a "diagnose" tool that collects network and disk information for support tickets. From one CI job they build five binaries (Windows, Linux, macOS Intel, macOS Apple Silicon, Linux ARM), each stamped with the release version, and attach them to an internal release page. Employees download one file and run it; nothing else to install.
#!/usr/bin/env bash
set -e
VERSION=$(git describe --tags --always)
for target in linux/amd64 linux/arm64 windows/amd64 darwin/amd64 darwin/arm64; do
GOOS=${target%/*}
GOARCH=${target#*/}
ext=""; [ "$GOOS" = "windows" ] && ext=".exe"
CGO_ENABLED=0 GOOS=$GOOS GOARCH=$GOARCH \
go build -trimpath -ldflags="-s -w -X main.version=$VERSION" \
-o "dist/diagnose-$GOOS-$GOARCH$ext" .
donePractice (0/4 done)
Which command builds a Linux ARM 64-bit binary?
What does CGO_ENABLED=0 give you?
var version = "dev" in package main. Which flag sets it to 2.0.1 at build time?
Take any small Go program, add a version variable, and build it for linux/amd64, windows/amd64 and darwin/arm64 with CGO_ENABLED=0, -trimpath and a version stamp. Compare the file sizes with and without -ldflags="-s -w", and run go version -m on one binary.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Docker images for Go servicesCLI tools and Go in cloud and DevOps · same module
- Go in cloud and DevOps: Kubernetes, Terraform and PrometheusCLI tools and Go in cloud and DevOps · same module
- What C++ is and how compiling worksC++ Programming (modern C++ and DSA) · First steps: compiling, output, variables and input
- Libraries: static and sharedC Programming (from zero to systems) · The preprocessor, multi-file programs and make
- What C is and why it still mattersC Programming (from zero to systems) · Getting started with C
- What Java is and where it is usedJava Programming (Core to Spring Boot) · Getting started with Java
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which command lists every OS/architecture pair Go supports?
Answer: go tool dist list
go tool dist list prints pairs like linux/amd64.
What does -ldflags="-s -w" remove?
Answer: The symbol table and debug information
The binary gets smaller but is harder to debug.
Does cross-compiling prove your program works on the target system?
Answer: No, you still need to run and test it there
Building and running are different things.
darwin/arm64 is for...
Answer: Apple Silicon Macs
darwin is macOS; arm64 is Apple Silicon.
Which flag asks the compiler to optimise the program for speed?Related
Answer: -O2
-O2 (capital letter O) turns on optimisation. Lowercase -o names the output file.
What does distroless static NOT include?Related
Answer: A shell
No shell or package manager, which reduces the attack surface.
Which flag links the math library on Linux?Related
Why keep os.Exit only in main?Related
Answer: os.Exit skips deferred calls, and code that returns errors is easier to test
The run() pattern keeps clean-up and testing simple.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “CLI tools and Go in cloud and DevOps” before its exam: Docker images for Go services, Go in cloud and DevOps: Kubernetes, Terraform and Prometheus.
- Module 9 exam: CLI tools and Go in cloud and DevOps · 12 questions · 20 min, without help. 4 topics of this module still to finish first.
- Then the next module, Careers and interviews. 46 topics left in the course before the final exam.