工具推荐·阅读约 2 分钟·
walgit:把 Git 仓库直接塞进对象存储的服务器,单二进制搞定,仓库比机器还大也能跑

walgit:把 Git 仓库直接塞进对象存储的服务器,单二进制搞定,仓库比机器还大也能跑

walgit 是 Shopify CEO Tobi Lütke 开源的 Rust Git 服务器:无数据库、无主节点、无本地状态,一个二进制指向 S3 桶就是完整部署。支持 bundle-uri、LFS、Web UI,仓库规模可以超过机器本身。

原文来源:walgit — a git server that is one binary in front of an object store — Shopify CEO 开源的 Git 服务器:一个二进制 + 一个 S3 桶,无数据库无主节点,仓库可以比运行它的机器还大。

自托管 Git 服务器一直是个尴尬的存在。Gitea、Forgejo 这类方案轻量易用,但仓库一多、一变大就开始吃资源;想达到 GitHub 的规模,就得照着 GitHub 的 Spokes 架构搭一整套——多副本、三阶段提交、数据库、固定副本集,养一群"宠物服务器"伺候着。对独立开发者和中小团队来说,两头都不讨好。

walgit 想用另一种方式解决这个问题:仓库不放在磁盘上,直接放进对象存储。它实现了 Cursor 团队在《Git at any scale》里描述的 Continuity 架构,用 Rust 重写,并且针对"机器比仓库还小"的场景做了大量适配。作者是 Tobi Lütke——没错,就是 Shopify 的 CEO,一个在公司忙着管电商帝国之余,周末写代码的开源爱好者。

三行配置的完整部署

先感受一下 walgit 的部署有多简单。整个流程就是:写一个配置文件,跑一个二进制,然后 push。

code
[server]
listen = "0.0.0.0:8080"
public_url = "https://git.example.com"
auto_create_on_push = true
[server.auth]
mode = "token"
tokens = [{ principal = "me", token_env = "WALGIT_TOKEN_ME", write = true }]
[store]
backend = "s3"
bucket = "my-walgit"
code
WALGIT_TOKEN_ME=$(openssl rand -hex 24) walgit serve --config walgit.toml
 
# push 到一个新名字,仓库自动创建
git -c http.extraHeader="Authorization: Bearer $WALGIT_TOKEN_ME" push https://git.example.com/acme/app.git main

没有数据库要建,没有主节点要选,没有状态要同步。任何数量的机器指向同一个桶,它们就服务同一批仓库,彼此之间零协调。杀掉所有实例,丢掉的只是缓存热度,不是数据。

—— 广告 ——

核心原理:WAL 就是仓库

walgit 的架构洞见来自 Continuity:让对象存储里的预写日志(WAL)成为唯一真相,磁盘上的仓库只是缓存

一次 push 的流程是这样的:客户端推来的 pack 被索引、校验后,作为一个不可变对象上传到桶里,然后一个极小的 manifest 文件通过 compare-and-swap(CAS)原子重写。这个 CAS 就是共识本身——不需要选举、不需要法定人数、不需要主节点。两个实例同时接受 push?不可能两个都赢,总有一个会收到 412 然后重试。任何一台从没见过这个仓库的实例,读一遍日志就能拥有它。

读操作更简单:先对 manifest 发一个条件 GET,304 就用本地副本,200 就应用新条目。因为每次读都会先向桶确认,所以不存在"最终一致性"的窗口——没有"eventually",只有"现在"。

这个设计带来一个副产品:完整溯源。每一次 push、每一次压缩重打包,都在日志里,可以回放到任意时间点。

三个为"小机器"而生的特性

仓库放进对象存储只是第一步。walgit 真正有意思的地方,是它解决"仓库比机器大"的三个增量特性:

remote reader(远端读取器):仓库的 pack 文件放不进这台机器的磁盘?没关系,walgit 通过 HTTP range 请求直接从桶里按需读对象。Git 协议层面完全透明,前端照样展示代码、历史、diff,只是数据是从桶里实时拉取的。

history pack(历史包):commits 和 trees 留在本地,blobs 留在桶里。这样日常浏览、分支操作不需要大 blob,需要大文件时才去桶里取。

bundle-uri(克隆分发):最妙的一个。克隆字节完全绕开服务器——walgit 按日历时间切片(每周全量 + 每日增量链),把 bundle 作为纯静态文件交给桶或 CDN 分发。新克隆直接从 CDN 下载最新全量加增量链,服务器只处理剩余部分。一台小机器托管一个超大 monorepo,克隆速度反而比传统方案快,因为带宽压力全被 CDN 扛走了。

该有的功能一个不少

架构很激进,功能却非常完整:

  • smart HTTP v0/v2:ls-refs 前缀过滤、fetch filter/shallow/deepen/sideband-all、原子 receive-pack、sha1 和 sha256 仓库都支持
  • Git LFS:对象直接存桶里,还能从上游 LFS 服务器透传导入
  • Web UI + JSON API:React 界面(tree、blob、commits、diff),repos.js 是个零依赖 SDK,页面、脚本、Agent 都能直接调
  • push 策略:每个仓库一份 policy.json,可配置受保护分支、fast-forward only、白名单
  • Webhooks:一个小桥接程序监听 WAL,按 (repo, seq, ref) 精确一次地把事件 POST 出去
  • 认证:none(本机实验)、token(静态令牌)、oidc(任何 OpenID Connect issuer——Google、Entra、Okta、Keycloak 都行)

部署形态也很灵活:server.roles 可以把服务拆成 serve(对外服务)、maintain(压缩维护)、events(webhook 桥)三种角色,多台机器各司其职,但任何一台都能提供 refs 级别的读取。

一些值得注意的设计细节

  • 维护是自愈的:checkpoint、bundle 构建、几何压缩、连通性审计,由一个循环根据 (config, WAL) 计算期望状态,每次做一件最要紧的缺失工作。宕机不留坑,删掉的东西会被一模一样地重建出来。
  • 慢操作都有进度:任何耗时任务都有 id、日志和进度流——给 git 走 sideband 2(remote: * ...),给浏览器走 SSE。不会让你干等。
  • 成本模型很清晰:所有协议设计的评判标准是"到桶的往返次数"(docs/ROUNDTRIPS.md)。作者把预算花在哪说得明明白白。
  • 故障注入测试:自带模拟器测试崩溃、分区、陈旧读,cargo test -p walgit-server --test sim 一条命令验证极端情况。
  • MIT 协议,完全开源。

适合谁用?

适合:有 S3/MinIO/R2/Ceph 等对象存储资源,想要自托管 Git 的独立开发者和中小团队;需要托管超大 monorepo 的人;受够了自建 Git 服务器"磁盘满了怎么办、主节点挂了怎么办"的人。理论上讲,你的 Git 服务可以像无状态应用一样随意扩容缩容。

不太适合:只想要一个"开箱即用、带完整 CI/CD 生态"的一体化平台的人——walgit 只管 Git 托管本身,没有内置 CI、issue tracker 这类周边设施(webhooks 可以接到你现有的 CI 上)。另外它还在快速迭代期,生产环境大规模使用前建议先在小流量下跑一段。

一句话总结:"桶就是仓库,机器全是缓存"——这个思路把 Git 自托管从"养宠物"变成了"放牛",值得所有被自建 Git 服务器折磨过的人试试。

分享到
微博Twitter

© 2026 四月

原文链接:https://www.aprilzz.com/tools/walgit-s3-git-server