
本文永久链接 – https://tonybai.com/2026/07/05/go-private-modules-lessons-learned
大家好,我是Tony Bai。
五年前,我写过两篇文章,讲的是怎么在公司内部搭一套私有 Go module 拉取方案。三年前,我又补了第三篇,说这套东西“跑了两年,基本没什么大问题”。
三篇文章,五年时间,我一直以为这件事已经被我解决了。
直到上个月,我们把一个 Go 私有库以白盒方式交付给一家客户。第二天一早,对方工程师就甩来一段报错:
$ go mod tidy
go: finding module for package mycompany.com/go/common
go: mycompany.com/go/common: unrecognized import path "mycompany.com/go/common":
https fetch: Get "https://mycompany.com/go/common?go-get=1":
dial tcp: lookup mycompany.com: no such host
我盯着这段报错愣了几秒,随即哑然失笑——这不就是五年前我们内部第一次遇到的那个问题吗?只不过这一次,“内部”变成了别人的“内部”。
五年里,我一直在同一个边界内部不断打磨这套方案,却从没想过,如果有一天代码要跨出这个边界呢?
客户的开发机、CI 机器,统统访问不到我们组织内部的私有 GOPROXY 服务器,也访问不到我们内部的 VCS。代码本身没有任何问题,问题出在一个我们几乎从未认真思考过的地方:这个 Go module 的 import path 本身,是一个只在我们自己的网络边界里才成立的“承诺”。

五年前,这个问题第一次找上我
这不是我第一次跟这类问题交手。
2021 年,我写过一篇《小厂内部私有Go module拉取方案》。当时的背景很简单:随着公司里 Go 项目变多,“代码重复"问题冒了出来,我们把公共代码抽成了内部私有仓库,于是就有了"如何让 go get 拉到私有Go代码库"这个需求。
大厂的答案很简单——因为大厂内部的 VCS 天然就挂在一个内部域名下(比如 git.company.com),import path 直接指向真实仓库地址即可,go get 该怎么工作就怎么工作。
但小厂或个人开发者通常没有这个条件:我们希望私有 module 的 import path 是干净的、带有自己域名语义的(比如 mycompany.com/go/common),但这个域名背后并没有真的托管代码,代码实际存放在内部的 Git 服务器上。这中间,需要一层"翻译”。
我们当时选择的方案是 govanityurls + Nginx + 内部 GOPROXY 的组合:govanityurls 负责把 mycompany.com/go/common 这样的 vanity import path 翻译成真实的仓库地址,内部 GOPROXY 负责统一代理公共 module 和私有 module 的拉取。开发者只需要把 GOPROXY 指向内部代理,剩下的事情对他们透明。
这套方案上线之后,确实解决了当时的问题。
方案在演进,但边界的假设没有变
2022 年,业务线决定把代码托管从 Gerrit 迁移到 GitLab,我又写了篇《小厂内部私有Go module拉取方案(续)》,记录了适配过程中踩的坑——SSH 密钥交换算法不匹配、vanity.yaml 里仓库地址的调整,等等。本质上,这次只是把"翻译层"背后指向的仓库换了个地方,原理没有变。
2023 年,我又写了第三篇《小厂内部私有Go module拉取方案3》。那时候这套基础设施已经稳定运行了两年,“总体感觉不错,没什么大问题”。文章里我提到了一个一直没有优雅解决的小麻烦:每次新增一个用作私有依赖的仓库,vanity.yml 都得手动改一次,开发者对这种运维琐事天然是抗拒的。同一篇文章里,我还顺手研究了一下 go mod replace 能不能绕开 vanity 层直接指向内部仓库——最后发现 Go 官方早就支持了这种用法,只是没多少人知道。
回头看这三篇文章,我当时关注的都是"怎么让方案更稳、更省心",但有一个前提我从来没有认真质疑过:
所有这些方案,都默认"拉代码的人"和"存代码的服务器"处在同一个网络边界、同一个信任边界里。
五年里,我们不断优化这个边界内部的体验,却从来没想过,如果有一天代码需要跨出这个边界呢?
今年,边界本身松动了
今年我们开始以白盒方式向客户交付 Go 私有库的源码。这是一种新的交付模式:不再是"我们运营一个服务,客户调用接口",而是代码本身要跑到客户自己组织的机房里,由客户自己的工程师编译、构建和维护。
这一下子把五年来被小心维护的假设全部打破了:
- 网络边界变了:客户访问不到我们内部的私有 GOPROXY,也访问不到我们内部的 Git 服务器。
- 信任边界变了:我们不可能把内部 VCS 的访问凭证交给客户,那意味着客户能拿到我们所有其他私有代码。
- 组织边界变了:
mycompany.com/go/common这样的 import path,在客户的网络里就是个死链接,govanityurls部署在我们这边,客户那边根本连不到。
于是我们不得不在客户组织内部,重新搭建一套简化版的私有 module 拉取方案——申请域名或者用内网 DNS 顶一下、配一遍 vanity 映射、协调 GOPROXY 或 GOPRIVATE、把仓库权限单独开一份给客户。整个过程和五年前第一次搭建时几乎一样繁琐,唯一的区别是,这次要在一个我们不熟悉、不受控的环境里,重新做一遍。
那一刻我意识到,这从来都不是一个"公司内部基础设施"问题,而是一个更普遍的问题:
只要代码需要跨越一个信任边界——从团队到团队,从公司到客户,从内网到公网——import path 和它背后真实仓库地址之间的这层"翻译",就会重新变成一个需要人工搭建的脆弱环节。
这个问题不是 Go 独有的,几乎任何一种需要"私有代码分发"的语言生态都会遇到类似的处境。只是 Go 因为 go get 直接依赖 import path 做域名解析这个设计,把这个问题暴露得格外明显。
命令行,正在重新变成“控制面”
过去这类"边界重新搭建"的问题,标准答案往往是造一个管理后台:一个 Web UI,让你去配置域名映射、TLS 证书、路由规则——本质上是把运维动作图形化。
但对开发者来说,这从来都不是最自然的心智模型。真正高频的运维动作,最后总会被收敛成一行命令:gh repo create、wrangler deploy、supabase db push——这几年你会发现,越来越多原本需要打开一个 Web 控制台才能完成的事情,正在被重新压缩进终端里的一行命令。命令行不再只是"极客的偏好",它正在重新成为很多产品事实上的控制面,AI 时代尤甚。
这和我这五年反复折腾的事情,其实是同一个道理:vanity.yml 手动改一次、Nginx 配一次、GOPROXY 调一次——这些散落在不同工具、不同配置文件里的动作,本质上都应该是一个命令的事。不管这个"边界"是公司内部,还是客户组织内部。
顺手把这五年的认知封装了一下
所以这次白盒交付踩完坑之后,我索性把这五年反复重复的这套操作——vanity import path 的映射、私有 module 的拉取配置——抽象成了一个通用的命令行工具,取名 gvu(gomodvanityurls 的缩写)。它现在正在做公测,官网在 gomodvanityurls.com。
它解决的问题很朴素:不管你是要在公司内部搭一套私有 module 拉取方案,还是像我们这次一样要在客户组织内部临时重建一套,都不用再手写 vanity.yml、配 Nginx、协调 GOPROXY 这些琐事了,一行命令搞定映射和分发。
这篇不是产品说明书,就不展开细节了,后面我会单独写几篇上手向的文章。感兴趣的话可以先去gvu官网看看,花五分钟体验一下。
这次,go build 一次成功
方案在客户组织内部重新搭好之后,我又收到了那位工程师的消息,这次贴的是:
$ go mod tidy
go: downloading mycompany.com/go/common v1.2.3
$ go build ./...
没有报错,干干净净地结束。
五年前那篇文章的开头,我写过"Go module 拉取公共依赖不再是痛点"。五年后我想补一句:私有依赖的拉取,只要代码不跨越边界,也早就不是痛点了。真正的痛点,永远出现在边界被打破的那一刻。
小结
这五年,我们的方案从“能用”到“稳定”,看起来是在不断进步,但本质上一直是在同一个边界内部打磨细节。
今年白盒交付撞到的这堵墙,才让我第一次看清问题的全貌:Go 私有 module 的拉取困境,本质上是一个"代码如何优雅地跨越信任边界"的问题,而不是一个"公司内部该怎么配置 GOPROXY"的问题。
五年三篇文章,记录的都是同一个问题在不同场景下的重复出现。这一次,我把重复出现的部分做成了一个工具。
接下来,我会写几篇 gvu 的上手教程和一些进阶用法,感兴趣的朋友可以留意后续文章。
如果你也被"私有 Go module 怎么优雅分发和拉去"这个问题困扰过,欢迎在评论区聊聊你的方案,说不定又是一次有意思的讨论。
还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:
- 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
- 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
- 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
- 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
- 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!
我们致力于打造一个高品质的 Go 语言深度学习 与 AI 应用探索 平台。在这里,你将获得:
- 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
- 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
- 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
- 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
- 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。
衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。
