Skip to content

Expert · CLI tools and Go in cloud and DevOps · topic 40 of 46 · 30 min

Cross-compiling and build flags

Explanation

Go can build a binary for another operating system and processor from your own machine: set GOOS (linux, windows, darwin) and GOARCH (amd64, arm64) before go build. CGO_ENABLED=0 gives a fully static binary that runs in minimal containers. -ldflags can shrink the binary and stamp a version into it.

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 / GOARCHRuns on
linux / amd64Most cloud servers and containers (Intel/AMD)
linux / arm64AWS Graviton, many newer cloud VMs, Raspberry Pi (64-bit OS)
windows / amd64Windows PCs and servers
darwin / arm64Apple Silicon Macs (M1 and later)
darwin / amd64Older Intel Macs
bash
# 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
The variables apply only to that one command.
powershell
# Windows PowerShell
$env:GOOS = "linux"; $env:GOARCH = "amd64"
go build -o bin/api-linux-amd64 .
Remove-Item Env:GOOS, Env:GOARCH   # back to normal builds
In PowerShell, environment variables stay set for the session until you remove them.

cgo 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.

bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o api .
The standard recipe for a Linux container binary.

Build flags worth knowing

FlagEffect
-o NAMEOutput file name
-trimpathRemoves 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
-raceBuilds with the race detector (testing only)
-tags NAMEIncludes files with a matching //go:build NAME constraint
go
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.0
Stamping the version (or Git commit) helps you know exactly what is running in production.
Tip: Since Go 1.18, binaries also embed module and Git information automatically. Run go version -m ./api to see it, or read it in code with runtime/debug.ReadBuildInfo.

Code 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.

Careful: Cross-compiling does not test your code on the target. A Linux binary built on Windows still needs to be run on Linux (or in Docker or WSL) before release. Also mind file paths: use filepath.Join, not hard-coded backslashes.

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

Cross-compiling

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.

bash
#!/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" .
done
One loop, five platforms. ${target%/*} keeps the part before the slash; ${target#*/} keeps the part after it.

Practice (0/4 done)

Task 1 · Question

Which command builds a Linux ARM 64-bit binary?

Task 2 · Question

What does CGO_ENABLED=0 give you?

Task 3 · Question

var version = "dev" in package main. Which flag sets it to 2.0.1 at build time?

Task 4 · Practice

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

Question 1

1.Which command lists every OS/architecture pair Go supports?

Question 2

2.What does -ldflags="-s -w" remove?

Question 3

3.Does cross-compiling prove your program works on the target system?

Question 4

4.darwin/arm64 is for...

Read more (free)

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.

From What C++ is and how compiling works

What does distroless static NOT include?Related

Answer: A shell

No shell or package manager, which reduces the attack surface.

From Docker images for Go services

Which flag links the math library on Linux?Related

Answer: -lm

The library is libm.

From Libraries: static and shared

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.

From Command-line tools with flag and os

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

  1. 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.
  2. 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.
  3. Then the next module, Careers and interviews. 46 topics left in the course before the final exam.

← Previous: Command-line tools with flag and os