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.

355 linesCodeBlameRaw
1// Everything g1t deploys to Cloudflare, in one place. Read by
2// scripts/deploy.mjs (plan, deploy), deploy/self-host/configs.mjs (what a
3// self-hosted installation runs) and the tests in scripts/deploy/.
4// docs.g1t.sh/guides/deploy-to-cloudflare/ explains each field and how to
5// add a unit.
6//
7// What is written here is what the Wrangler configs cannot say. The rest is
8// read from each unit's wrangler.jsonc, never copied: its D1 databases and
9// migrations, the services it binds to, its KV, R2, queues and routes. The
10// shared crates and packages a unit is built from are read from Cargo's and
11// npm's workspace metadata. `worker` and `d1` are written here too, so the
12// file reads as an inventory, and a test checks they match the configs.
13{
14 // Deployed in this order. A stage starts only when the one before it
15 // succeeded. A unit binds only to units in its own stage or an earlier
16 // one (a test checks it), so new code never calls a service that has
17 // not shipped yet. Within a stage, units go out in parallel.
18 //
19 // migrations: every pending D1 migration, before any code.
20 // core: the services, reached through service bindings.
21 // edge: public endpoints other than the site: API, MCP, g1t.page, status.
22 // front: the site, sudo and the docs.
23 "stages": ["migrations", "core", "edge", "front"],
24
25 // Names for the resources the configs refer to by id, for setup
26 // commands and the docs. A test checks every KV id in a config is here.
27 "resources": {
28 "kv": {
29 "16a4232cb746418db53782aa068be693": "g1t-actions-blobs",
30 "e627b571f07047e187c03e1fc2b3bbdd": "g1t-avatars",
31 "14bc5c233d4c46a5bbf23b5367cce5fd": "g1t-domains",
32 "be765052d0124c2a935b3db4dff99f1f": "g1t-repos-git-cache",
33 },
34 },
35
36 // Each deployable unit, by short name (`--only events,web`).
37 //
38 // kind: rust-worker (worker-build), ts-worker (Wrangler bundles it),
39 // react-router (vite build first), astro (astro build first).
40 // secrets: names only; set with `npx wrangler secret put NAME` in its folder.
41 // setup: one-time steps no config can say, for a first deploy.
42 // self_host: what deploy/self-host does with it: "run" (in the one
43 // workerd), "off" (bound to the off Worker), "separate" (a
44 // process of its own), or "none".
45 // inputs: files outside its folder it is built from that no workspace
46 // metadata names (a test finds such imports).
47 // image: a Containers image (the guide's "The runner's images"):
48 // dockerfile the image a deploy ships: the base plus the binary
49 // crate the crate that binary is built from (and what it uses)
50 // base { context: the base's folder, lock: the file that
51 // records the base that was pushed }; the base is
52 // rebuilt only when its folder changes
53 // repository where both are pushed in Cloudflare's registry
54 "units": {
55 "events": {
56 "path": "services/events",
57 "kind": "rust-worker",
58 "worker": "g1t-events",
59 "d1": { "database": "g1t-events", "migrations": "migrations" },
60 "stage": "core",
61 "secrets": [],
62 "setup": [
63 "The dead-letter queue every queue consumer sends what it gave up on to, before any unit that names it deploys: npx wrangler queues create g1t-events-dlq",
64 ],
65 "self_host": "run",
66 },
67 "identity": {
68 "path": "services/identity",
69 "kind": "rust-worker",
70 "worker": "g1t-identity",
71 "d1": { "database": "g1t", "migrations": "migrations" },
72 "stage": "core",
73 "secrets": ["GITHUB_APP_CLIENT_SECRET", "IDENTITY_KEY"],
74 "setup": ["Email Sending on g1t.sh (the EMAIL binding)"],
75 "self_host": "run",
76 },
77 "repos": {
78 "path": "services/repos",
79 "kind": "rust-worker",
80 "worker": "g1t-repos",
81 "d1": { "database": "g1t-repos", "migrations": "migrations" },
82 "stage": "core",
83 // REPOS_KEY seals what its isolates share (src/shared.rs) and the
84 // API's tarball and zipball links (src/archive_links.rs), which
85 // fail without it.
86 "secrets": ["REPOS_KEY"],
87 "setup": [
88 "The Artifacts namespace `g1t` (the ARTIFACTS binding)",
89 "The R2 bucket `g1t-git-packs` (GIT_PACKS) with its lifecycle rule: `npx wrangler r2 bucket create g1t-git-packs`, then `npx wrangler r2 bucket lifecycle add g1t-git-packs expire-packs packs/ --expire-days 7 --abort-multipart-days 1`",
90 "The R2 bucket for nightly backups: npx wrangler r2 bucket create g1t-backups",
91 ],
92 "self_host": "run",
93 },
94 "work": {
95 "path": "services/work",
96 "kind": "rust-worker",
97 "worker": "g1t-work",
98 "d1": { "database": "g1t-work", "migrations": "migrations" },
99 "stage": "core",
100 "secrets": [],
101 "self_host": "run",
102 },
103 "search": {
104 "path": "services/search",
105 "kind": "rust-worker",
106 "worker": "g1t-search",
107 "d1": { "database": "g1t-search", "migrations": "migrations" },
108 "stage": "core",
109 "secrets": [],
110 "self_host": "run",
111 },
112 "projects": {
113 "path": "services/projects",
114 "kind": "ts-worker",
115 "worker": "g1t-projects",
116 "d1": { "database": "g1t-projects", "migrations": "migrations" },
117 "stage": "core",
118 "secrets": [],
119 "self_host": "run",
120 },
121 "chat": {
122 "path": "services/chat",
123 "kind": "ts-worker",
124 "worker": "g1t-chat",
125 "d1": { "database": "g1t-chat", "migrations": "migrations" },
126 "stage": "core",
127 "secrets": [],
128 "setup": ["The D1 database, before the first deploy: npx wrangler d1 create g1t-chat, then put its id in services/chat/wrangler.jsonc"],
129 "self_host": "run",
130 },
131 // Artifacts: docs (a doc is one kind of artifact), their spaces,
132 // sharing and live rooms. It was g1t-docs-service (services/docs)
133 // until 2026-10; its resources kept their g1t-docs names. g1t-docs
134 // is the documentation site (apps/docs).
135 "artifacts": {
136 "path": "services/artifacts",
137 "kind": "ts-worker",
138 "worker": "g1t-artifacts",
139 "d1": { "database": "g1t-docs", "migrations": "migrations" },
140 "stage": "core",
141 "secrets": [],
142 "setup": [
143 "An installation that ran g1t-docs-service: move to g1t-artifacts as \"Renaming a Worker\" in docs.g1t.sh/guides/deploy-to-cloudflare/ says (its queue consumer first, then this Worker, which takes the old Worker's live rooms)",
144 "The D1 database, before the first deploy: npx wrangler d1 create g1t-docs, then put its id in services/artifacts/wrangler.jsonc",
145 "The R2 bucket for files in pages: npx wrangler r2 bucket create g1t-docs-files",
146 "The queue the events service sends it merges and pushes on (pages whose cited code changed, projects' docs): npx wrangler queues create g1t-events-docs. The service also sends its own backfill jobs to it (JOBS)",
147 "The Vectorize index Docs are searched by meaning from: npx wrangler vectorize create g1t-docs --dimensions=768 --metric=cosine, with string metadata indexes on workspace_id and space_id (npx wrangler vectorize create-metadata-index g1t-docs --property-name=<name> --type=string)",
148 "The Vectorize index for artifacts (folios), before the first deploy with FOLIO_VECTORS: npx wrangler vectorize create g1t-folios --dimensions=768 --metric=cosine, with string metadata indexes on workspace_id, scope and kind (npx wrangler vectorize create-metadata-index g1t-folios --property-name=<name> --type=string). Without it, artifacts are searched by words",
149 ],
150 "self_host": "run",
151 },
152 "telemetry": {
153 "path": "services/telemetry",
154 "kind": "ts-worker",
155 "worker": "g1t-telemetry",
156 // No D1: each project's telemetry is kept in its own Durable
157 // Object's SQLite storage. Public at otel.g1t.sh (OTLP), and
158 // every app on g1t.page names it as its Tail Worker.
159 "stage": "core",
160 // CLOUDFLARE_EXPORT_KEY is optional (Cloudflare's own OpenTelemetry
161 // export of the apps), so it is not listed.
162 "secrets": [],
163 "setup": [
164 "Deployed once before the deployments service and the dispatcher, which name it (`node scripts/deploy.mjs deploy --only telemetry`): a binding or Tail Worker that names a Worker not yet there is refused",
165 ],
166 "self_host": "run",
167 },
168 "notify": {
169 "path": "services/notify",
170 "kind": "ts-worker",
171 "worker": "g1t-notify",
172 // No D1: each person's feed keeps its own state in its Durable
173 // Object's SQLite storage.
174 "stage": "core",
175 // The private half of the VAPID key pair browser pushes are signed
176 // with; without it, notifications are live in open tabs only.
177 // EXPO_ACCESS_TOKEN, for pushes to the phone app, is optional
178 // (only for an Expo project with enhanced push security), so it
179 // is not listed.
180 "secrets": ["VAPID_PRIVATE_KEY"],
181 "setup": [
182 "The VAPID key pair for browser push: `node scripts/ops/vapid-keys.mjs` prints both halves and stores nothing. Put the public half in VAPID_PUBLIC_KEY in services/notify/wrangler.jsonc, and the private half with `npx wrangler secret put VAPID_PRIVATE_KEY` in services/notify",
183 ],
184 "self_host": "run",
185 },
186 "billing": {
187 "path": "services/billing",
188 "kind": "rust-worker",
189 "worker": "g1t-billing",
190 "d1": { "database": "g1t-billing", "migrations": "migrations" },
191 "stage": "core",
192 "secrets": ["STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET", "CLOUDFLARE_USAGE_TOKEN", "BILLING_KEY"],
193 "self_host": "run",
194 },
195 "integrations": {
196 "path": "services/integrations",
197 "kind": "rust-worker",
198 "worker": "g1t-integrations",
199 "d1": { "database": "g1t-integrations", "migrations": "migrations" },
200 "stage": "core",
201 "secrets": ["INTEGRATIONS_KEY", "GITHUB_APP_PRIVATE_KEY", "GITHUB_APP_WEBHOOK_SECRET"],
202 "self_host": "run",
203 },
204 "webhooks": {
205 "path": "services/webhooks",
206 "kind": "rust-worker",
207 "worker": "g1t-webhooks",
208 "d1": { "database": "g1t-webhooks", "migrations": "migrations" },
209 "stage": "core",
210 "secrets": ["WEBHOOKS_KEY"],
211 "self_host": "run",
212 },
213 "actions": {
214 "path": "services/actions",
215 "kind": "rust-worker",
216 "worker": "g1t-actions",
217 "d1": { "database": "g1t-actions", "migrations": "migrations" },
218 "stage": "core",
219 "secrets": ["ACTIONS_KEY"],
220 "self_host": "run",
221 },
222 "packages": {
223 "path": "services/packages",
224 "kind": "rust-worker",
225 "worker": "g1t-packages",
226 "d1": { "database": "g1t-packages", "migrations": "migrations" },
227 "stage": "core",
228 "secrets": ["PACKAGES_TOKEN_SECRET", "R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY"],
229 "setup": [
230 "The D1 database: npx wrangler d1 create g1t-packages, its id in services/packages/wrangler.jsonc",
231 "The R2 bucket for packages' files: npx wrangler r2 bucket create g1t-packages",
232 "The events queue: npx wrangler queues create g1t-events-packages",
233 "For signed downloads: an R2 API token with read access to g1t-packages, as R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY",
234 ],
235 "self_host": "run",
236 },
237 "security": {
238 "path": "services/security",
239 "kind": "rust-worker",
240 "worker": "g1t-security",
241 "d1": { "database": "g1t-security", "migrations": "migrations" },
242 "stage": "core",
243 "secrets": [],
244 "self_host": "run",
245 },
246 "deployments": {
247 "path": "services/deployments",
248 "kind": "ts-worker",
249 "worker": "g1t-deployments",
250 "d1": { "database": "g1t-deployments", "migrations": "migrations" },
251 "stage": "core",
252 "secrets": ["CLOUDFLARE_API_TOKEN"],
253 "setup": [
254 "Workers for Platforms, and the dispatch namespace: scripts/setup-deployments.sh",
255 "Custom domains (Cloudflare for SaaS on g1t.page): scripts/setup-custom-domains.sh",
256 ],
257 "self_host": "run",
258 },
259 "runner": {
260 "path": "services/runner",
261 "kind": "ts-worker",
262 "worker": "g1t-runner",
263 "stage": "core",
264 "secrets": [],
265 "setup": ["Containers on the account; Docker on the machine that builds a new image", "The base image, once: node scripts/deploy.mjs build-base"],
266 "image": {
267 "dockerfile": "services/runner/Dockerfile",
268 // The binary the image adds to its base (scripts/build-runner.mjs).
269 "crate": "g1t-runner",
270 "base": { "context": "services/runner/base", "lock": "services/runner/base.json" },
271 "repository": "g1t-runner",
272 },
273 "self_host": "off",
274 },
275 "og": {
276 "path": "services/og",
277 "kind": "ts-worker",
278 "worker": "g1t-og",
279 "stage": "core",
280 "secrets": [],
281 "setup": ["Browser Rendering on the account (the BROWSER binding)"],
282 // The roadmap cards read the site's roadmap.
283 "inputs": ["apps/web/app/lib/roadmap.ts"],
284 "self_host": "none",
285 },
286 "api": {
287 "path": "apps/api",
288 "kind": "rust-worker",
289 "worker": "g1t-api",
290 "stage": "edge",
291 // ACTIONS_OIDC_KEY signs workflow jobs' OIDC tokens; without it the
292 // issuer answers 404 and jobs are not offered tokens.
293 // ACTIONS_OIDC_KEY_PREVIOUS only while rotating (the guide's "OIDC
294 // tokens for workflow jobs").
295 "secrets": ["ACTIONS_OIDC_KEY"],
296 "setup": [
297 "The OIDC signing key for workflow jobs: an RSA key made with `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048`, stored with `npx wrangler secret put ACTIONS_OIDC_KEY` (docs.g1t.sh/guides/deploy-to-cloudflare/#oidc-tokens-for-workflow-jobs)",
298 "The actions cache bucket's lifecycle rule limited to `c/`, so artifacts under `a/` are kept their retention-days (docs.g1t.sh/guides/deploy-to-cloudflare/#a-first-deploy-to-a-new-account)",
299 ],
300 "self_host": "separate",
301 },
302 "pages": {
303 "path": "services/pages",
304 "kind": "ts-worker",
305 "worker": "g1t-pages",
306 "stage": "edge",
307 "secrets": [],
308 "setup": ["A proxied wildcard DNS record on g1t.page (`*`, AAAA 100::): scripts/setup-deployments.sh"],
309 "self_host": "none",
310 },
311 "status": {
312 "path": "apps/status",
313 "kind": "ts-worker",
314 "worker": "g1t-status",
315 "d1": { "database": "g1t-status", "migrations": "migrations" },
316 "stage": "edge",
317 "secrets": ["STATUS_SECRET"],
318 "setup": ["Email Sending on g1t.sh (the EMAIL binding)"],
319 "self_host": "separate",
320 },
321 "web": {
322 "path": "apps/web",
323 "kind": "react-router",
324 "worker": "g1t",
325 "stage": "front",
326 // USERCONTENT_KEY signs the short-lived addresses of private
327 // repositories' files on g1tusercontent.com; without it they are
328 // served from g1t.sh instead.
329 "secrets": ["USERCONTENT_KEY"],
330 "setup": [
331 "The R2 bucket g1t-downloads, for the releases of the self-hosted runner, the CLI and the desktop app: npx wrangler r2 bucket create g1t-downloads",
332 "The zone g1tusercontent.com on the account; the Worker's custom domain on it is made by the deploy",
333 "The key for private files' addresses: `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\" | npx wrangler secret put USERCONTENT_KEY` in apps/web",
334 ],
335 "self_host": "run",
336 },
337 "sudo": {
338 "path": "apps/sudo",
339 "kind": "react-router",
340 "worker": "g1t-sudo",
341 "stage": "front",
342 "secrets": [],
343 "setup": ["A Cloudflare Access application on sudo.g1t.sh; its AUD tag is ACCESS_AUD"],
344 "self_host": "none",
345 },
346 "docs": {
347 "path": "apps/docs",
348 "kind": "astro",
349 "worker": "g1t-docs",
350 "stage": "front",
351 "secrets": [],
352 "self_host": "none",
353 },
354 },
355}