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.

531 linesCodeBlameRaw
1---
2title: Deploy g1t to Cloudflare
3description: How g1t.sh is deployed to Cloudflare, and how to deploy the same code to an account of your own.
4---
5
6g1t.sh runs on Cloudflare, and the tool that deploys it is in the
7repository. You can deploy the same code to a Cloudflare account of your
8own. To run g1t on one machine without Cloudflare, see
9[Run g1t yourself](/guides/self-hosting/) instead.
10
11You need a Cloudflare account on the Workers Paid plan (Cloudflare
12Artifacts, where repositories are kept, requires it), Node 24,
13Rust with the `wasm32-unknown-unknown` target, and Docker to build the
14runner's image.
15
16Every `wrangler.jsonc` names g1t.sh's account, domains and resource ids.
17Before a first deploy to another account, change those to your own (see
18[A first deploy to a new account](#a-first-deploy-to-a-new-account)).
19
20## The pieces
21
22| Piece | Where |
23| ----------------------------------- | -------------------------------------------------------------------------------------------- |
24| The manifest: every deployable part | `deploy/stack.jsonc` |
25| The tool | `scripts/deploy.mjs`, with its library and tests in `scripts/deploy/` |
26| Rust Worker builds | `scripts/build-rust-worker.mjs` |
27| The runner's images | `services/runner/base/Dockerfile`, `services/runner/Dockerfile`, `services/runner/base.json` |
28| The workflows | `.g1t/workflows/deploy.yml`, `.g1t/workflows/runner-base.yml` |
29
30## The manifest
31
32`deploy/stack.jsonc` lists every unit: each folder with a `wrangler.jsonc`.
33
34| Field | What it is |
35| ----------- | ---------------------------------------------------------------------------------------------------------------------- |
36| `path` | The unit's folder |
37| `kind` | `rust-worker`, `ts-worker`, `react-router` or `astro` |
38| `worker` | The Worker's name. It must match its `wrangler.jsonc`. |
39| `d1` | `{ database, migrations }`, when it has a database |
40| `stage` | `core`, `edge` or `front` (below) |
41| `secrets` | The Wrangler secrets it needs, by name |
42| `setup` | One-time steps a first deploy needs that no config can say |
43| `inputs` | Files outside its folder it is built from that no package names |
44| `image` | A Containers image, for the runner |
45| `self_host` | `run`, `off`, `separate` or `none`: what a [self-hosted installation](/guides/self-hosting-architecture/) does with it |
46
47What a unit is built from is read, never listed by hand: Rust path
48dependencies from `cargo metadata`, workspace packages from each
49`package.json`. A lockfile change counts for a unit only when a package
50it uses changed. `node scripts/deploy.mjs manifest` prints the result.
51
52Units deploy in stages. A stage starts only when the one before it
53succeeded, and units inside a stage deploy in parallel:
54
55| Stage | What |
56| ------------ | -------------------------------------------------------------- |
57| `migrations` | Every pending D1 migration, before any code |
58| `core` | The services, reached through bindings |
59| `edge` | Public endpoints other than the site: `api`, `pages`, `status` |
60| `front` | The site, sudo and the docs |
61
62A unit binds only to units in its own stage or an earlier one, so new code
63never calls a service that has not shipped. `npm run test:deploy` checks
64this, and that every `wrangler.jsonc` has a unit whose names match it.
65Cloudflare refuses a Worker bound to a Worker that does not exist, so
66inside a stage a unit whose Worker has never been deployed (a new or
67renamed one) goes first, and the units of its stage that bind to it go
68after it succeeds.
69
70## The tool
71
72Web releases retain fingerprinted JavaScript, CSS and fonts in the existing
73`DOWNLOADS` R2 bucket under `web-assets/assets/`. Before switching the Worker,
74the deploy tool ensures the currently live site's asset graph and the new
75build's assets are archived. Release manifests under `web-assets/manifests/`
76let later deploys skip unchanged files without scanning deployment history or
77fetching the entire live build again. Provider rate limits retry with a bound;
78an archive failure stops the web cutover. A confirmed first deployment skips
79the live-site copy. The deploy credential needs object read and write access
80to this bucket.
81
82Every web build also runs the compiled Worker locally in workerd. The gate
83checks old CSS through R2, missing assets, and old-client navigation with an
84existing cached response before any backend loaders run. Upload receipts
85must match the local asset checksums before cutover is allowed.
86
87Current files still use Workers Static Assets. Requests for older filenames
88fall back to R2, so an open tab can finish loading after a release. Archived
89files are immutable and retained without automatic deletion; do not apply an
90expiration lifecycle rule to this prefix. Assets already removed before the
91first protected deployment cannot be recovered by this archive.
92
93The public HTML/data cache is separated by build. On navigation, a tab from
94an older build gets a document navigation to its destination before route
95loaders run. Background refreshes do not force a reload, and writes are not
96replayed. Service APIs must remain compatible with existing clients during
97rollout. Use the deploy tool or the web package's `npm run deploy`; invoking
98`wrangler deploy` directly bypasses the asset archive step.
99
100After cutover, the tool checks the live build identifier and the bytes of
101both old and new stylesheets, client entries and route manifests. A failed
102check retries during propagation, bounded by seven attempts and a two-minute
103request deadline. It still requires the exact expected build and asset bytes.
104An exhausted check marks verification failed and reports the already-deployed
105Worker version; it does not automatically roll back service
106code or database migrations. The production workflow serializes deploys
107without cancelling an active release. Manual deploys must not race that
108workflow. Roll back only to web versions that include the archive fallback;
109older versions would remove that protection. Database changes must be
110additive during rollout, with removals deferred until old callers are gone.
111
112```sh
113node scripts/deploy.mjs plan # what would deploy, and why (read-only)
114node scripts/deploy.mjs deploy # migrations, then every changed unit
115node scripts/deploy.mjs deploy --only web,api # just these, if they changed
116node scripts/deploy.mjs deploy --only web --force # just this, changed or not
117node scripts/deploy.mjs deploy --all # everything
118node scripts/deploy.mjs build --only events # build as a deploy would; upload nothing
119node scripts/deploy.mjs migrate # pending migrations only
120node scripts/deploy.mjs manifest --check # the manifest's problems
121node scripts/deploy.mjs doctor # secrets each unit lacks
122node scripts/deploy.mjs build-base # build and push the runner's base image (Docker)
123node scripts/deploy.mjs image # make the runner's image in the registry (no Docker)
124```
125
126| Flag | What it does |
127| ----------------------------------- | -------------------------------------------------------------------------------------------------- |
128| `--only a,b`, `--skip a,b` | Units by short name, folder or Worker name |
129| `--all` | Every unit, changed or not |
130| `--force` | The selected units even if unchanged |
131| `--concurrency N` | Units at once inside a stage, and migrations at once (default 4) |
132| `--stage core` | One stage only |
133| `--no-migrations` | Skip the migrations step |
134| `--allow-dirty` | Deploy with uncommitted changes. The version records no commit, so the next plan deploys it again. |
135| `--rebuild-image`, `--rebuild-base` | Build the runner's image, or its base, even if one exists |
136| `--rollback` | Allow deploying a commit older than the one live |
137| `--since REV` | Treat Workers with no recorded commit as running `REV` |
138
139**Only what changed deploys.** Each deploy records its commit on the
140Worker's version (`wrangler deploy --message "g1t-deploy <sha> ..." --tag
141g1t-<sha>`). `plan` reads the live version of each Worker, diffs from its
142commit to `HEAD`, and deploys the units the change touches. A change only
143to a crate's tests deploys nothing. A version deployed any other way has
144no commit, so the unit is deployed again.
145
146With `CLOUDFLARE_API_TOKEN` (in CI) or `CLOUDFLARE_DEPLOY_TOKEN` (on your
147machine) and `CLOUDFLARE_ACCOUNT_ID`, `plan` reads Cloudflare's API itself
148and takes a few seconds. With only `wrangler login`, it asks Wrangler and
149takes minutes. Each unit's output is kept in `$TMPDIR/g1t-deploy/<unit>.log`.
150
151### Migrations run while the old code is live
152
153Migrations apply before any code, so for a minute or more the old code
154reads the new schema. Keep it working:
155
156- **Add, never change meaning.** New tables and columns with defaults are
157 safe.
158- **Change meaning in two deploys.** First ship code that reads both forms;
159 then the migration that rewrites the rows.
160- **Never drop or rename** a column or table in the same deploy that stops
161 reading it.
162
163Migrations never run backwards: undoing one is a new migration.
164
165## The workflow
166
167`.g1t/workflows/deploy.yml` runs the tool on every push to `main`, and by
168hand (**Actions → Deploy → Run workflow**) with `units`, `all` and
169`dry_run`.
170
171| Job | Does |
172| ----------------------- | ---------------------------------------------------- |
173| `check` | `manifest --check` and `npm run test:deploy` |
174| `plan` | The plan, shown in the run's summary |
175| `migrate` | Pending migrations; skipped when there are none |
176| `core`, `edge`, `front` | Each stage's units, one job per build group |
177| `smoke` | Loads the landing page, sign-in, sign-up and pricing |
178
179Deploys run one at a time; a second push waits. Rust units build on
180`g1t-4core` machines, at most four to a job, with Cargo's target and
181sccache kept in the Actions cache.
182
183The deploy jobs use `environment: production`, so only they can read the
184Cloudflare token. Jobs on g1t's machines reach Cloudflare's API and
185registry as they reach any host; there is nothing to allow.
186
187Every deploy shows on the repository's Deployments page. A deploy run by
188hand reports one too when `G1T_DEPLOY_TOKEN` holds a g1t token with
189`deployments:write`.
190
191### The Cloudflare API token
192
193Make a custom token at **dash.cloudflare.com → My Profile → API Tokens**,
194restricted to your account and zones:
195
196| Scope | Permission |
197| ------- | ------------------------ |
198| Account | Workers Scripts: Edit |
199| Account | D1: Edit |
200| Account | Queues: Edit |
201| Account | Workers R2 Storage: Read |
202| Account | Account Settings: Read |
203| Account | Containers: Edit |
204| Zone | Workers Routes: Edit |
205| Zone | DNS: Edit |
206| Zone | Zone: Read |
207
208Store it on the repository under **Settings → Secrets and variables** as
209the secret `CLOUDFLARE_API_TOKEN`, for workflows in the `production`
210environment, and your account id as the variable `CLOUDFLARE_ACCOUNT_ID`.
211A missing permission fails with `Authentication error [code: 10000]` and
212the route it was refused on.
213
214## A first deploy to a new account
215
216What the configs refer to must exist first.
217`node scripts/deploy.mjs manifest --json` lists, for each unit, its
218queues, KV namespaces, R2 buckets, Vectorize indexes and dispatch
219namespaces; its `setup` and `secrets` say the rest.
220
2211. Set `account_id`, routes and custom domains in each `wrangler.jsonc` to
222 your own.
2232. D1: `npx wrangler d1 create <database>` for each, and put its id in the
224 unit's `wrangler.jsonc`.
2253. KV: `npx wrangler kv namespace create <name>` for each name under
226 `resources.kv` in the manifest, and put the ids in the configs.
2274. Queues: `npx wrangler queues create <queue>` for each queue in the
228 manifest, including the dead-letter queue `g1t-events-dlq`.
2295. R2: create `g1t-screenshots`, `g1t-actions-cache` and `g1t-git-packs`,
230 with lifecycle rules:
231
232 ```sh
233 npx wrangler r2 bucket lifecycle add g1t-actions-cache expire-cache c/ --expire-days 30 --abort-multipart-days 1
234 npx wrangler r2 bucket lifecycle add g1t-actions-cache expire-artifacts a/ --expire-days 91 --abort-multipart-days 1
235 npx wrangler r2 bucket lifecycle add g1t-git-packs expire-packs packs/ --expire-days 7 --abort-multipart-days 1
236 ```
237
2386. The runner's base image: `node scripts/deploy.mjs build-base`, then
239 commit `services/runner/base.json`.
2407. Everything else each unit's `setup` names: Vectorize, the dispatch
241 namespace, DNS, Access, Email Sending and Artifacts.
2428. Secrets: `npx wrangler secret put <NAME>` in the unit's folder.
243 `node scripts/deploy.mjs doctor` lists what is missing.
2449. For [Deployments](/guides/deployments/), which need the Workers for
245 Platforms add-on and a zone for apps: `scripts/setup-deployments.sh`.
24610. Durable Objects: the artifacts service's v3 migration
247 (`services/artifacts/wrangler.jsonc`) moves g1t.sh's live rooms from
248 the Worker it was renamed from, which a new account does not have.
249 Replace it with
250 `{ "tag": "v3", "new_sqlite_classes": ["PageRoom", "FolioRoom"] }`.
25111. `node scripts/deploy.mjs deploy --all`. Inside a stage, Workers that do
252 not exist yet go before those bound to them; a Worker bound to one in a
253 later stage, or to one that failed, is refused until it exists.
25412. Register on your site to make the first account, or run
255 `node services/identity/scripts/create-user.mjs <username>`.
256
257### Adding a unit
258
2591. Make its folder with a `wrangler.jsonc`. A Rust Worker is a member of
260 the root `Cargo.toml`, with `node ../../scripts/build-rust-worker.mjs`
261 as its build command and `wasm-opt = ["-O1"]`; a TypeScript one is an
262 npm workspace.
2632. Add it to `deploy/stack.jsonc` in the earliest stage after everything
264 it binds to, with its secrets, setup and `self_host`.
2653. Add a row for it to the table in
266 [How a self-hosted g1t runs](/guides/self-hosting-architecture/#each-part).
2674. `npm run test:deploy` and `node scripts/deploy.mjs manifest --check`
268 say what is missing.
269
270### Renaming a Worker
271
272A Worker's name is its identity on Cloudflare, so a renamed Worker is a
273new one. Keep its resources: D1 databases, R2 buckets, Vectorize indexes
274and queues are bound by id or name and keep theirs. Durable Objects
275belong to a Worker, so the new one takes them with a
276[transfer migration](https://developers.cloudflare.com/durable-objects/reference/durable-object-class-migrations-legacy/#transfer-migration)
277(`transferred_classes`, with `from_script` the old name), every object's
278storage with them; until the old Worker is deleted, its own bindings to
279them reach the new one. A queue has one consumer, so the old Worker lets
280go of it first.
281
282The artifacts service was `g1t-docs-service` (`services/docs`) and is
283`g1t-artifacts` (`services/artifacts`). An installation that ran the old
284one moves once, in this order:
285
2861. Let go of the queue. Its messages wait for the new consumer:
287
288 ```sh
289 npx wrangler queues consumer remove g1t-events-docs g1t-docs-service
290 ```
291
2922. Deploy: push to `main`, or `node scripts/deploy.mjs deploy`.
293 `g1t-artifacts` goes first in `core`, takes `PageRoom` and `FolioRoom`
294 and the queue; then `api` (edge) and `web` (front), each bound to it.
295 Until each has gone out it still calls
296 `g1t-docs-service`, which answers from the same database, bucket and
297 (forwarded) rooms.
2983. Check: an artifact opens with its content and edits live, a file
299 uploads, and `npx wrangler queues info g1t-events-docs` names
300 `g1t-artifacts` as the consumer. Cloudflare's
301 `GET /accounts/{account_id}/workers/durable_objects/namespaces` lists
302 `PageRoom` and `FolioRoom` under `g1t-artifacts`.
3034. After a day with no requests to it (`npx wrangler tail g1t-docs-service`
304 shows only its 03:17 UTC cron), delete it by hand:
305 `npx wrangler delete g1t-docs-service`. Never with `--force`: a refusal
306 means something still binds to it.
307
308If step 2 fails before `g1t-artifacts` deploys, nothing moved: put the
309consumer back with `npx wrangler deploy` in `services/docs` at the
310previous commit. Once it has deployed, the rooms are its own: fix forward.
311
312## The runner's images
313
314`services/runner` runs every sandbox (checks, the merge queue,
315workflow jobs and deploy builds) from one Containers image in two parts:
316
317| Image | Built from | Rebuilt |
318| ---------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
319| The base | `services/runner/base/Dockerfile`: Debian, Node, Python, Docker, Go, Rust, Java, .NET and Ruby | When its folder changes, weekly by `runner-base.yml`, or by hand with `build-base` |
320| The runner | `services/runner/Dockerfile`: the base plus the `g1t-runner` binary | When the binary or the base changes |
321
322`services/runner/base.json` records the base that was pushed;
323`npm run test:deploy` fails while it and the base's folder disagree. The
324runner's image is tagged with a hash of what it is built from, so a deploy
325makes it only when the registry does not already have it. It is made in
326the registry, without Docker: the deploy reads the base's manifest,
327uploads one layer holding the binary, and writes a manifest that names the
328base's layers and that one, with the entrypoint `services/runner/Dockerfile`
329gives it. Nothing of the base is downloaded, so the deploy job needs no
330room for it. If that fails and Docker runs where the deploy does, the
331image is built from `services/runner/Dockerfile` and pushed instead, and
332the log shows how full the disk was before and after. A dry run
333(`build`, or `image --no-push`) builds it with Docker. When nothing
334the image is built from changed, the deploy leaves running sandboxes
335alone. When it did, Cloudflare replaces sandboxes on the new image but
336leaves any that started within the last two hours
337(`rollout_active_grace_period` in `services/runner/wrangler.jsonc`) to
338finish first. A workflow job on g1t's machines runs for at most 60
339minutes, so a deploy never cuts one short.
340
341The standard sandbox is the instance type `standard-3` (2 vCPU, 8 GiB,
34216 GB), and so is `runs-on: g1t-2core`, in a class of its own; jobs that
343ask for `runs-on: g1t-4core`, and `ubuntu-latest` jobs in a public
344repository, get `standard-4` (4 vCPU, 12 GiB, 20 GB). At
345the `max_instances` in `services/runner/wrangler.jsonc` (20, 10 and 10),
34640 sandboxes at once hold 100 vCPU, 360 GiB of memory and 680 GB of disk,
347which your account's Containers limits for live instances must allow.
348
349Set the runner Worker's `DOCKER` variable to `off` to give workflow jobs no
350Docker Engine.
351
352`CLOUDFLARE_API_TOKEN=<token> node scripts/ops/runner-errors.mjs` sorts the
353sandboxes' errors over the last 7 days into expected ones (a deploy
354resetting them, no free capacity, a container that stopped) and ones to
355read.
356
357## Docker deployments
358
359Container apps ([Docker deployments](/guides/deployments/#docker-deployments))
360run when `CONTAINERS` is `on` in `services/deployments/wrangler.jsonc`, as
361it is for g1t.sh. On an account without Containers, set it to `off`: a
362container build then fails, saying so, and a project with a site beside
363its `Dockerfile` is built as that site.
364
365The calls follow Cloudflare's API reference and Wrangler's own client.
366On a new account, deploy a small server (an Express app with a
367`Dockerfile`, or none) and check each of these:
368
3691. **The token.** `CLOUDFLARE_API_TOKEN` of `services/deployments` has
370 **Containers: Edit** (and the account has Containers, on Workers Paid).
3712. **Registry logins.**
372 `POST /accounts/{id}/containers/registries/registry.cloudflare.com/credentials`
373 with `{ "permissions": ["push"], "expiration_minutes": 15 }` answers
374 inside the API's usual `{ success, result }` with a `username` and
375 `password`, and `docker push` succeeds with that login alone, with no
376 `pull` (Docker checks for layers before it uploads them). If it needs
377 `pull`, do not add it: a pull login reads every app's image. Find
378 another way first, such as Cloudflare scoping logins to a repository.
3793. **Checking a digest.** g1t asks
380 `HEAD https://registry.cloudflare.com/v2/<account>/g1t-<app>/manifests/sha256:…`
381 with a pull login of its own, following a `WWW-Authenticate: Bearer`
382 challenge. It answers 200 for an image that was pushed.
3834. **The app's Worker.** A script uploaded to the dispatch namespace with
384 `exports: { "G1tApp": { "type": "durable-object", "storage": "sqlite" } }`
385 and `containers: [{ "class_name": "G1tApp" }]` in its metadata is taken,
386 and taken again on the next deploy with nothing changed about the class.
3875. **Its Durable Object namespace.** The app's Worker's `APP` binding
388 (`GET …/dispatch/namespaces/g1t-deployments/scripts/<app>/bindings`)
389 names its `namespace_id`; failing that,
390 `GET /accounts/{id}/workers/durable_objects/namespaces` lists it with
391 `class` `G1tApp` and `script` the app's name.
3926. **The Container application.** `POST /accounts/{id}/containers/applications`
393 with `scheduling_policy` `default`, the image by digest, `instance_type`,
394 `max_instances` 1 and `durable_objects.namespace_id` creates it; a
395 rollout (`…/rollouts`, `strategy` `rolling`, `step_percentage` 100,
396 `kind` `full_auto`) moves it to a new image; `DELETE` removes it.
3977. **Starting.** `ctx.container.start({ env, enableInternet: true })`
398 works under the default policy from a Worker in a dispatch namespace,
399 the container gets its environment, and `getTcpPort(port).fetch()`
400 reaches it; the dispatcher's limits (50 ms of CPU, 50 subrequests) leave
401 room for a page that waits on a container starting.
4028. **Sleeping and billing.** Five minutes after the last request the
403 container stops (the app's object stops it itself), Cloudflare's billable
404 usage for Containers stops growing, and `container_meters` in the
405 deployments database has the minutes the app's Worker reported, which
406 should be at least what Cloudflare billed for the same time.
4079. **Pausing and taking down.** A workspace over its limit gets its
408 container app's Worker put back paused, which stops the container, and
409 taking an app down deletes its application before its Worker.
41010. **Old images.** After a second deploy of the app, the registry's tag
411 list for `<account>/g1t-<app>` has only the two newest builds' tags
412 (`GET /v2/<account>/g1t-<app>/tags/list`), and none once the app is
413 taken down. The account holds 50 GB of images in all.
414
415## Rolling back
416
417- **One unit, at once:** `npx wrangler rollback` in its folder. Revert the
418 commit on `main` too, or the next push brings it back.
419- **To a commit:** check it out and run
420 `node scripts/deploy.mjs deploy --only <units> --rollback`. Without
421 `--rollback` the tool refuses a unit whose live commit is newer.
422- **The runner's image:** redeploy the older commit with
423 `--only runner --rollback`; its image is still in the registry.
424
425## Other one-time setup
426
427### The dead-letter queue
428
429Every queue consumer retries a message a fixed number of times, then puts
430it on `g1t-events-dlq`. Nothing consumes that queue: read it with
431`npx wrangler queues info g1t-events-dlq`, fix the cause, and replay by
432hand.
433
434### OIDC tokens for workflow jobs
435
436The API issues workflow jobs' [OIDC tokens](/guides/actions/), signed with
437an RSA key in its secret `ACTIONS_OIDC_KEY`. Without it, jobs get no
438tokens.
439
440```sh
441openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out oidc.pem
442cd apps/api && npx wrangler secret put ACTIONS_OIDC_KEY < ../../oidc.pem
443rm ../../oidc.pem
444```
445
446To rotate it, store the current key as `ACTIONS_OIDC_KEY_PREVIOUS`, store
447a new one as `ACTIONS_OIDC_KEY`, and delete the previous one a day later.
448For a key that may have leaked, skip keeping the previous one.
449
450### Telling the status page about deploys
451
452Restarts during a deploy can look like an outage to the status page's
453checks. Set the same value as the status Worker's secret
454`STATUS_DEPLOY_TOKEN` and as the repository's Actions secret
455`STATUS_DEPLOY_TOKEN`. Deploys then announce their start and finish, and the status page
456drafts no incident during them or for 3 minutes after.
457
458### Browser push
459
460`node scripts/ops/vapid-keys.mjs` prints a VAPID key pair. Put the public
461half in `VAPID_PUBLIC_KEY` in `services/notify/wrangler.jsonc` and the
462private half in the secret `VAPID_PRIVATE_KEY`.
463
464Pushes to the phone app go through Expo's push service and need nothing
465set. If the Expo project the app is built from turns on **enhanced push
466security**, put an Expo access token in the notify Worker's secret
467`EXPO_ACCESS_TOKEN`:
468
469```sh
470cd services/notify && npx wrangler secret put EXPO_ACCESS_TOKEN
471```
472
473For g1t.sh, leave enhanced push security off: every self-hosted g1t
474pushes to the same app, through the same Expo project, without a token of
475g1t's. Turning it on stops notifications to phones from every
476installation but g1t.sh.
477
478To send phones nothing at all, set `PHONE_PUSH` to `off` in the `vars` of
479both `services/notify/wrangler.jsonc` and `apps/web/wrangler.jsonc`: the
480notify Worker sends them nothing, and the site's
481`/.well-known/g1t.json` tells the app so.
482
483## Rate limits
484
485Every public surface that costs money or sends something is behind a
486[Workers Rate Limiting](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/)
487binding, with the limits on the [rate limits](/reference/rate-limits/)
488page. The binding's `namespace_id` must be unique in your account. A
489missing binding, or one that fails, lets requests through.
490
491## Spend guardrails
492
493Cloudflare has no hard spending cap. Besides each workspace's own
494[limits](/guides/usage-and-billing/), g1t watches the platform as a whole:
495
496- **The hourly watch.** At a quarter past each hour, billing reads the
497 hour before from Cloudflare's GraphQL Analytics API: Workers requests
498 and CPU, D1 rows, Queue operations, Durable Objects, KV and Artifacts.
499 It needs `CLOUDFLARE_BILLING_TOKEN` (or `CLOUDFLARE_USAGE_TOKEN`) with
500 Account Analytics Read. An hour over its threshold, or over ten times
501 the week's median hour, is emailed to `COSTS_ALERT_EMAIL`.
502- **Thresholds** are the `PLATFORM_HOURLY_*` variables in
503 `services/billing/wrangler.jsonc`; `0` turns one off. A breach at five
504 times its threshold pauses the kinds of work in `AUTO_PAUSE`.
505- **The platform pause** stops a kind of work everywhere: `compute`
506 (sandboxes), `schedules`, `indexing` or `renders` (social cards). Staff
507 pause and resume in sudo, under **Costs & margin**. Work already
508 running finishes, and if the pause cannot be read, nothing is paused.
509- **Check the watch's queries** after changing them:
510 `CLOUDFLARE_API_TOKEN=<token> node scripts/ops/platform-usage.mjs`. It
511 exits 1 and names any dataset that errored or answered empty.
512
513In Cloudflare's dashboard, which no code can set, add **Billable Usage**
514notifications for each product g1t uses, a monthly **Budget alert**, and a
515notification destination that reaches someone.
516
517## Logs
518
519Each Worker keeps a share of its invocations' logs, set by
520`head_sampling_rate` in its `wrangler.jsonc`: every one for billing,
521identity, runner, actions, deployments, status and sudo, a quarter for
522repos and work, which every page asks, and a tenth for the rest. Metrics
523are not sampled. To chase a rare error on a sampled Worker, raise its rate
524to 1 while you look.
525
526Slow work in repos logs itself as fields, so the invocations kept can be
527counted by what was slow: an RPC answer that took a second or more logs
528`rpc` (the method) and `answered_ms`, and a slow or partial About run logs
529`about_run` (the repository's id), `took_ms` and `partial`. In Workers
530Logs, filter `g1t-repos` on `answered_ms` greater than 0, and multiply
531what you count by four.