An interactive explainer

The bucket is the repository.

walgit is a git server that is one Rust binary in front of an object store. No database. No leader. No local state that matters. This is the story of why hosting git was miserable, the architectural insight that changed the economics, and the small binary that realizes it.

scroll

Chapter 01 — the pain

Git was never meant to be served.

On your laptop, git feels instant. That feeling is an accident of physics — and it evaporates the moment the repository is no longer a file on a local disk.

Almost everything in a repository ends up compressed into packfiles: large binary files where objects — commits, trees, blobs — are laid out to be small, not to be read in order. Blobs are delta-compressed against other blobs that may sit megabytes away. The pack is a puzzle optimized for bytes on the wire, and every git operation solves its corner of that puzzle by seeking.

Reading one file out of history is a random walk: consult the .idx for an offset, seek into the pack, inflate a delta, discover its base lives at another offset, seek again. On a laptop this is fine — the pack sits in page cache and a seek costs microseconds. Each of those hops is a syscall against memory. Nobody notices.

Now move the repository to a network filesystem and every hop becomes a network round trip. The same walk that cost microseconds now costs milliseconds — per hop, thousands of times per operation. This is not a theory; it is why “just put the repositories on NFS” failed at every large host that tried it.

~50 µs
random read from local NVMe / page cache — the regime git was designed for
~1–10 ms
the same read over a network filesystem — 100× slower, paid per hop
1000s
of random hops in an ordinary fetch or checkout against a large pack

The design that survived looks like GitHub’s Spokes:

keep real repositories on local NVMe so upstream git does the work it was born to do, and replicate at the packfile level with strict consistency. It works — but the bill is steep. Every write goes through a three-phase commit across a fixed replica set. A database remembers which machines hold which repository. Replicas run leader election. Each repository is a little distributed system, and the fleet is a collection of pets: named machines with irreplaceable disks that you feed, patch, and grieve.

World 1 in one line: git’s storage format demands fast random access; networks can’t provide it; so the industry answer was pet machines with local disks, held together by consensus protocols, a routing database, and on-call rotations.

Chapter 02 — the insight

Make the log the truth.

Cursor described the architecture in their post Git at any scale — they call the system Continuity. Its move changes the economics of the whole problem.

Put a write-ahead log in object storage and declare it the source of truth. Then demote everything else: every on-disk repository is a cache.

A push is no longer a mutation of a blessed replica. It is stored as an immutable object in the bucket and becomes visible only when a tiny manifest is rewritten with a compare-and-swap. That CAS is the consensus — no election, no quorum, no primary. Any instance may accept a push; if two attempt concurrent writes, they cannot both win.

Watch a push end to end. The server indexes the incoming pack, checks it, uploads the objects under their content address — immutable, write-once — and only then flips the manifest. If the manifest moved underneath it, the bucket answers 412 Precondition Failed and the loser re-reads, re-validates, and retries. The client sees ok only after the bucket does.

The manifest CAS is the single linearization point of the entire system. Put two instances in front of the same bucket and let them race: object storage guarantees exactly one winner. There is nothing to elect because there is nothing to lead.

Reads are consistent without coordination. Every read first asks the store whether anything changed — a conditional GET on the manifest, usually answered 304 Not Modified, served from cache. When it comes back 200, the replica applies the new log entries and serves the new state. There is no “eventually”: every request revalidates against the truth first.

A replica that has never seen a repository simply reads the log — and has it.

< 10 ms
a 304 revalidation — metadata only, paid once per read (Continuity’s measurement)
~120/s
pushes on S3 Standard — over 300/s on S3 Express One Zone; the CAS is a throughput cap, not a bottleneck pyramid
100×
replicas with linear read scaling — they are caches, so adding one is free

Maintenance follows the same rule. Compaction is done once, by whoever holds a lease, and the result is published into the log — so replicas download compacted packs instead of repacking locally. And because the WAL is the truth, you get complete provenance for free: every push and every repack, replayable to any point.

World 2 in one line: consensus shrinks to one atomic compare-and-swap on a tiny manifest; every machine becomes a disposable, self-populating cache of a log that lives in the bucket.

Chapter 03 — the realization

walgit: one binary, one bucket.

walgit is a Rust implementation of that architecture — with the changes needed to run on machines smaller than the repository.

The entire deployment story fits on one screen. One config file, one binary, walgit serve — and the first push creates the repository.

deploy — from zero to git server
# walgit.toml — the whole configuration (from the README)
[server]
listen = "0.0.0.0:8080"
public_url = "https://git.example.com"
auto_create_on_push = true          # push creates the repo

[server.auth]
mode = "token"                      # none | token | oidc
tokens = [{ principal = "me", token_env = "WALGIT_TOKEN_ME", write = true }]

[store]
backend = "s3"                        # S3-compatible + GCS
bucket = "my-walgit"

$ WALGIT_TOKEN_ME=$(openssl rand -hex 24) walgit serve --config walgit.toml
▸ listening on 0.0.0.0:8080 — the bucket is the repository

$ git push https://git.example.com/acme/app.git main
▸ ok — main accepted: the manifest CAS landed in the bucket

Pointed at the bucket, that one process gives you smart HTTP (v0/v2) fetch and push, bundle-uri clones served as static files, Git LFS, a browsing web UI, a JSON API with an SDK, per-repository push policy (policy.json), and webhooks driven by an events bridge that tails the WAL and POSTs ref events exactly-once. Repositories can be sha1 or sha256. A background maintainer self-heals: checkpoints, bundle builds, geometric compaction, audits — and its output is a pure function of (config, WAL).

Everything walgit ever writes lands in one of a handful of paths:

The classic catch with object storage: a repository can be larger than any machine you want to pay for. walgit’s monorepo additions erase the catch. A remote reader serves refs and web pages for a repository whose packs never fit on the instance, reading straight out of the bucket with HTTP Range requests. A history pack keeps commits and trees local while the blobs — the bulk of the bytes — stay in the bucket. And bundle-uri turns fresh clones and catch-ups into static files: weekly full bundles plus chained dailies and hourlies, cut as a pure function of the WAL, handed out by the bucket or a CDN — bytes that never touch a walgit process.

57 GiB
reference monorepo — 73 M objects, 1.4 M commits, 466 k refs — served from 20 GiB machines
2.77 MB
through the server for a fresh 32.7 GB clone — the rest is static bundle bytes
2075 s → 8.4 s
CI blobless clone, before and after bundle-uri

Which leaves the operational posture: every machine running walgit is a disposable cache. Add machines pointed at the same bucket and they serve the same repositories consistently with nothing to coordinate. Kill them all and you lose warmth — nothing else.

the manifest CAS is the only commit point immutable, content-addressed objects every read revalidates — there is no “eventually” disk is a cache, memory is a cache, the bucket is the repository

Chapter 04 — the payoff

Same protocol. Radically less system.

To a git client, all three worlds look identical. Underneath, the operational difference is not a matter of degree.

Spokes-style hosting Cursor · Continuity walgit
source of truth replica disks + routing DB WAL in object storage WAL in object storage
consensus three-phase commit, leader election, quorum manifest compare-and-swap manifest compare-and-swap
control plane database mapping repo → machines none — any instance serves any repo none — any instance serves any repo
machines pets: named, stateful, irreplaceable cattle sized to the repos disposable — smaller than the repo is fine
clone traffic served by git processes on replicas served from caches static bundle files via bucket / CDN
maintenance per-replica repack, coordinated failover lease holder compacts once, published into the log self-healing maintainer — pure function of (config, WAL)
provenance what the DB happens to record every push and repack, replayable every push and repack, replayable
kill every machine restore from replicas, replay routing DB, re-elect caches rebuild from the log you lose warmth, nothing else

the whole argument

One binary. One bucket.
That’s the git server.

walgit keeps a write-ahead log in object storage as the only source of truth. A compare-and-swap on a tiny manifest is the entire consensus protocol. Every machine is a disposable cache; every read revalidates; every clone can come from static bundle files. Add machines freely. Kill them freely. The repository was never on them.

no database no leader no local state that matters repos bigger than any machine
The superiority isn’t asserted, it’s counted: one commit point instead of a quorum protocol, zero coordination services instead of a routing database and leader election, static files instead of compute for clones, and a fleet you can delete without a meeting.
View more demos Get $10 off Kimi K3