goos 和 goarch 必须同时显式设置,缺一不可;仅设其一将生成当前平台二进制,导致运行时报错;cgo_enabled=0 是稳定交叉编译的默认安全开关,纯 go 项目应强制关闭以避免动态链接失败。

GOOS 和 GOARCH 必须成对设置,缺一不可
只设 GOOS=linux 不设 GOARCH,Go 不会自动补全,也不会报错,但生成的仍是当前机器平台的二进制(比如在 macOS 上跑出 darwin/amd64),运行时直接报 Bad CPU type in executable 或 exec format error。这不是编译失败,而是目标不匹配。
正确做法是始终同时指定两个变量:
GOOS=linux GOARCH=arm64 go build -o app-linux-arm64 .-
GOOS=windows GOARCH=amd64 go build -o app.exe .(Windows 二进制必须带.exe后缀) -
GOOS=darwin GOARCH=arm64 go build -o app-darwin-arm64 .(M1/M2 Mac)
查所有合法组合用 go tool dist list,别靠记忆或文档猜——比如 GOOS=freebsd GOARCH=riscv64 是否支持,运行命令一眼可知。
CGO_ENABLED=0 是默认安全线,不是可选项
启用 CGO(CGO_ENABLED=1)会让 go build 尝试调用宿主机的 gcc 或 clang,并链接本地 C 库(如 libc、libSystem.dylib)。你在 macOS 上没有 Windows 的 kernel32.lib,Linux 上也没有 Darwin 的系统库——结果就是 exec: "gcc": executable file not found 或链接失败。
纯 Go 项目(比如只用 net/http、encoding/json、os/exec)应显式加 CGO_ENABLED=0:
- 生成完全静态链接的二进制,零运行时依赖
- 避免 DNS 解析降级问题(启用 CGO 时可能读
/etc/resolv.conf,禁用后自动走 Go 自带 resolver) - 若用了 SQLite,换用
github.com/ncruces/go-sqlite3(纯 Go 实现),原版mattn/go-sqlite3必须 CGO
不要用 export 设置 GOOS/GOARCH,用 env 前置更干净
在 shell 里执行 export GOOS=linux 再跑 go build,看似省事,实则危险:后续所有 go 命令(包括 go test、go mod tidy)都会继承该变量。CI 脚本漏重置、zsh 环境继承、甚至你第二天继续开发都可能踩坑。
推荐写法是把环境变量直接前置在命令前:
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o app .- 写脚本时也保持这种风格,避免污染全局环境
- 如果真要批量构建,用循环 + 变量展开,而不是
export后统一 build
Docker 是最稳妥的多平台构建方案
本地交叉编译在 CGO 场景下极易失败,尤其当你要编译 Linux ARM64 且依赖 OpenSSL 或 CUDA 时,配齐 aarch64-linux-gnu-gcc、头文件路径、链接库版本,复杂度远超 Go 本身。这时候 Docker 不是“高级玩法”,而是事实标准。
一个最小可行镜像示例:
FROM golang:1.21-alpine RUN apk add --no-cache musl-dev gcc linux-headers WORKDIR /app COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o app-arm64 . RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o app.exe .
关键点:
- 基础镜像选
golang:1.21-alpine(小、快、自带 musl) -
apk add补上gcc和linux-headers(仅当需 CGO 时才真正用到) - 即使没用 CGO,加这些包也不影响构建,但能覆盖更多场景
真正容易被忽略的是:跨平台二进制不能在宿主机上直接验证是否“能跑”——file app.exe 看到 PE32+ executable 才算成功;./app.exe 在 macOS 上必然失败,这不是 bug,是预期行为。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











