Introduction
When CGO_ENABLED=1, Go uses the system C compiler (gcc or clang) to compile C code in cgo-enabled packages like database/sql drivers, net, and any package using import "C". Cross-compiling (e.g., building linux/amd64 from macOS/arm64) requires a cross-compiler for the target architecture. Without it, the linker fails with cryptic errors.
Symptoms
gcc: error: unrecognized command-line option '-m64'ld: unknown option: --build-id=none# runtime/cgo: gcc: error: unrecognized argument: '-marm'exec: "x86_64-linux-gnu-gcc": executable file not found in $PATHclang: error: unable to execute command: Segmentation fault
``` $ GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build ./cmd/server # runtime/cgo cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH
$ CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build . # runtime/cgo xcrun: error: unable to find utility "clang", not a developer tool or in PATH ```
Common Causes
- Cross-compiling from macOS to Linux without cross-compiler
- Building ARM64 binary on x86_64 machine without
aarch64-linux-gnu-gcc - Docker build from multi-arch base images without gcc
- CI runner missing cross-compilation toolchain
- CGO_ENABLED defaults to 1 on macOS but target is Linux
Step-by-Step Fix
- 1.Disable CGO if not needed (simplest fix):
- 2.```bash
- 3.# Most Go packages don't need CGO
- 4.CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./cmd/server
# Pure Go binary - works on any platform file ./server # ELF 64-bit LSB executable, x86-64, statically linked ```
- 1.Install cross-compiler on Ubuntu/Debian:
- 2.```bash
- 3.# For linux/amd64 from any host
- 4.sudo apt install -y gcc-x86-64-linux-gnu
- 5.CC=x86_64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build
# For linux/arm64 from x86_64 sudo apt install -y gcc-aarch64-linux-gnu CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build
# For linux/arm/v7 sudo apt install -y gcc-arm-linux-gnueabihf CC=arm-linux-gnueabihf-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm go build ```
- 1.Cross-compile from macOS:
- 2.```bash
- 3.# Install cross-compiler via Homebrew
- 4.brew install FiloSottile/musl-cross/musl-cross
# Build linux/amd64 with musl (static binary) CC=x86_64-linux-musl-gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "-extldflags=-static"
# Build linux/arm64 CC=aarch64-linux-musl-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-extldflags=-static" ```
- 1.Use Docker for reliable cross-compilation:
- 2.```dockerfile
- 3.FROM golang:1.22-bookworm AS builder
- 4.WORKDIR /app
- 5.COPY go.mod go.sum ./
- 6.RUN go mod download
- 7.COPY . .
- 8.# Build inside target-compatible container
- 9.RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
- 10.go build -ldflags="-s -w" -o server ./cmd/server
FROM debian:bookworm-slim COPY --from=builder /app/server /server CMD ["/server"] ```
- 1.Use zig as universal C cross-compiler:
- 2.```bash
- 3.brew install zig
# zig cc acts as a drop-in replacement for any target CC CC="zig cc -target x86_64-linux-gnu" CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build CC="zig cc -target aarch64-linux-gnu" CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build ```
Prevention
- Set
CGO_ENABLED=0by default unless CGO is explicitly needed - Use
-ldflags "-s -w"to strip debug info and reduce binary size - In CI, install cross-compilers as part of the build image
- Use
go tool dist listto see supported OS/ARCH combinations - Test cross-compilation locally before pushing to CI
- Use
filecommand to verify binary architecture after build