






















本文永久链接 – 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 私有库的源码。这是一种新的交付模式:不再是"我们运营一个服务,客户调用接口",而是代码本身要跑到客户自己组织的机房里,由客户自己的工程师编译、构建和维护。
这一下子把五年来被小心维护的假设全部打破了:
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》 将带你:
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!
我们致力于打造一个高品质的 Go 语言深度学习 与 AI 应用探索 平台。在这里,你将获得:
衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

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

此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。