
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。
[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"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 服务器折磨过的人试试。
© 2026 四月
原文链接:https://www.aprilzz.com/tools/walgit-s3-git-server
相关文章
Sem:基于 Git 的语义化版本控制工具——让 AI Agent 理解代码变更的真正含义
Sem 是一个建立在 Git 之上的语义化版本控制工具,用 tree-sitter 解析代码,展示函数、类、方法层面的变更,而不是行级别的 diff。AI Agent 的代码理解准确率提升 2.3 倍。
Hister:一个自己掌控的私有全文搜索引擎,把你看过的网页变成可检索的知识库
书签只能找回标题,搜索引擎搜不到你记得的内容。Hister 把浏览器访问过的页面、本地文件和爬取的内容全文索引到你自己控制的服务器上,支持字段过滤、短语、通配符、MCP 接入,AGPLv3 开源。
Rust Glancer:把 Rust 语言服务器的内存压到 100MB 以下,rust-analyzer 之外的轻量选择
一个花了 4 个月开发的 Rust LSP 替代实现,通过'冻结分析结果'的核心设计,把大型项目的语言服务器内存占用从 GB 级压到 100MB 以下,还能免重索引热重启。