go mod download 不是独立完成依赖管理的命令,它仅拉取 go.mod 中声明的依赖且不校验、不写入 go.sum、不清理缓存;必须与 go mod tidy -v 和 go mod verify 串行执行,并预置 goproxy/goprivate、隔离模块缓存,才能保障构建可重现性、供应链安全与部署稳定性。

go mod download 不是“下载完就完事”的命令,它直接决定构建可重现性、供应链安全和部署稳定性。生产环境执行前必须明确:它只拉取 go.mod 中声明的依赖(含间接依赖),但不校验、不写入、不清理——这些都得靠后续动作补上。
为什么不能直接在 CI/CD 流水线里裸跑 go mod download
常见错误现象:CI 构建成功,线上运行报 cannot find package 或 panic 于某个未显式 require 的子依赖;或某天突然构建失败,因为某个间接依赖的 tag 被删了(比如作者 force-push 覆盖了 v1.2.0)。
根本原因在于:go mod download 默认只按 go.mod 拉取,但不会强制检查 go.sum 是否完整覆盖所有依赖的 checksum,也不保证所有 transitive 依赖都被显式锁定。
- 它跳过
go.sum校验步骤,无法发现被篡改或中间人劫持的包 - 它不触发隐式依赖的自动补全,
go list -m all显示的模块可能比go.mod多,但download不管那些没出现在 require 块里的 - 它不清理缓存中已失效的版本(比如本地有 v1.1.0,远程已删,但
download不报错也不重试)
生产环境推荐组合动作:go mod tidy -v + go mod download -x + go mod verify
这三步不是可选,而是必须串行执行,顺序不能乱:
-
go mod tidy -v:扫描全部.go文件,补全缺失的require,移除未引用的依赖,并**重写go.mod和go.sum** —— 这是锁定真实依赖图的第一步 -
go mod download -x:加-x参数输出实际下载路径和命令,便于审计;它会拉取tidy后go.mod中所有条目(含 indirect),并**触发 checksum 写入go.sum**(如果缺失) -
go mod verify:对所有已下载模块逐个比对go.sum中记录的哈希值,**失败即 exit 1**,这是最后的安全闸门
注意:go mod tidy 本身不下载,go mod download 本身不校验,go mod verify 本身不拉取——三者职责分离,缺一不可。
GOPROXY 和 GOPRIVATE 必须预置,且不能依赖默认值
生产环境严禁使用 GOPROXY=https://proxy.golang.org,direct 这类默认配置,尤其当项目含私有模块时:
- 若未设
GOPRIVATE,私有仓库地址(如git.company.com/internal/lib)会被代理尝试拉取,必然 404,然后 fallback 到direct——但此时 Git 认证往往未配置,导致静默失败或卡住 - 若代理地址不可达(如内网无外网出口),
go mod download会卡在第一个超时请求上,而非快速 failover - 多个代理用英文逗号分隔,末尾必须带
direct,否则私有库完全无法拉取
正确做法是:CI 环境变量中硬编码,例如:
export GOPROXY=https://goproxy.cn,https://goproxy.io,direct export GOPRIVATE=git.company.com/internal,github.com/myorg/private
别信 “公司统一配置”,每个构建节点必须独立确认生效。
容易被忽略的缓存与隔离问题
go mod download 默认复用 $GOCACHE 和 $GOPATH/pkg/mod,这在共享构建机上极危险:
- 不同项目共用同一模块缓存目录,A 项目
go.sum锁的是 v1.2.0,B 项目却因缓存里已有 v1.3.0 而偷偷用了新版(checksum 不匹配但未触发校验) - 并发构建时,
go mod download可能写冲突,导致部分模块下载不全,go build却不报错(Go 1.21+ 有改进,但不绝对) -
go clean -modcache是重置手段,但生产 CI 中不应依赖它——应使用干净容器或临时GOPATH
最稳妥方式:为每次构建设置独立模块缓存目录,例如:
export GOMODCACHE=$(mktemp -d) go mod download -x
这样既隔离又可审计,失败后整个目录可直接丢弃,不留残留风险。











