Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

162 linesCodeBlameRaw
1---
2title: An open letter to Cloudflare
3description: From the team at Flagon, Inc. building g1t, a git platform that runs entirely on Cloudflare. What works, where we hit walls, and what we'd ask for.
4---
5
6Dear Cloudflare,
7
8We're the small team at Flagon, Inc. building g1t, a git platform where
9people and coding agents work in the same work items, pull requests and merge
10queue. Every part of it runs on you: more than twenty-five Workers, a D1 database for
11each service that keeps data, Artifacts for every repository and every pull
12request's fork, Containers for agents and CI, R2, KV, Queues, and Cloudflare
13for SaaS for our customers' domains. We have no servers.
14
15This is a thank-you, and a list of what would help us most next.
16
17## Why we built on you
18
19A git platform is usually a fleet: storage nodes, a job system, a database
20cluster, a CDN in front, and people on call for all of it. We wanted to run
21one for thousands of teams with a handful of people. You offered a global
22platform where the unit of work is a request, the unit of storage is a
23Durable Object, and nothing costs money while nobody is using it.
24
25Artifacts is the reason g1t exists in this shape. One Durable Object per
26repository is the right isolation unit: a busy repository doesn't slow its
27neighbours, and there is nothing to shard by hand. It speaks real git, so
28stock clients cloned, fetched and pushed through our proxy from the first
29day. `fork()` is a single call, and per-pull-request isolation for agents
30fell out of it almost for free. Scoped tokens that expire on their own let
31us hand a sandbox a credential that dies with it. The read binding powers
32every page we render, blame, mergeability and search, without a git client
33anywhere.
34
35The rest of the platform held up too. Rust compiled to WebAssembly runs our
36core (repositories, work items and pull requests, the API, security and
37billing), and TypeScript the rest. D1's read replication is free and good. Containers gave us
38sandboxes in three sizes. With a cached credential and ref listing, a
39`git fetch` with nothing new answers in under half a second, and most of
40our pages answer in under 250 ms. We went from an empty repository to a
41launch on this stack, and most of it worked the first time.
42
43## Where we hit walls
44
45Running a real platform for many teams found the edges. None of these
46stopped us. Each one costs us code, latency or certainty, and each one will
47cost the next team building on you the same.
48
49**What a billable operation is.** Artifacts pricing names "repo operations,
50such as create, push, pull, and clone", and the metrics list a different set
51of event names. Neither says whether binding reads, token mints or ref
52listings count. We price from cost, so this decides what our customers pay.
53Depending on the answer, our model for a few thousand workspaces lands
54anywhere between about $1.8k and $31k a month. Today we count every clone,
55fetch and push ourselves, and hope it matches.
56
57**What a fork stores.** Forks are the natural primitive for a pull request,
58and agents open pull requests by the thousand. We can't find whether a fork
59shares objects with its source or copies them. If it copies, an agent-heavy
60account reaches the 1 TB account limit in days (it can be raised on
61request, but only by asking), and at that point every push
62in the account fails, for every customer at once. So we remove a pull
63request's fork a day after it closes, keeping its head in the repository it
64came from, and measure the rest ourselves.
65
66**A write path and a pre-receive hook.** The binding reads, but it can't
67list refs, move them, or write objects. So to land a pull request we speak
68git's wire protocol to our own storage from inside a Worker, buffering packs
69in an isolate with 128 MB to share. With no hook before refs move, branch
70protection and secret scanning only hold for pushes through our proxy, which
71parses each pack in WebAssembly before forwarding it, up to the size an
72isolate can hold. We wrote a second
73implementation of git's pack format to get there.
74
75**Ref-change events and the cost of a credential.** Push events need one
76subscription per repository, which doesn't scale to tens of thousands of
77repositories and forks. We record ref changes ourselves, and a test scans
78our own source to make sure every code path that moves a ref says so. Every
79git credential takes three binding calls and about 0.8 s to mint, so we
80cache sealed tokens across isolates in KV.
81
82**Placement that follows data.** Smart Placement once ran our site in
83Amsterdam for a visitor in Denver, while every D1 primary we have is in
84western North America. Every query crossed the Atlantic, and our Explore
85page took 0.85 s instead of 0.17 s. We turned placement off everywhere and
86measure each Worker by hand.
87
88**D1 sessions across service bindings.** Read replicas need a bookmark to
89give read-your-writes. Our site reads from about twenty services, most with
90their own database, so we built a header protocol to carry bookmarks through service
91bindings into a cookie and back.
92
93**Container networks and disks.** Docker runs inside a Container, and our
94CI jobs use it, but a container can't route a network of its own or change
95its packet filter, so every container in a job shares the job's network and
96two of them can't listen on the same port. Container disk is ephemeral, so
97the self-hostable git store we keep as a fallback has to live off
98Cloudflare.
99
100**Sharding is ours to do.** One Artifacts namespace takes 2,000 requests
101every 10 seconds, and a binding addresses one namespace. To grow past that,
102and to keep EU workspaces' repositories in the EU, we route each repository
103to a namespace and move repositories between them ourselves.
104
105**Inbound TCP for SSH.** Git users expect `git@host:owner/repo`. Workers
106take no inbound TCP, so g1t is HTTPS only. We've applied for the beta and
107are waiting.
108
109## What we built in the meantime
110
111A per-workspace operation counter that is our best guess at your invoice. A
112smart HTTP
113client inside a Worker for landing, catch-up and imports. Our own
114push policy in front of Artifacts. A versioned ref cache with a test that
115guards it. Two layers of credential caching. A bookmark protocol for D1.
116A probe that deploys throwaway Workers to measure placement, and a
117`Server-Timing` header on every response so we see the next regression. A
118sweep that removes each pull request's fork a day after it closes.
119Repository sharding across namespaces, with a read-only fallback rebuilt
120from nightly backups.
121
122All of it works. Most of it is code we'd happily delete.
123
124## What we're asking for
125
1261. A published definition of a billable Artifacts operation, with
127 per-repository metrics that use the same names as the invoice.
1282. Documented fork storage, an expiry on `fork()`, a repository's stored
129 bytes in `info()`, and a warning before the account storage limit, with
130 failures per repository rather than account-wide.
1313. Ref listing, atomic compare-and-swap ref updates and streaming pack
132 writes in the binding.
1334. A pre-receive hook that a Worker answers.
1345. Account-level ref-change events to a Queue, a read-after-write guarantee
135 for refs, and git forwarding authenticated by the binding, with no token
136 to mint.
1376. Placement that accounts for D1 primaries and service bindings, and D1 as
138 a placement target.
1397. D1 sessions that travel across service bindings.
1408. Persistent volumes for Containers, and container networks a job can
141 route.
1429. One binding that addresses many namespaces, and a namespace rate limit
143 that can be raised on request.
14410. Inbound TCP, so git can run over SSH.
14511. A support path during the beta, snapshot restore and export for
146 repositories, and a date for general availability with an SLA.
147
148## Let's work on it together
149
150We chose you on purpose, and we'd choose you again. A small team running a
151global git platform with no servers is the promise of what you've built,
152and g1t shows that it mostly holds. We'd like to help close the
153rest of the gap: traces, test repositories, early builds to try, or a call
154with the teams involved. Whatever is useful.
155
156You can reach us through [g1t.sh](https://g1t.sh/support). Our code lives
157at [g1t.sh/flagon-io/g1t](https://g1t.sh/flagon-io/g1t), on the platform
158it describes.
159
160With thanks,
161
162The team at Flagon, Inc.