Skip to content

g1t/apps/docs/src/content/docs/guides/deployments.md

491 lines25,290 bytesCodeBlame
1---
2title: Deployments
3description: A project's production on every push and a live preview of every branch with a pull request, on g1t.page. Scales to zero, and billed to the workspace.
4---
5
6Deployments put your [project](/guides/projects/) on the web. They are off
7for every project until you turn them on. Once they are on, every branch
8with a pull request gets its own live preview, linked on the pull request,
9and the default branch goes to production on every push. Reviewers, and the agents reviewing for you,
10click through the change instead of reading a diff.
11
12Apps run on Cloudflare Workers, on `g1t.page`:
13
14| | Address |
15| --- | --- |
16| Production | `https://<project>-<workspace>.g1t.page` |
17| Preview of the branch `fix-login` | `https://<project>-git-fix-login-<workspace>.g1t.page` |
18
19A preview's address follows its branch, so it stays the same for every push
20to it. A pull request from a fork, as g1t's agents make them, is named
21`pr-<number>` in place of the branch. A name too long for an address, or
22one another app already has, is shortened or given a short suffix.
23
24A workspace whose name ends like a domain is written without that last
25hyphen: `flagon-io` gives `https://web-flagonio.g1t.page`, not
26`web-flagon-io.g1t.page`. Browsers read `flagon-io` in an address as a
27copy of `flagon.io`, and warn people who visit flagon.io that the page
28looks fake. Apps made before this moved to the new address on their own;
29the old one redirects to it.
30
31When a workspace is [renamed](/guides/workspaces/#rename-a-workspace), or a
32repository is [transferred](/guides/transferring-repositories/#deployments)
33to another workspace or [renamed](/guides/managing-repositories/), its apps
34are built again under addresses with the new name, and the old addresses
35redirect to them for 90 days. A rebuild that fails is tried again an hour
36later, then two hours after that. After three failures with the same
37error, or after one whose commit no longer exists, it is not tried again
38until you push or choose **Redeploy**.
39
40When the [default branch changes](/guides/managing-repositories/), production
41is built again from the new default branch. An
42[archived](/guides/managing-repositories/) repository's apps keep serving.
43
44Every build is also a deployment in the repository's one list of
45deployments, beside those reported from any CI and those g1t Actions
46jobs make, with the source `g1t_page` and the environment `production` or
47`preview`. See [Deployments API](/guides/deployments-api/) to read them,
48report your own, and hear of them by webhook.
49
50An app runs only while it answers a request. One nobody visits runs
51nothing and costs nothing, and the next visit wakes it in milliseconds.
52
53Deployments are part of the [g1t plan](/guides/usage-and-billing/#the-g1t-plan),
54and are opt-in per project. A project set to deploy on g1t, with
55deployments off, says **Deployments are off** on its overview, with **Turn on deployments** for
56people with the Admin [role](/guides/access-and-roles/) on its repository. Nothing builds or runs until you turn them on, and one click turns
57them off again.
58
59## Turn on deployments
60
611. **Start the g1t plan for the workspace**, if it is not on. An owner
62 opens **Settings → Billing and plans**, `g1t.sh/<workspace>/-/billing`,
63 and chooses **Start the g1t plan** on the **g1t** card. Back on
64 Billing, the card says **On the g1t plan**. The trial does not pay for
65 deployments.
662. **Turn on deployments for a project.** Someone with the Admin role on
67 its repository opens the project's
68 **Settings → Deployments**, `g1t.sh/<workspace>/<project>/settings/deployments`,
69 or chooses **Deploy** on its overview,
70 and chooses **Turn on deployments**.
71
72Production starts building at once from the default branch. Every pull
73request opened or pushed to from then on gets a preview.
74
75Deployments are for projects g1t runs. Not every project is one: a
76library, a tool or documentation is published rather than deployed, and
77an app may be deployed by its own pipeline somewhere else. Only a project
78that is [an app or site deployed on g1t](/guides/projects/#what-a-project-is)
79is asked to turn deployments on. Any other project's **Deployments** page
80still turns them on; doing so makes it an app deployed on g1t, including
81one that was deployed elsewhere. A project set to be a library, a tool or
82something else cannot have deployments turned on until that setting
83changes.
84
85While payments on g1t are in test mode, no real card is charged: use the
86test card `4242 4242 4242 4242` with any future date and any code.
87
88## What deploys
89
90g1t looks at the project's root directory and builds it the way it is meant to be built.
91You do not configure anything for the common cases.
92
93| The project has | g1t |
94| --- | --- |
95| `wrangler.jsonc`, `wrangler.json` or `wrangler.toml` | Builds it as a **Workers project**: installs dependencies, runs `wrangler deploy --dry-run` to bundle it (which runs the config's own `build` command), and deploys the bundle with the config's static assets, `compatibility_date`, `compatibility_flags` and `vars`. |
96| A `build` script in `package.json` | Installs dependencies, runs `npm run build`, and serves the output as a **static site**. |
97| An `index.html` and nothing to build | Serves it as it is. |
98
99**Settings → Deployments** shows what g1t detected at the project's last
100finished build (**Workers project**, **Static site** or **Plain HTML**),
101and what running it costs. Before the first build it says **Detected at
102the first build**.
103
104Dependencies are installed by the lockfile that is there: `npm ci`,
105`pnpm install --frozen-lockfile`, `yarn install` or `bun install`, and
106`npm install` with no lockfile.
107
108A static site is served from the first of these that exists after the
109build: `dist`, `build`, `out`, `public`, `_site`, `.output/public`. Set
110**Output directory** to choose another.
111
112### Static sites
113
114- A site without a `404.html` is treated as a single-page app: an address
115 that matches no file serves `index.html`. With a `404.html`, that page
116 is served instead, with status 404.
117- `_headers` and `_redirects` files in the output are honored, in
118 [Cloudflare's format](https://developers.cloudflare.com/workers/static-assets/headers/).
119- Up to 20,000 files, and 25 MiB per file: Cloudflare's limits.
120
121### Workers projects
122
123- Your Worker's `fetch` handler runs as written, and its static assets
124 are served under the binding name your config gives them. Cron triggers
125 (`triggers.crons`) are not scheduled, so a `scheduled` handler never
126 runs; the deployment says so in its warnings.
127- `vars` are deployed as plain-text bindings (or JSON, for objects). Rows
128 of the project's [secrets and variables](/guides/secrets-and-variables/)
129 available to Deployments are bound too, and replace a `var` of the same
130 name: secrets as secret bindings.
131- **Not provisioned yet:** D1, KV, R2, Durable Objects, Queues, service
132 bindings, Vectorize, Hyperdrive, Workers AI and Workflows. A project that
133 declares any of them still deploys, without them, and its deployment
134 lists each one it left out. Code that needs them should check that the
135 binding is there.
136
137What Deployments cannot run yet, such as long-running servers, is on
138[What g1t can't do yet](/about/limitations/#deployments).
139
140## Addresses of other projects
141
142A project that [depends on another](/guides/projects/#dependencies) with
143`as: API_URL` gets that project's address as `API_URL`, in its build and in
144its running app:
145
146| Building | `API_URL` is |
147| --- | --- |
148| Production | The other project's production. |
149| A preview of branch `x` | The other project's preview of `x` if it is up, else its production. |
150
151A secret or variable of the same name wins over it.
152
153### Preview stacks
154
155A change to an API is best seen in the apps that call it. On a pull
156request whose preview is up, **Preview them against this change** (under
157**Affects**) builds a preview of every project that uses this one, from its
158own default branch, under the same branch name. Each gets this preview's
159address through its variable. They come down with their idle days, like
160any preview.
161
162## Previews of branches
163
164A preview is built when a pull request is opened, when it is marked ready,
165and on every push to it, including an agent's. Pull requests from forks,
166which is how g1t's agents work, are built from the fork.
167
168The pull request shows the deployment as a check named **g1t / deploy**:
169
170| State | Means |
171| --- | --- |
172| Pending, "Building" | The build is running. The link opens its log. |
173| Passed, "Preview is live" | The link opens the preview. |
174| Failed, "Deployment failed" | The link opens the build log and the reason. |
175
176A newer push replaces a build that is still running for the same pull
177request. Previews are marked `noindex`, so search engines leave them alone.
178
179The **Deployments** page lists the same failure of one app, repeated, as a
180single row with how many times it happened and when it last did. The row
181opens the newest of them.
182
183## Production
184
185Each push to the default branch, which is each merge on a protected
186branch, builds and replaces production. The **Deployments** page shows the
187live address, the commit it runs and when it went up.
188
189### The production screenshot
190
191When production goes live, g1t takes a screenshot of its home page, as a
1921280 by 800 browser window sees it, and shows it on the project's
193[overview](/guides/projects/#the-overview). Choosing it opens the site.
194
195- One screenshot is taken per production deploy. g1t waits up to 12
196 seconds for the page to settle, then takes it as it is.
197- The screenshot is of the app's `g1t.page` address. A custom domain
198 serves the same app, so it looks the same.
199- Until it is ready, or if it could not be taken, the overview shows a
200 plain frame with the address. A failed screenshot is tried again when
201 the overview is next opened, at most every five minutes.
202- Only people who can read the project's repository see it, like the
203 rest of its deployments.
204- Screenshots are not charged.
205
206The [checklist on the overview](/guides/projects/#get-to-production) also
207tracks the first production deploy, a custom domain and a first preview.
208
209## Custom domains
210
211Production can be served at a domain of your own, such as `example.com` or
212`www.example.com`, as well as at its address on g1t.page. A domain belongs
213to the project, not to a build: every redeploy is served on it with nothing
214to change.
215
216### Add a domain
217
2181. Open the project's **Settings → Domains**,
219 `g1t.sh/<workspace>/<repo>/settings/domains`.
2202. Enter the domain and choose **Add domain**. Leave **Also add the www
221 (or apex) twin** checked to add both `example.com` and
222 `www.example.com` at once: the one you typed serves the app, and the
223 other redirects to it with a `308`, path and query kept.
2243. Add the DNS records the page lists, at whoever manages the domain's
225 DNS. Each name and value has a copy button.
2264. Wait. The page follows the domain from **Waiting for DNS** to
227 **Issuing certificate** to **Active** by itself; **Check now** asks
228 again at once.
229
230Custom domains need the workspace's g1t plan, and are $0.12 a month each
231(their cost plus 20%), as many as you like. Adding or removing one needs
232the Admin [role](/guides/access-and-roles/) on the project's repository.
233
234### A subdomain, such as www
235
236Add one record:
237
238| Type | Name | Target |
239| --- | --- | --- |
240| `CNAME` | `www` | `domains.g1t.page` |
241
242The same goes for any subdomain, such as `app.example.com` (name `app`).
243
244### The apex, such as example.com
245
246DNS does not allow a plain `CNAME` at the apex of a domain, so use your
247provider's flattened form of one, pointed at `domains.g1t.page`:
248
249| DNS provider | Record |
250| --- | --- |
251| Cloudflare DNS | `CNAME` at `@` (Cloudflare flattens it) |
252| Amazon Route 53 | Not supported by alias records to other zones; use the www pairing below |
253| DNSimple, NS1, Namecheap, Porkbun, Gandi | `ALIAS` at `@` |
254| DNS Made Easy, Constellix | `ANAME` at `@` |
255
256If your provider has no flattened `CNAME`, `ALIAS` or `ANAME`, add
257`www.example.com` on g1t instead, and set up your provider's forwarding (or
258any redirect) from `example.com` to `www.example.com`.
259
260### Verification and certificates
261
262Pointing the domain at `domains.g1t.page` is what proves it is yours: once
263the record is seen, the certificate authority checks the domain over HTTP
264and issues a certificate, renewed by itself before it expires. Visitors are
265always served over HTTPS, TLS 1.2 or newer.
266
267The page may also list a `TXT` record named `_cf-custom-hostname.<domain>`.
268It proves ownership before traffic moves, which is useful when the domain
269is serving a site elsewhere today: add the `TXT` first, wait for
270**Issuing certificate** or **Active**, then change the `CNAME`. Any other
271`TXT` records listed are for the certificate and are needed as shown.
272
273DNS changes can take from a few minutes to an hour to be seen. A domain
274that stays at **Waiting for DNS** usually has a record with a typo, a
275leftover `A` or `AAAA` record beside the new one, or (on Cloudflare DNS) a
276`CAA` record that does not allow the certificate's authority.
277
278### Remove a domain
279
280Choose **Remove** beside it. It stops serving the project at once, and any
281domain redirecting to it goes too. Your DNS records are left as they are.
282
283## When apps come down
284
285Nothing keeps running unasked. An app comes down, and stops costing
286anything, when:
287
288| | |
289| --- | --- |
290| Its pull request is merged or closed | That preview, at once. |
291| No one visits a preview for the project's **idle days** | That preview, at the next sweep (every 10 minutes). The default is 7 days. |
292| You choose **Take down** on the Deployments page | That app, at once. |
293| You turn off previews or production | All of that kind, at once. |
294| You choose **Turn off deployments** | Every app of the project, at once, and no more builds. |
295| The workspace's plan ends or its payment fails | Every app of the workspace, at the next sweep. |
296| The repository is [deleted](/guides/managing-repositories/) | Every app of its projects, at once. Custom domains stay set up and serve nothing. If you restore the repository, production is built again and its domains serve it; previews come back with their next push. When it is purged, its custom domains are removed. |
297
298A preview that came down comes back with the next push to its pull
299request, or **Redeploy** on the Deployments page.
300
301## Who can do what
302
303What you can do with a project's deployments follows your
304[role](/guides/access-and-roles/) on its repository:
305
306| | Needs |
307| --- | --- |
308| See its deployments, their build logs, previews and domains | Read |
309| **Redeploy**, preview a stack, take an app down | Write |
310| Turn deployments on or off, change their settings | Admin |
311| Add, verify and remove custom domains | Admin |
312
313A public repository's deployments, and their build logs, can be seen by
314anyone, signed in or not, the same as its code. Its settings and domains
315still need Admin. A private repository's deployments are seen only by
316people with a role on it.
317
318## Settings
319
320Under the project's **Settings → Deployments**,
321`g1t.sh/<workspace>/<repo>/settings/deployments`:
322
323| Setting | Default | |
324| --- | --- | --- |
325| Production | On | Deploy the default branch on every push. |
326| Previews | On | A preview for every branch with an open pull request. |
327| Build command | The project's own | Runs instead of `npm run build`, or before bundling a Workers project. |
328| Output directory | Found by itself | What a static site serves. |
329| Idle days | 7 | 1 to 90. A preview no one visits this long comes down. |
330
331## Secrets and variables
332
333Builds and running apps read the project's
334[secrets and variables](/guides/secrets-and-variables/) that are
335available to Deployments, and the workspace's that reach it:
336
337| | Reads |
338| --- | --- |
339| A production build, and production | Each key's Production row, else its row for all environments. |
340| A preview build, and the preview | Each key's Preview row, else its row for all environments. |
341
342The build gets them as environment variables, with secrets hidden in its
343log. The running app gets them as bindings, `env.KEY`, put in place by g1t
344rather than the build. Secrets reach a preview only when the pull
345request's author has Write or higher on the repository, whether a member
346or an [outside collaborator](/guides/access-and-roles/#outside-collaborators),
347or it is g1t working on its own. For a pull request g1t made, whoever
348asked for it is the one whose role counts. A preview of anyone else's pull request, such as
349one from a fork or by someone with Read or Triage, is built and runs with
350variables only, no secrets.
351
352For example, a `STRIPE_KEY` secret with a Production row holding the live
353key and a Preview row holding the test key gives every preview the test
354key.
355
356## What it costs
357
358Deployments come with the
359[g1t plan](/guides/usage-and-billing/#the-g1t-plan), $20 a month per
360workspace with $10 of usage included. There are **no quotas**: as many
361projects and previews as you like, and no count of builds, requests or
362domains ever stops or pauses an app. What deployments cost g1t is metered
363from the first unit, at Cloudflare's price plus 20%, and paid from the
364plan's $10 of included usage first; past it, it is charged up to your
365[spend limit](/guides/usage-and-billing/#limits).
366
367| | Costs g1t | You pay |
368| --- | --- | --- |
369| Projects, previews and the apps behind them | Next to nothing | Not charged |
370| Builds, by the second, whether they succeed or fail | About $0.001 a minute | About $0.0012 a minute |
371| Requests, to all of the workspace's apps | $0.30 a million | $0.36 a million |
372| CPU time your code spends computing (waiting on the network is not counted) | $0.02 a million ms | $0.024 a million ms |
373| Custom domains, with their certificates (a www/apex pair is two) | $0.10 a month | $0.12 a month, by the most the workspace had at once that month |
374
375Builds are charged when they finish. Requests, CPU time and custom domains
376are counted through the month, so your limit and **Billing** follow them as
377they happen, and charged once, on the first sweep after the month ends, as
378one line: *Deployments in 2026-10: 1,240,000 requests, 3,100,000 CPU ms, 2
379custom domains*.
380
381A static site, or plain HTML, runs no code when it is visited: its files
382are served as they are, so serving it adds no requests or CPU time. Its
383builds are metered. A Workers project's code runs on every request, so its
384requests and CPU time are metered too.
385
386An app that no one visits costs nothing. Previews still come down when
387their pull request closes and after their idle days, to keep the list of
388what is up short.
389
390The only things that stop a workspace's apps are its own spend limit
391(apps pause with a notice, and come back once there is room) and the plan
392ending (they come down).
393
394Deployments used to be a $5 plan of their own. A Deployments subscription
395bought before keeps working until its current period ends, and is not
396renewed.
397
398### Seeing what you use
399
400- **Usage** shows **Deployments** with *Builds*
401 (build minutes and their cost), *Requests & CPU* and *Custom domains*,
402 in dollars, beside the rest of the workspace's usage. Requests and CPU
403 time are counted from Cloudflare's analytics every 10 minutes.
404- The **statement** on Billing lists every build (*Building acme/web to
405 production (48 s), paid by your plan's included usage*) and every
406 month's requests, CPU time and custom domains.
407- Each build's page shows how long it ran.
408
409## Turn it off
410
411- **For a project:** **Turn off deployments** under its **Settings →
412 Deployments**. Every app comes down at once. Turning it on again
413 rebuilds production.
414- **For the workspace:** when an owner ends the g1t plan on Billing,
415 deployments keep working until the date shown; then every app comes
416 down and nothing more is charged.
417
418Usage from the month that is under way is still charged once it ends.
419
420## How it works
421
4221. A pull request opens, or someone pushes. The deployments service hears
423 of it, checks that the workspace's plan is on, and reserves what the
424 build may cost (30 minutes of sandbox time) with billing. If billing
425 refuses (no paid plan, the spend limit reached, compute paused), the
426 build does not start: the deployment's status is **Skipped** with the
427 reason and what to do, and **Redeploy** shows the same reason.
4282. It starts a build in a sandbox of its own, the same machines that run
429 [GitHub Actions](/guides/actions/) and agents. The sandbox checks out
430 the commit with a read-only token that expires in 30 minutes. Its
431 network is restricted to the project's allowed domains, the package
432 registries, GitHub and Cloudflare's API, as
433 [workflow jobs'](/guides/actions/#what-a-job-can-reach) are, and it
434 stops at 30 minutes.
435 When it stops, what it cost is settled against what was reserved.
4363. The sandbox builds, then lists its files. g1t opens an upload with
437 Cloudflare for exactly those files and hands the sandbox a key that can
438 upload them and nothing else. Files Cloudflare already has are skipped.
4394. The sandbox sends the Worker's code to g1t, which puts the app in
440 g1t's [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/)
441 namespace, under the name in its address.
4425. A request to `*.g1t.page` reaches g1t's dispatcher, which runs the app
443 by the name in the hostname. Nothing else is looked up. A request to a
444 custom domain reaches the same dispatcher through Cloudflare for SaaS;
445 it looks the hostname up once, in a key-value store kept at the edge.
446
447Your code never holds a Cloudflare credential, and apps are served from
448`g1t.page`, not `g1t.sh`, so they share no cookies or origin with the site
449you sign in to.
450
451## Troubleshooting
452
453| You see | Do |
454| --- | --- |
455| "Deployments come with the g1t plan … and `<workspace>` does not have it" | An owner starts the g1t plan under Billing. |
456| A deployment **Skipped**, saying it reached its spend limit or that new compute is paused | The build was refused before it started. Follow the link in the message: raise the spend limit, prepay, or answer the spike, on Billing. |
457| "Stopped: unusual CPU use" | The build looked like it was mining. Contact support if it was a real build; see [abuse and mining](/guides/guardrails/#abuse-and-mining). |
458| "403" from a host in the build log | The build's network is restricted. Add the host to the project's allowed domains under **Settings → Guardrails**. |
459| "found nothing to serve" | Add a `build` script, an `index.html`, or a Workers config; or set **Output directory**. |
460| "the output directory `x` does not exist after the build" | The build wrote elsewhere: check its log, then fix **Output directory**. |
461| "`d1_databases` is not provisioned on g1t.page yet" | The app deployed without that binding. See [Workers projects](#workers-projects). |
462| "`triggers.crons` (1 schedule) is not set up on g1t.page yet" | The app deployed, but its `scheduled` handler never runs. See [Workers projects](#workers-projects). |
463| A preview page says "This preview is not up" | It came down (see [When apps come down](#when-apps-come-down)). Push, or choose **Redeploy**. |
464| "Custom domains are being switched on" | Custom domains are not on for g1t.page yet. Domains you add are kept, and set up by themselves once they are. |
465| A domain stays at **Waiting for DNS** | Check the record against the one listed, remove other `A`/`AAAA` records for the same name, then choose **Check now**. |
466| A domain says "This domain is not set up" | It points at g1t, but no project has added it. Add it under **Settings → Domains**. |
467| "This pull request's commit no longer exists" | The pull request's head was force-pushed over, or its branch deleted, after the build was asked for. Push to the pull request again, or close it. g1t does not try that commit again. |
468| "The build did not finish in 45 minutes" | The build stopped reporting: a build stops at 30 minutes, and one not heard from after 45 is failed. Make the build faster, or build less for previews with **Build command**. |
469
470## Running your own g1t
471
472Deployments need a Workers for Platforms namespace and a zone for apps:
473
4741. Create the namespace: `npx wrangler dispatch-namespace create g1t-deployments`.
4752. Add a proxied wildcard DNS record (`*`, `AAAA`, `100::`) on the apps'
476 zone, and set the zone in `services/pages/wrangler.jsonc`.
4773. Create an API token with **Workers Scripts: Edit** and **Account
478 Analytics: Read**, and store it:
479 `npx wrangler secret put CLOUDFLARE_API_TOKEN` in `services/deployments`.
4804. Deploy `services/deployments`, `services/pages`, and the runner.
481
482Custom domains also need Cloudflare for SaaS on the apps' zone. Turn it on
483under the zone's **SSL/TLS → Custom Hostnames**, give the token **SSL and
484Certificates: Edit** on the zone, and run `scripts/setup-custom-domains.sh`:
485it creates the `g1t-domains` KV namespace, the fallback origin's DNS record
486(`domains`, `AAAA`, `100::`, proxied) and sets it as the fallback origin.
487The dispatcher's `*/*` route on the zone, in `services/pages/wrangler.jsonc`,
488is what brings custom domains' traffic to it.
489
490Without a card processor configured, every feature is on and nothing is
491charged.