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.

1,055 linesCodeBlameRaw
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
10click through the change instead of reading a diff.
11
12Apps run on Cloudflare Workers, on `g1t.page`: sites, Workers projects,
13and Node.js servers such as Express apps. A server that cannot run as a
14Worker runs as a container on Cloudflare Containers behind one; see
15[What a project is built as](#what-a-project-is-built-as) and
16[Docker deployments](#docker-deployments):
17
18| | Address |
19| --- | --- |
20| Production | `https://<project>-<workspace>.g1t.page` |
21| Preview of the branch `fix-login` | `https://<project>-git-fix-login-<workspace>.g1t.page` |
22
23A preview's address follows its branch, so it stays the same for every push
24to it. A pull request from a fork, as coding agents make them, is named
25`pr-<number>` in place of the branch. A name too long for an address, or
26one another app already has, is shortened or given a short suffix.
27
28A workspace whose name ends like a domain is written without that last
29hyphen: `flagon-io` gives `https://web-flagonio.g1t.page`, not
30`web-flagon-io.g1t.page`. Browsers read `flagon-io` in an address as a
31copy of `flagon.io`, and warn people who visit flagon.io that the page
32looks fake. Apps made before this moved to the new address on their own;
33the old one redirects to it.
34
35When a workspace is [renamed](/guides/workspaces/#rename-a-workspace), or a
36repository is [transferred](/guides/transferring-repositories/#deployments)
37to another workspace or [renamed](/guides/managing-repositories/), its apps
38are built again under addresses with the new name, and the old addresses
39redirect to them for 90 days. A rebuild that fails is tried again an hour
40later, then two hours after that. After three failures with the same
41error, or after one whose commit no longer exists, it is not tried again
42until you push or choose **Redeploy**.
43
44When the [default branch changes](/guides/managing-repositories/), production
45is built again from the new default branch. An
46[archived](/guides/managing-repositories/) repository's apps keep serving.
47
48Every build is also a deployment in the repository's one list of
49deployments, beside those reported from any CI and those g1t Actions
50jobs make, with the source `g1t_page` and the environment `production` or
51`preview`. See [Deployments API](/guides/deployments-api/) to read them,
52report your own, and hear of them by webhook.
53
54An app runs only while it answers a request. One nobody visits runs
55nothing and costs nothing, and the next visit wakes it in milliseconds. A
56container wakes in seconds, and sleeps again after five minutes without a
57request.
58
59Deployments are part of the [g1t plan](/guides/usage-and-billing/#the-g1t-plan),
60and are opt-in per project. A project set to deploy on g1t, with
61deployments off, says **Deployments are off** on its overview, with **Turn on deployments** for
62people with the Admin [role](/guides/access-and-roles/) on its repository. Nothing builds or runs until you turn them on, and one click turns
63them off again.
64
65## Turn on deployments
66
671. **Start the g1t plan for the workspace**, if it is not on. An owner
68 opens **Settings → Billing and plans**, `g1t.sh/<workspace>/-/billing`,
69 and chooses **Start the g1t plan** on the **g1t** card. Back on
70 Billing, the card says **On the g1t plan**. The trial does not pay for
71 deployments.
722. **Turn on deployments for a project.** Someone with the Admin role on
73 its repository opens the project's
74 **Settings → Deployments**, `g1t.sh/<workspace>/<project>/settings/deployments`,
75 or chooses **Deploy** on its overview,
76 and chooses **Turn on deployments**.
77
78Production starts building at once from the default branch. Every pull
79request opened or pushed to from then on gets a preview.
80
81### Without the site
82
83An agent, a script or the CLI turns them on the same way, as someone with
84the Admin role on the repository; the workspace still needs the g1t plan.
85
86- **As you make the repository.** Give `deploy` when you create it:
87 `g1t repo create acme/web --deploy`, `deploy: true` on the
88 [MCP](/reference/mcp/) `repository` tool's `create`, or on
89 [`POST /repos`](/reference/api/repositories/create-repo/). Its first
90 push to the default branch then deploys production. The repository is
91 made even when deployments can't be turned on: the response has
92 `deployments` (the settings, with `production_url`) when they are on, or
93 `deploy_error` saying why not, such as the workspace having no g1t plan.
94- **For a project that exists.** `g1t deploy enable` in its checkout
95 (`--project acme/web` from anywhere), the MCP `workflow` tool's
96 `update_deployment_settings` with `enabled: true`, or:
97
98```sh
99curl -X PATCH https://api.g1t.sh/workspaces/acme/projects/web/deployments/settings \
100 -H "Authorization: Bearer $G1T_TOKEN" \
101 -H "Content-Type: application/json" \
102 -d '{"enabled": true}'
103```
104
105It answers with the project's settings, `production_url` among them.
106Without the g1t plan it is refused with `402 payment_required`, saying
107where an owner starts it; without the Admin role, with `403`. Turning them
108on for an existing project needs a token with `repo:admin`; turning them
109on as you make a repository needs only `repo:create`, which the
110[Agent preset](/guides/authentication/#presets) gives, since you are the
111new repository's admin. `g1t deploy disable` (or `enabled: false`) turns
112them off. The other [settings](#settings) change the same way:
113`g1t deploy settings` shows them, and takes flags such as
114`--build-command` and `--previews off`.
115
116Deployments are for projects g1t.page hosts. Not every project is one: a
117library, a tool or documentation is published rather than deployed, and
118an app may deploy itself, with [a workflow](#deploying-from-a-workflow)
119or another pipeline, to hosting of its own. Only a project that is
120[an app or site hosted on g1t.page](/guides/projects/#what-a-project-is)
121is asked to turn deployments on. Any other project's **Deployments** page
122still turns them on; doing so makes it an app hosted on g1t.page,
123including one that deployed itself. A project set to be a library, a tool or
124something else cannot have deployments turned on until that setting
125changes.
126
127On a g1t whose payments use Stripe's test mode, such as a self-hosted one
128set up with test keys, no real card is charged: use the test card
129`4242 4242 4242 4242` with any future date and any code. With live
130payments, the card you enter is charged.
131
132## What deploys
133
134g1t looks at the project's root directory and builds it the way it is meant to be built.
135You do not configure anything for the common cases.
136
137| The project has | g1t |
138| --- | --- |
139| `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`. |
140| A framework from the table below, even beside a `Dockerfile` | Installs dependencies, builds it with the framework's command, and serves the folder it writes as a **static site**, or deploys the Worker its Cloudflare adapter writes as a **Workers project**. |
141| A framework set up to need a server (Next.js without a static export, Astro's `@astrojs/node`, SvelteKit's `adapter-node`, a server-rendered React Router or Remix app) | Builds it as a **container**: from its `Dockerfile`, or one g1t writes for it. |
142| A Node.js server (Express, Koa, Hono, `node:http` and others) with no `Dockerfile` | Runs it as a **Workers project**: wraps its server in a Worker, bundles it, and checks that it starts. One that cannot run as a Worker runs as a **container** instead, and the build's log says why. See [Node.js servers as Workers](#nodejs-servers-as-workers). |
143| A Python, Go, Ruby, PHP or Rust server, or a Node.js server with a `Dockerfile` | Builds it as a **container**: from its `Dockerfile`, or one g1t writes for it and shows in the build's log. See [Docker deployments](#docker-deployments). |
144| A `build` script in `package.json`, even beside a `Dockerfile` | Installs dependencies, runs `npm run build`, and serves the output as a **static site**. |
145| An `index.html` and nothing to build, even beside a `Dockerfile` | Serves it as it is. |
146| A `Dockerfile` and nothing else to serve | Builds the image and runs it as a **container**. |
147| Nothing g1t recognises | Fails, saying to add a `Dockerfile`, a `build` script or an `index.html`. |
148
149### What a project is built as
150
151A site or a Worker is always preferred: it runs nothing while no one
152visits, so it costs nothing then, and it wakes in milliseconds. A
153container is the fallback. With **Build as** on **Auto** (the default),
154the first of these that fits is what the project is built as:
155
1561. **A Workers project**: a Wrangler config (`wrangler.jsonc`,
157 `wrangler.json` or `wrangler.toml`), or a framework's Cloudflare
158 adapter, even beside a `Dockerfile`.
1592. **A framework's site**: a framework from the [table below](#frameworks)
160 that builds a site, even beside a `Dockerfile`. A framework set up to
161 need a server is a container, from its `Dockerfile` or one g1t writes.
1623. **A Node.js server as a Worker**: a Node.js server with no
163 `Dockerfile` ([Node.js servers as Workers](#nodejs-servers-as-workers)).
164 One that cannot run as a Worker is a container, from a Dockerfile g1t
165 writes.
1664. **A container for any other server g1t recognises**: Python, Go, Ruby,
167 PHP or Rust, or a Node.js server with a `Dockerfile` of its own; from
168 that `Dockerfile`, or one g1t writes.
1695. **A site**: a `build` script's output, or an `index.html`, even beside
170 a `Dockerfile`.
1716. **A container from its `Dockerfile`** (or `Containerfile`, or the path
172 set as its **Dockerfile**), when there is nothing else to serve.
1737. Otherwise the build fails, saying what to add.
174
175When a site is built beside a `Dockerfile`, the build's log says the
176`Dockerfile` is not used, near its top: "Built as a Vite site; Dockerfile
177is not used (a container is only the fallback; choose **Build as**:
178Container to use it)." To run the project's `Dockerfile` instead, set
179**Build as** to **Container** under **Settings → Deployments**. **Build
180as** set to **Static site**, **Workers** or **Container** is what the
181project is built as, whatever else it has.
182
183**Settings → Deployments** shows what g1t detected at the project's last
184finished build and what it was built with, such as **Static site ·
185Astro**, **Workers project**, **Container · Dockerfile** or **Container ·
186Flask, with a Dockerfile g1t wrote**, and what running it costs. Before
187the first build it says **Detected at the first build**. The framework is
188named at the top of the build's log, as `Detected Astro: framework=astro.`
189**Build as**, under the same settings, overrides what is detected.
190
191Dependencies are installed by the lockfile that is there: `npm ci`,
192`pnpm install --frozen-lockfile`, `yarn install` or `bun install`, and
193`npm install` with no lockfile.
194
195### Frameworks
196
197g1t knows a framework by the packages in `package.json`
198(`dependencies` and `devDependencies`) and by its config file. The build
199command is the package's own `build` script when it has one, else the
200command in the table. A **Build command** or **Output directory** of
201your own, under **Settings → Deployments**, always wins over the table.
202
203| Framework | Found by | Builds with | Serves |
204| --- | --- | --- | --- |
205| Vite, and React, Vue, Svelte, Solid or Preact on Vite | `vite` | `vite build` | `dist`, or `build.outDir` from `vite.config` |
206| Astro | `astro` | `astro build` | `dist`, or `outDir` |
207| Astro with `@astrojs/cloudflare` | `astro`, `@astrojs/cloudflare` | `astro build` | A Workers project: the Worker and config the adapter writes |
208| Next.js with `output: 'export'` | `next`, and `output: 'export'` in `next.config` | `next build` | `out` |
209| Next.js with OpenNext | `next`, `@opennextjs/cloudflare` and a `wrangler.jsonc` | `opennextjs-cloudflare build`, then `wrangler deploy --dry-run` | A Workers project |
210| SvelteKit with `@sveltejs/adapter-static` | `@sveltejs/kit` and the adapter | `vite build` | `build`, or the adapter's `pages` |
211| SvelteKit with `@sveltejs/adapter-cloudflare` | `@sveltejs/kit` and the adapter | `vite build` | A Workers project: `.svelte-kit/cloudflare/_worker.js` and the files beside it |
212| Nuxt | `nuxt` | `npm run generate` if there is one, else `nuxi generate` | `.output/public` |
213| React Router in SPA mode | `@react-router/dev`, and `ssr: false` in `react-router.config` | `react-router build` | `build/client` |
214| Remix in SPA mode | `@remix-run/dev`, and `ssr: false` in `vite.config` | `remix vite:build` | `build/client` |
215| Gatsby | `gatsby` | `gatsby build` | `public` |
216| Docusaurus | `@docusaurus/core` | `docusaurus build` | `build` |
217| Eleventy | `@11ty/eleventy` | `eleventy` | `_site`, or `dir.output` |
218| Angular | `@angular/core` and `angular.json` | `ng build` | `dist/<project>/browser`, from `angular.json` |
219| Create React App | `react-scripts` | `react-scripts build` | `build` |
220| Hugo | `hugo.toml`, `hugo.yaml` or `hugo.json`; or a Hugo `config.toml` and no `package.json` | `hugo --minify` | `public`, or `publishDir` |
221| Jekyll | `_config.yml` and a `Gemfile` | `bundle install`, then `bundle exec jekyll build` | `_site`, or `destination` |
222| mdBook | `book.toml` | `mdbook build` | `book`, or `build.build-dir` |
223
224- **Frameworks that need a server run as containers.** A Next.js app
225 without `output: 'export'`, an Astro site with `@astrojs/node`, a
226 SvelteKit app with `adapter-node`, and a React Router or Remix app that
227 renders on the server are built from their `Dockerfile`, or one g1t
228 writes ([Docker deployments](#docker-deployments)). A static export or
229 the framework's Cloudflare adapter costs less, since it runs nothing
230 while no one visits; the build's log says so. One g1t cannot run as a
231 server, such as SvelteKit with `adapter-auto`, fails before anything is
232 installed, saying what to change or to add a `Dockerfile`. An **Output
233 directory** of your own, or **Build as** Static site, builds it as a
234 static site instead.
235- **A framework's Worker.** Astro's and SvelteKit's Cloudflare adapters,
236 OpenNext, and `@cloudflare/vite-plugin` with a Wrangler config build a
237 Worker. g1t runs the build, then bundles what it wrote with
238 `wrangler deploy --dry-run`, reading the config the build wrote
239 (`.wrangler/deploy/config.json`) when there is one. A `_worker.js`,
240 `_routes.json` or `.assetsignore` at the top of the static files, and
241 the names `.assetsignore` lists, are never served.
242- **Hugo and mdBook** use the `hugo` or `mdbook` on the machine's `PATH`.
243 Without one, the build fetches Hugo 0.166.0 (the extended edition, with
244 npm) or builds mdBook 0.5.4 with cargo, which takes a minute or two.
245 Jekyll's gems are installed into `vendor/bundle` in the project.
246
247A project that is none of these is served from the first of these that
248exists after the build: `dist`, `build`, `out`, `public`, `_site`,
249`.output/public`. Set **Output directory** to choose another.
250
251### Static sites
252
253- A site without a `404.html` is treated as a single-page app: an address
254 that matches no file serves `index.html`. With a `404.html`, that page
255 is served instead, with status 404.
256- `_headers` and `_redirects` files in the output are honored, in
257 [Cloudflare's format](https://developers.cloudflare.com/workers/static-assets/headers/).
258- Up to 20,000 files, and 25 MiB per file: Cloudflare's limits.
259
260### Workers projects
261
262- Your Worker's `fetch` handler runs as written, and its static assets
263 are served under the binding name your config gives them. Cron triggers
264 (`triggers.crons`) are not scheduled, so a `scheduled` handler never
265 runs; the deployment says so in its warnings.
266- Each request your Worker answers may use up to 50 ms of CPU time
267 (waiting on the network does not count) and make up to 50 requests of
268 its own (`fetch` calls and the like). A request that goes over either
269 is stopped and answered with a 503 page that says so. Static assets are
270 served without running your Worker, and count toward neither.
271- `vars` are deployed as plain-text bindings (or JSON, for objects). Rows
272 of the project's [secrets and variables](/guides/secrets-and-variables/)
273 available to Deployments are bound too, and replace a `var` of the same
274 name: secrets as secret bindings.
275- **Not provisioned yet:** D1, KV, R2, Durable Objects, Queues, service
276 bindings, Vectorize, Hyperdrive, Workers AI and Workflows. A project that
277 declares any of them still deploys, without them, and its deployment
278 lists each one it left out. Code that needs them should check that the
279 binding is there.
280
281### Node.js servers as Workers
282
283A Node.js server with no `Dockerfile` runs as a Worker, not a container:
284an Express, Koa, Hono, Polka, Restify or hapi app, or one on `node:http`
285alone. Workers run Node.js HTTP servers with the `nodejs_compat`
286compatibility flag, through
287[`httpServerHandler`](https://developers.cloudflare.com/workers/runtime-apis/nodejs/http/)
288from `cloudflare:node`, so the app's code runs as written. g1t knows a
289Node.js server by a server package in `package.json`, or by a `start`
290script with no `build` script or `index.html` beside it.
291
2921. **It finds the file that starts the app**: the file its `start` script
293 runs with `node`, `tsx` or `ts-node` (`node server.js`,
294 `NODE_ENV=production node dist/index.js`), else `main` in
295 `package.json`, else `server.js`, `index.js`, `app.js` or the like.
2962. **It installs and builds** as for any project: the lockfile's install,
297 then `npm run build` when there is a `build` script (a TypeScript app
298 compiled to `dist`, say).
2993. **It writes a Worker entry** that imports that file, keeps the server
300 it starts, and answers each request with `httpServerHandler`. An app
301 whose default export has a `fetch` method (a Hono app) is answered by
302 that `fetch` directly. `listen` may name any port and address: on
303 Workers the port only names the server. `PORT` is `8080` unless the
304 project's variables set it. The entry is printed whole in the build's
305 log.
3064. **It bundles the Worker** with `wrangler deploy --dry-run`, with the
307 compatibility date `2026-09-26` and `nodejs_compat`.
3085. **It starts the Worker once** with `wrangler dev`, with the project's
309 [variables and secrets](#secrets-and-variables), and asks it for `/`. A
310 Worker that fails as it starts (a top-level `await` that never settles,
311 a module that cannot load, a server that never calls `listen`) is not
312 deployed.
313
314A folder the app serves with `app.use(express.static("public"))`, with
315no path before it, is uploaded as the Worker's static files: they are
316served without running the app, and count toward none of its requests or
317CPU time.
318
319What is known not to run as a Worker is found before anything is built,
320and the app runs as a container instead, from a Dockerfile g1t writes,
321with the reason in the build's log:
322
323| The app | Why |
324| --- | --- |
325| Depends on a native addon: `better-sqlite3`, `sqlite3`, `bcrypt`, `sharp`, `canvas`, `argon2`, `puppeteer`, `playwright`, `node-pty`, `ssh2` and others | A Worker cannot load native code. |
326| Is a Fastify, NestJS or Sails app | They start on timers or read their modules from disk as they start, which a Worker cannot do before its first request. |
327| Accepts WebSocket connections with `socket.io`, `express-ws`, `WebSocketServer` or an `upgrade` handler | Workers do not support Node's `upgrade` event. |
328| Uses `child_process`, `cluster` or `worker_threads` | A Worker cannot start processes or threads. |
329| Reads or writes files while it runs: `readFile`, `writeFile`, `createReadStream`, `sendFile`, `res.render` and a view engine, `multer`'s disk storage | A Worker has no disk with the project's files. |
330| Serves files from disk under a path (`app.use("/static", express.static("public"))`), from a folder that does not exist, or from more than one folder | g1t can serve one folder, at the top, for it. |
331| Has a `start` script g1t cannot read (`nest start`, `pm2-runtime …`) | g1t cannot tell which file starts it. |
332
333g1t looks for these in the app's own JavaScript and TypeScript, not in
334`node_modules`, tests, `scripts`, `public` or a build's output. A Worker
335that does not bundle, or does not start, also runs as a container, saying
336"Could not run as a Worker: …; deploying as a container." With a
337`Dockerfile` of its own, a Node.js server is built from that `Dockerfile`
338as a container. To run one as a Worker whatever g1t finds, set **Build
339as** to **Workers**; to run it as a container, **Container**.
340
341### Docker deployments
342
343Any app that runs in a container deploys as one, whatever its framework or
344age: give it a `Dockerfile`, or let g1t write one. It runs on
345[Cloudflare Containers](https://developers.cloudflare.com/containers/)
346behind the app's Worker, at the same addresses, custom domains and
347previews as any other app.
348
349A container is the fallback, for what cannot be served as a site or run
350as a Worker ([What a project is built as](#what-a-project-is-built-as)):
351a Python, Go, Ruby, PHP or Rust server, a Node.js server with a
352`Dockerfile` of its own or one that cannot run as a Worker, a framework
353set up to need a server, or a `Dockerfile` with nothing else to serve.
354There is nothing to set for those. A site or a Node.js server is built
355without its `Dockerfile`; to deploy its container instead, set **Build
356as** to **Container** under **Settings → Deployments**.
357
358The build's log says which it chose and why, near its top: for example,
359"Built as a static site; Dockerfile is not used (a container is only the
360fallback; choose **Build as**: Container to use it)", or "Could not run as
361a Worker: it depends on `better-sqlite3`, which builds native code a
362Worker cannot load; deploying as a container."
363
364g1t writes a Dockerfile for these, when there is none:
365
366| The project has | Runs with |
367| --- | --- |
368| `package.json` with a `start` script, or a server package (`express`, `fastify`, `koa`, `@nestjs/core`, `@hono/node-server` and others) and a main file | `npm start`, or `node <main>`, on `node:22-slim` (or the major release in `.nvmrc` or `.node-version`), after `npm run build` when there is one |
369| Next.js, Astro with `@astrojs/node`, SvelteKit with `adapter-node`, React Router or Remix | `next start` (or `node .next/standalone/server.js`, with its static files beside it, for `output: 'standalone'`), `node ./dist/server/entry.mjs`, `node build`, `react-router-serve` or `remix-serve`, after the build |
370| `requirements.txt`, `pyproject.toml` or `Pipfile`, and `manage.py` (Django) | `gunicorn <project>.wsgi` on `python:3.12-slim` |
371| The same, with Flask and an `app.py`, `main.py`, `server.py`, `wsgi.py` or `asgi.py` | `gunicorn <module>:app` |
372| The same, with FastAPI or Starlette | `uvicorn <module>:app` |
373| The same, with neither | `python <module>.py` |
374| `go.mod` and one `package main`, at the top or in one folder of `cmd/` | The program, built with `golang:1.25` into an image with nothing else |
375| A `Gemfile` with Rails | `rails server`, after `assets:precompile`, on `ruby:3.3-slim` |
376| A `Gemfile` and a `config.ru` | `rackup` |
377| `composer.json`, and `artisan` with `public/index.php` (Laravel), or an `index.php` | Apache with PHP 8.3, after `composer install` |
378| `Cargo.toml` with one program and `axum`, `actix-web`, `rocket`, `warp`, `poem`, `salvo` or `tide` | The program, built with `rust:1` into `debian:bookworm-slim` |
379
380The Dockerfile g1t writes is printed whole in the build's log. To change
381how the app is built or run, commit it as `Dockerfile` and edit it. It
382leaves `.git`, `node_modules`, `.venv` and `__pycache__` out of the image.
383
384What a container gets, and what it must do:
385
386- **Listen on `0.0.0.0` at `PORT`.** `PORT` is the port its Dockerfile's
387 first `EXPOSE` names (in its last stage), else the `PORT` it sets with
388 `ENV`, else `8080`. A Dockerfile g1t writes sets `HOST` and `HOSTNAME`
389 to `0.0.0.0` too. The first request
390 waits up to 90 seconds for it to answer there; one that never does gets
391 a 503 page saying so.
392- **The project's [secrets and variables](#secrets-and-variables)** are
393 its environment variables, set by g1t when it starts, never baked into
394 the image. The image's build gets none of them: a step that needs one
395 belongs in the app's start.
396- **One instance, which sleeps.** It starts on the first request, and
397 stops after five minutes without one; asleep, it costs nothing, and the
398 next request starts it again in a few seconds. Its disk starts from the
399 image each time: keep data in a database or a bucket.
400- **Its size** is **Container size** under **Settings → Deployments**:
401 `lite` (the default) to `standard-4`, as [What it costs](#what-it-costs)
402 lists. The image may be as large as the size's disk, 2 GB for `lite`;
403 a larger image fails the build, saying how large it was. An app keeps
404 the image it runs and the one before it; older ones are deleted once a
405 deploy is live, and all of them when the app is taken down.
406- **Built for `linux/amd64`**, with Docker, in the build's sandbox (or the
407 workflow job's Docker, for [`g1t-runner deploy`](#deploying-from-a-workflow)),
408 with network access as any build has. **Build command** is not used:
409 the Dockerfile builds it.
410
411What Deployments cannot run yet is on
412[What g1t can't do yet](/about/limitations/#deployments).
413
414## Previews of branches
415
416A preview is built when a pull request is opened, when it is marked ready,
417and on every push to it. Pull requests from forks are built from the
418fork.
419
420The pull request shows the deployment as a check named **g1t / deploy**:
421
422| State | Means |
423| --- | --- |
424| Pending, "Building" | The build is running. The link opens its log. |
425| Passed, "Preview is live" | The link opens the preview. |
426| Failed, "Deployment failed" | The link opens the build log and the reason. |
427
428A newer push replaces a build that is still running for the same pull
429request. Previews are marked `noindex`, so search engines leave them alone.
430
431The **Deployments** page lists the same failure of one app, repeated, as a
432single row with how many times it happened and when it last did. The row
433opens the newest of them.
434
435## Production
436
437Each push to the default branch, which is each merge on a protected
438branch, builds and replaces production. The **Deployments** page shows the
439live address, the commit it runs and when it went up.
440
441### The production screenshot
442
443When production goes live, g1t takes a screenshot of its home page, as a
4441280 by 800 browser window sees it, and shows it on the project's
445[overview](/guides/projects/#the-overview). Choosing it opens the site.
446
447- One screenshot is taken per production deploy. g1t waits up to 12
448 seconds for the page to settle, then takes it as it is.
449- The screenshot is of the app's `g1t.page` address. A custom domain
450 serves the same app, so it looks the same.
451- Until it is ready, or if it could not be taken, the overview shows the
452 project's uploaded image, if it has one, or else a plain frame with the
453 address. A failed screenshot is tried again when the overview is next
454 opened, at most every five minutes.
455- Only people with a role on the project's repository see it, like the
456 rest of its deployments; anyone else who can see the project sees its
457 uploaded image or the frame.
458- With **Use the uploaded image instead of a screenshot** on, the
459 uploaded image shows instead.
460- Screenshots are not charged.
461
462An app that deploys itself gets a screenshot of its production address
463too, and any project can show an uploaded image instead. See
464[production's picture](/guides/projects/#productions-picture).
465
466## Deploying from a workflow
467
468A [workflow](/guides/actions/) job can build and deploy the project
469itself, with g1t's own action, `g1t/deploy`. Use it when the build needs
470steps of yours first (generated code, a test that must pass, a tool the
471default build lacks), or to deploy from a branch without a pull request.
472The app, its address, its checks and its custom domains are the same as
473for the builds g1t starts by itself.
474
475```yaml
476# .g1t/workflows/deploy.yml
477name: Deploy
478
479on:
480 push:
481 branches: [main]
482 pull_request:
483
484permissions:
485 contents: read
486 deployments: write
487
488jobs:
489 deploy:
490 runs-on: ubuntu-latest
491 steps:
492 - uses: actions/checkout@v5
493 - uses: actions/setup-node@v4
494 with:
495 node-version: 24
496 - run: npm ci
497 - run: npm test
498 - id: deploy
499 uses: g1t/deploy@v1
500 with:
501 build-command: npm run build
502 - run: echo "Live at ${{ steps.deploy.outputs.url }}"
503```
504
5051. Turn Deployments on for the project, as in
506 [Turn on deployments](#turn-on-deployments).
5072. Add a workflow like the one above. The job needs `deployments: write`
508 (or `pages: write`) in its `permissions:`.
5093. Push. The run on `main` deploys production; a pull request's run
510 deploys its preview.
511
512What the run builds:
513
514| The run | Builds |
515| --- | --- |
516| On the project's production branch (a push, `workflow_dispatch` or `schedule` there) | Production |
517| For a pull request (`pull_request`, `pull_request_target`) | That pull request's preview, at its branch's address, or `pr-<number>` for one from a fork |
518| On another branch | That branch's preview |
519| For a tag | Nothing: the step fails and says to run the workflow on a branch |
520
521`g1t/deploy` takes these inputs:
522
523| Input | Default | |
524| --- | --- | --- |
525| `project` | The repository's one project with Deployments on | The project's slug. Needed when the repository has several. |
526| `root-dir` | The project's **Root directory** | Where the project is in the checkout. |
527| `build-command` | The project's **Build command**, else what the build finds | What builds it, run after the install. |
528| `output-dir` | The project's **Output directory**, else the first folder found | The folder a static build writes. |
529| `sha` | The run's commit | The commit the `g1t / deploy` check goes on. |
530| `token` | `${{ github.token }}` | The job's own token. No other token can open a build. |
531
532Its outputs are `url` (the app's address), `deployment-id` (`dpl_…`) and
533`kind` (`production` or `preview`).
534
535The action calls
536[`POST /repos/{owner}/{repo}/deployments/builds`](/reference/api/deployments/start-deployment-build/),
537which checks the plan and the spend limit as any build does, sets the
538commit's `g1t / deploy` check to pending, and answers with a token for
539that build alone and the project's
540[variables and secrets](#secrets-and-variables) for production or
541previews. The action masks the token and every secret in the log, then
542runs `g1t-runner deploy` in the checkout, which installs, builds and
543uploads exactly as [What deploys](#what-deploys) says; its output is in
544the step's log and on the deployment's page. When the build fails, or
545g1t does not take what it built, the step fails and the deployment says
546why. When the job ends before the build has finished, because it was
547cancelled, failed in an earlier step or timed out, the deployment fails
548as the job ends, saying how it ended, rather than staying **Building**.
549
550- **Secrets.** A pull request from a fork or from someone without the
551 Write role, and a run that had to be
552 [approved](/guides/actions/#pull-requests), build without
553 the project's secrets, as previews of them always have. Their token is
554 read-only, so they cannot open a build at all unless the workflow runs
555 on `pull_request_target`.
556- **Cost.** The build runs in the job, so its time is the job's
557 [Actions time](/guides/usage-and-billing/), free on a
558 [self-hosted runner](/guides/self-hosted-runners/), and is not charged
559 again as build seconds. Requests, CPU time and custom domains are
560 charged as for any app.
561- **g1t's own builds still run.** A push to the production branch and a
562 pull request's push still start g1t's build too. Whichever starts
563 later replaces the other, so the app ends up at the newest commit
564 either way.
565- **On your own machines**, the action uses the `g1t-runner` on `PATH`, or
566 downloads the newest release and checks it against its `SHA256SUMS`. It
567 runs on Linux and macOS, with `curl` and `jq` or `python3`.
568
569`g1t/detect` says what a build of the checkout would find, without
570building anything: its outputs are `kind` (`workers`, `static`, `html`, `container` or
571`none`), `install`, `build`, `output-dir`, `framework` (such as
572`astro`, `nextjs` or `node`, empty when there is none), `dockerfile` and
573`port` for a container, and `entry` for a Node.js server run as a Worker. A framework set up to
574need a server is `none`, and the step's log says what to change. Use
575them to cache, or to fail early:
576
577```yaml
578- id: detect
579 uses: g1t/detect@v1
580- if: steps.detect.outputs.kind == 'none'
581 run: |
582 echo "::error::Nothing here would be served: add an index.html or a build script."
583 exit 1
584```
585
586### Build with a workflow
587
588Turn on **Build with a workflow** under **Settings → Deployments** and the
589project's builds run as a workflow g1t manages for it, instead of as
590builds Deployments starts by itself. Nothing else changes: the same
591previews and production, at the same addresses, with the same
592**g1t / deploy** check, which is now the check of the workflow's job.
593
594- The workflow is listed on the repository's **Actions** page, named
595 `g1t`, with its runs, logs and re-runs like any other. It is not a file
596 in the repository: it is made from the project's settings, and follows
597 them when they change.
598- It calls g1t's reusable deploy workflow,
599 `g1t/workflows/.g1t/workflows/deploy.yml@v1`, which checks out the
600 commit and runs [`g1t/deploy`](#deploying-from-a-workflow). As g1t
601 improves how projects are built, that workflow improves for every
602 project that uses it.
603- A pull request from a fork runs it with a read-only token, as on
604 GitHub, and still gets its preview, without the project's build
605 secrets.
606- To change it, commit a workflow named `g1t` to `.g1t/workflows/`: that
607 file runs in its place. Start from the one the **Actions** page shows,
608 or call the reusable workflow yourself:
609
610```yaml
611# .g1t/workflows/g1t.yml
612name: g1t
613
614on:
615 push:
616 branches: [main]
617 pull_request:
618
619permissions:
620 contents: read
621 deployments: write
622
623jobs:
624 g1t:
625 name: g1t
626 uses: g1t/workflows/.g1t/workflows/deploy.yml@v1
627 with:
628 project: web
629 secrets: inherit
630```
631
632Its jobs are [workflow jobs](/guides/actions/#machine-sizes): on g1t's
633machines they are charged as Actions time, at cost plus 20% like every
634build, and on a [self-hosted runner](/guides/self-hosted-runners/) they
635cost nothing.
636
637## Custom domains
638
639Production can be served at a domain of your own, such as `example.com` or
640`www.example.com`, as well as at its address on g1t.page. A domain belongs
641to the project, not to a build: every redeploy is served on it with nothing
642to change.
643
644### Add a domain
645
6461. Open the project's **Settings → Domains**,
647 `g1t.sh/<workspace>/<repo>/settings/domains`.
6482. Enter the domain and choose **Add domain**. Leave **Also add the www
649 (or apex) twin** checked to add both `example.com` and
650 `www.example.com` at once: the one you typed serves the app, and the
651 other redirects to it with a `308`, path and query kept.
6523. Add the DNS records the page lists, at whoever manages the domain's
653 DNS. Each name and value has a copy button.
6544. Wait. The page follows the domain from **Waiting for DNS** to
655 **Issuing certificate** to **Active** by itself; **Check now** asks
656 again at once.
657
658Custom domains need the workspace's g1t plan, and are $0.12 a month each
659(their cost plus 20%), as many as you like. Adding or removing one needs
660the Admin [role](/guides/access-and-roles/) on the project's repository.
661
662### A subdomain, such as www
663
664Add one record:
665
666| Type | Name | Target |
667| --- | --- | --- |
668| `CNAME` | `www` | `domains.g1t.page` |
669
670The same goes for any subdomain, such as `app.example.com` (name `app`).
671
672### The apex, such as example.com
673
674DNS does not allow a plain `CNAME` at the apex of a domain, so use your
675provider's flattened form of one, pointed at `domains.g1t.page`:
676
677| DNS provider | Record |
678| --- | --- |
679| Cloudflare DNS | `CNAME` at `@` (Cloudflare flattens it) |
680| Amazon Route 53 | Not supported by alias records to other zones; use the www pairing below |
681| DNSimple, NS1, Namecheap, Porkbun, Gandi | `ALIAS` at `@` |
682| DNS Made Easy, Constellix | `ANAME` at `@` |
683
684If your provider has no flattened `CNAME`, `ALIAS` or `ANAME`, add
685`www.example.com` on g1t instead, and set up your provider's forwarding (or
686any redirect) from `example.com` to `www.example.com`.
687
688### Verification and certificates
689
690Pointing the domain at `domains.g1t.page` is what proves it is yours: once
691the record is seen, the certificate authority checks the domain over HTTP
692and issues a certificate, renewed by itself before it expires. Visitors are
693always served over HTTPS, TLS 1.2 or newer.
694
695The page may also list a `TXT` record named `_cf-custom-hostname.<domain>`.
696It proves ownership before traffic moves, which is useful when the domain
697is serving a site elsewhere today: add the `TXT` first, wait for
698**Issuing certificate** or **Active**, then change the `CNAME`. Any other
699`TXT` records listed are for the certificate and are needed as shown.
700
701DNS changes can take from a few minutes to an hour to be seen. A domain
702that stays at **Waiting for DNS** usually has a record with a typo, a
703leftover `A` or `AAAA` record beside the new one, or (on Cloudflare DNS) a
704`CAA` record that does not allow the certificate's authority.
705
706### Remove a domain
707
708Choose **Remove** beside it. It stops serving the project at once, and any
709domain redirecting to it goes too. Your DNS records are left as they are.
710
711## When apps come down
712
713Nothing keeps running unasked. An app comes down, and stops costing
714anything, when:
715
716| | |
717| --- | --- |
718| Its pull request is merged or closed | That preview, at once. |
719| 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. |
720| You choose **Take down** on the Deployments page | That app, at once. |
721| You turn off previews or production | All of that kind, at once. |
722| You choose **Turn off deployments** | Every app of the project, at once, and no more builds. |
723| The workspace's plan ends or its payment fails | Every app of the workspace, at the next sweep. |
724| 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. |
725
726A preview that came down comes back with the next push to its pull
727request, or **Redeploy** on the Deployments page.
728
729## Who can do what
730
731What you can do with a project's deployments follows your
732[role](/guides/access-and-roles/) on its repository:
733
734| | Needs |
735| --- | --- |
736| See its deployments, their build logs, previews and domains | Read |
737| **Redeploy**, take an app down | Write |
738| Turn deployments on or off, change their settings | Admin |
739| Add, verify and remove custom domains | Admin |
740
741A public repository's deployments, and their build logs, can be seen by
742anyone, signed in or not, the same as its code. Its settings and domains
743still need Admin. A private repository's deployments are seen only by
744people with a role on it.
745
746## Settings
747
748Under the project's **Settings → Deployments**,
749`g1t.sh/<workspace>/<repo>/settings/deployments`:
750
751| Setting | Default | |
752| --- | --- | --- |
753| Production | On | Deploy the default branch on every push. |
754| Previews | On | A preview for every branch with an open pull request. |
755| Build as | Auto | **Auto** (found from its files, a site or a Worker before a container), **Static site**, **Workers** or **Container**; see [What a project is built as](#what-a-project-is-built-as). |
756| Dockerfile | `Dockerfile` or `Containerfile` | For a container: its Dockerfile's path, inside the project. |
757| Container size | `lite` | For a container: the instance it runs at, with its price an hour awake. |
758| Build command | The project's own | Runs instead of `npm run build`, or before bundling a Workers project. Not used for a container. |
759| Output directory | Found by itself | What a static site serves. |
760| Idle days | 7 | 1 to 90. A preview no one visits this long comes down. |
761| Build with a workflow | Off | Build through the deploy workflow g1t manages for the project; see [build with a workflow](#build-with-a-workflow). |
762
763The same settings are read and changed with
764[`get_deployment_settings`](/reference/api/deployments/get-deployment-settings/) and
765[`update_deployment_settings`](/reference/api/deployments/update-deployment-settings/)
766(`GET` and `PATCH /workspaces/{workspace}/projects/{project}/deployments/settings`,
767the MCP `workflow` tool's actions of those names, and `g1t deploy settings`), as
768`production`, `previews`, `build_as`, `dockerfile`, `instance_type`,
769`build_command`, `output_dir`, `idle_days` and `via_workflow`.
770
771## Logs, traces and errors
772
773Every request an app answers shows under the project's
774[Observability](/guides/observability/) within seconds: its status,
775duration, CPU time and the lines it wrote, with its exceptions grouped into
776errors and its numbers by deployment. Nothing to set up. Each request
777reaches the app with an `x-g1t-request-id` header, its id there, and a
778`traceparent`, and the app is deployed with OpenTelemetry's
779`OTEL_EXPORTER_OTLP_*` variables set, so an OpenTelemetry SDK in it sends
780its spans to the project without configuration.
781
782## Secrets and variables
783
784Builds and running apps read the project's
785[secrets and variables](/guides/secrets-and-variables/) that are
786available to Deployments, and the workspace's that reach it:
787
788| | Reads |
789| --- | --- |
790| A production build, and production | Each key's Production row, else its row for all environments. |
791| A preview build, and the preview | Each key's Preview row, else its row for all environments. |
792
793The build gets them as environment variables, with secrets hidden in its
794log. The running app gets them as bindings, `env.KEY`, put in place by g1t
795rather than the build; a container gets them as its environment variables
796when it starts, and its image's build gets none. Secrets reach a preview only when the pull
797request's author has Write or higher on the repository, whether a member
798or an [outside collaborator](/guides/access-and-roles/#outside-collaborators),
799or it is g1t working on its own. A preview of anyone else's pull request, such as
800one from a fork or by someone with Read or Triage, is built and runs with
801variables only, no secrets.
802
803For example, a `STRIPE_KEY` secret with a Production row holding the live
804key and a Preview row holding the test key gives every preview the test
805key.
806
807## What it costs
808
809Deployments come with the
810[g1t plan](/guides/usage-and-billing/#the-g1t-plan), $21 a month per
811workspace with $10 of usage included. There are **no quotas**: as many
812projects and previews as you like, and no count of builds, requests or
813domains ever stops or pauses an app. What deployments cost g1t is metered
814from the first unit, at Cloudflare's price plus 20%, and paid from the
815plan's $10 of included usage first; past it, it is charged up to your
816[spend limit](/guides/usage-and-billing/#limits).
817
818| | Costs g1t | You pay |
819| --- | --- | --- |
820| Projects, previews and the apps behind them | Next to nothing | Not charged |
821| Builds g1t runs, by the second, whether they succeed or fail (a [workflow's build](#deploying-from-a-workflow) is its job's Actions time instead) | About $0.001 a minute | About $0.0012 a minute |
822| Requests, to all of the workspace's apps | $0.30 a million | $0.36 a million |
823| CPU time your code spends computing (waiting on the network is not counted) | $0.02 a million ms | $0.024 a million ms |
824| 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 |
825| A container, while it is awake, by the minute (see the sizes below) | From $0.0129 an hour (`lite`) | From $0.0155 an hour |
826| Data a container sends | $0.05 a GB, at most | $0.06 a GB |
827
828A container's price is its size's memory, disk and vCPU while it is awake,
829with the Durable Object in front of it, at Cloudflare's
830[Containers prices](https://developers.cloudflare.com/containers/platform/pricing/)
831plus 20%. Cloudflare bills CPU only as it is used; g1t charges every vCPU
832as busy while the container is awake, so a container that mostly waits
833costs g1t less than it is charged. Data sent is charged at the most a GB
834costs anywhere ($0.05; $0.025 in North America and Europe), since g1t
835cannot tell where it went. Each minute it is awake is charged from its
836start, so a container woken by one request, and stopped five minutes after
837it, is charged five minutes.
838
839| Size | vCPU | Memory | Disk, and the largest image | An hour awake | Awake all month (730 hours) |
840| --- | --- | --- | --- | --- | --- |
841| `lite` | 1/16 | 256 MiB | 2 GB | $0.0155 | $11.28 |
842| `basic` | 1/4 | 1 GiB | 4 GB | $0.0404 | $29.46 |
843| `standard-1` | 1/2 | 4 GiB | 8 GB | $0.0956 | $69.77 |
844| `standard-2` | 1 | 6 GiB | 12 GB | $0.1616 | $117.95 |
845| `standard-3` | 2 | 8 GiB | 16 GB | $0.2708 | $197.68 |
846| `standard-4` | 4 | 12 GiB | 20 GB | $0.4880 | $356.24 |
847
848**Settings → Deployments** shows these with the price book's prices of the
849day. A container that is visited all day never sleeps, and costs the last
850column; one visited for an hour a day costs about a thirtieth of it.
851
852Builds are charged when they finish. Requests, CPU time, custom domains
853and container time are counted through the month, so your limit and
854**Billing** follow them as they happen, and charged once, on the first
855sweep after the month ends, as one line: *Deployments in 2026-10:
8561,240,000 requests, 3,100,000 CPU ms, 2 custom domains, 41.5 container
857hours and 2.3 GB sent*.
858
859A static site, or plain HTML, runs no code when it is visited: its files
860are served as they are, so serving it adds no requests or CPU time. Its
861builds are metered. A Workers project's code runs on every request, so its
862requests and CPU time are metered too. A container's Worker is metered the
863same way, and its container by the minute while it is awake.
864
865An app that no one visits costs nothing. Previews still come down when
866their pull request closes and after their idle days, to keep the list of
867what is up short.
868
869The only things that stop a workspace's apps are its own spend limit
870(apps pause with a notice, and come back once there is room; a paused
871container is stopped and starts no more) and the plan ending (they come
872down).
873
874Deployments used to be a $5 plan of their own. A Deployments subscription
875bought before keeps working until its current period ends, and is not
876renewed.
877
878### Seeing what you use
879
880- **Usage** shows **Deployments** with *Builds*
881 (build minutes and their cost), *Requests & CPU* and *Custom domains*,
882 in dollars, beside the rest of the workspace's usage. Requests and CPU
883 time are counted from Cloudflare's analytics every 10 minutes.
884- The **statement** on Billing lists every build (*Building acme/web to
885 production (48 s), paid by your plan's included usage*) and every
886 month's requests, CPU time and custom domains.
887- Each build's page shows how long it ran.
888
889## Turn it off
890
891- **For a project:** **Turn off deployments** under its **Settings →
892 Deployments**. Every app comes down at once. Turning it on again
893 rebuilds production.
894- **For the workspace:** when an owner ends the g1t plan on Billing,
895 deployments keep working until the date shown; then every app comes
896 down and nothing more is charged.
897
898Usage from the month that is under way is still charged once it ends.
899
900## How it works
901
9021. A pull request opens, or someone pushes. The deployments service hears
903 of it, checks that the workspace's plan is on, and reserves what the
904 build may cost (30 minutes of sandbox time) with billing. If billing
905 refuses (no paid plan, the spend limit reached, compute paused), the
906 build does not start: the deployment's status is **Skipped** with the
907 reason and what to do, and **Redeploy** shows the same reason.
9082. It starts a build in a sandbox of its own, the same machines that run
909 [GitHub Actions](/guides/actions/). The sandbox checks out
910 the commit with a read-only token that expires in 30 minutes. It
911 reaches the internet, as
912 [workflow jobs](/guides/actions/#what-a-job-can-reach) do, and it
913 stops at 30 minutes.
914 When it stops, what it cost is settled against what was reserved.
9153. The sandbox builds, then lists its files. g1t opens an upload with
916 Cloudflare for exactly those files and hands the sandbox a key that can
917 upload them and nothing else. Files Cloudflare already has are skipped.
9184. The sandbox sends the Worker's code to g1t, which puts the app in
919 g1t's [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/)
920 namespace, under the name in its address. A container build goes as
921 [below](#containers).
9225. A request to `*.g1t.page` reaches g1t's dispatcher, which runs the app
923 by the name in the hostname. Nothing else is looked up. A request to a
924 custom domain reaches the same dispatcher through Cloudflare for SaaS;
925 it looks the hostname up once, in a key-value store kept at the edge.
926
927Your code never holds a Cloudflare credential, and apps are served from
928`g1t.page`, not `g1t.sh`, so they share no cookies or origin with the site
929you sign in to.
930
931### Containers
932
9331. The build finds it is a container, and asks g1t where to push its
934 image (the build's `registry` step). g1t mints a login to Cloudflare's
935 registry, `registry.cloudflare.com`, that can **push only** and lasts
936 **15 minutes**, and names the app's own repository,
937 `<account>/g1t-<app>`, with the build's id as its tag. Asked before
938 anything is built, so a build that could not push is not run.
9392. The sandbox starts a Docker Engine of its own (as a workflow job's),
940 builds the image for `linux/amd64`, checks its size against the
941 container size's disk, pushes it with that login, and reports the
942 image by its digest.
9433. g1t runs only an image by digest in that app's repository, and asks
944 the registry, with a pull login of its own, that the digest is there.
945 It puts the app's Worker in the namespace: g1t's own code, never the
946 project's, with a Durable Object class that holds the container, the
947 project's secrets and variables, and a token for the app to report its
948 running time with. Then it creates the app's Container application
949 (at most one instance, at the project's size, attached to that class),
950 or rolls it out to the new image.
9514. A request reaches the app's Worker through the dispatcher, as any
952 app's does. The Worker hands it to the app's Durable Object, which
953 starts the container if it is asleep, with the environment, waits for
954 it to answer on its port, and passes the request to it. Every minute it
955 is awake, the object charges the next minute ahead and reports it to g1t
956 (`POST /deployments/apps/<app>/meter` on the API, with its token); five
957 minutes after the last request it stops the container.
9585. A preview's container comes down with its pull request, its
959 application and Worker deleted together.
960
961Why it is built this way, from Cloudflare's documentation and Wrangler's
962own code (October 2026):
963
964- **Workers for Platforms runs containers.** A user Worker in a dispatch
965 namespace may bind Containers, as a Durable Object
966 ([WfP bindings](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/configuration/bindings/)).
967 Wrangler's `deploy --dispatch-namespace` deploys containers by finding
968 the Durable Object namespace of the class in that dispatch namespace and
969 creating a Container application attached to it
970 (`POST /accounts/{id}/containers/applications`, with
971 `durable_objects.namespace_id`), then rolling out new images
972 (`…/applications/{id}/rollouts`). So each app is a Worker of its own,
973 with a class of its own, isolated as every app is, and the dispatcher
974 routes to it unchanged. A single host Worker of g1t's could not run every
975 app's image: a Durable Object starts only the images its Worker's
976 configuration names.
977- **The default scheduling policy.** It takes one image and size for the
978 whole application, set through the API, which suits one app per
979 application; the `durable_object` policy is in beta, and needs the
980 image map uploaded with the Worker.
981- **Registry logins.** Cloudflare mints registry credentials with
982 `POST /accounts/{id}/containers/registries/registry.cloudflare.com/credentials`
983 and `{ "permissions": ["push"], "expiration_minutes": 15 }`, as Wrangler
984 does for its own pushes. They cannot be held to one repository, so g1t
985 holds the build to its app's: it names the repository, runs nothing but a
986 digest in it, and never gives a build a login that can pull, so no build
987 can read another app's image. g1t's own Cloudflare token never reaches
988 the sandbox.
989- **Sizes and limits.** Containers' instance types are `lite` (1/16 vCPU,
990 256 MiB, 2 GB) through `standard-4` (4 vCPU, 12 GiB, 20 GB); an image
991 may be as large as its instance's disk, and an account holds 50 GB of
992 images ([limits](https://developers.cloudflare.com/containers/platform/limits/)).
993- **Scale to zero.** Containers are billed every 10 ms while running, and
994 stop being billed once asleep. The Durable Object stops its container
995 itself after five minutes without a request, rather than leaving it to
996 the inactivity timeout, since the object's own minute alarm, which
997 meters it, would otherwise keep it awake.
998
999## Troubleshooting
1000
1001| You see | Do |
1002| --- | --- |
1003| "Deployments come with the g1t plan … and `<workspace>` does not have it" | An owner starts the g1t plan under Billing. |
1004| 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. |
1005| "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). |
1006| "found nothing to serve" | Add a `build` script, an `index.html`, a Workers config or a `Dockerfile`; or set **Output directory**. |
1007| "Docker deployments are switched off on this g1t" | The project is a container, and this g1t (a self-hosted one) runs none. Set **Build as** to Static site or Workers if it can be built as one. See [Docker deployments](#docker-deployments). |
1008| "**Build as** is Container, but this project has no Dockerfile" | Add a `Dockerfile`, or set **Dockerfile** to its path. |
1009| "Could not run as a Worker: …; deploying as a container" | The Node.js server cannot run as a Worker, for the reason given; it deploys as a container. See [Node.js servers as Workers](#nodejs-servers-as-workers) for what to change, or set **Build as** to **Container** to skip the attempt. |
1010| "Built as a … site; Dockerfile is not used" | A site is preferred to a container. Set **Build as** to **Container** to deploy the `Dockerfile` instead. |
1011| "The image is 2.4 GB, more than the 2.0 GB an app of its size (lite) can hold" | Make the image smaller (a slim base image, a multi-stage build), or choose a larger **Container size**. |
1012| A container app says "The app did not start" | It did not answer on its port within 90 seconds. Make it listen on `0.0.0.0` at `PORT`, or `EXPOSE` the port it uses in its Dockerfile. Its output is in the build's log only while it builds; run the image locally with `docker run -e PORT=8080 -p 8080:8080` to see it start. |
1013| "the output directory `x` does not exist after the build" | The build wrote elsewhere: check its log, then fix **Output directory**. |
1014| "`d1_databases` is not provisioned on g1t.page yet" | The app deployed without that binding. See [Workers projects](#workers-projects). |
1015| "`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). |
1016| 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**. |
1017| "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. |
1018| 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**. |
1019| A domain says "This domain is not set up" | It points at g1t, but no project has added it. Add it under **Settings → Domains**. |
1020| "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. |
1021| g1t/deploy says "The job's token needs `deployments: write`" | Add `deployments: write` to the workflow's or the job's `permissions:`. A pull request's run from outside gets a read-only token and cannot deploy. |
1022| g1t/deploy says "Name the project to build" | The repository has several projects with Deployments on: give `project:`. |
1023| g1t/deploy says "g1t.page deploys branches and pull requests" | The run is for a tag. Run the workflow on a branch, such as on a push to `main`. |
1024| "The workflow job was cancelled before the build finished" | The job that opened the build (`g1t/deploy`) was cancelled, or ended another way the message names, before `g1t-runner deploy` reported. Run the job again. |
1025| "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**. |
1026
1027## Running your own g1t
1028
1029Deployments need a Workers for Platforms namespace and a zone for apps:
1030
10311. Create the namespace: `npx wrangler dispatch-namespace create g1t-deployments`.
10322. Add a proxied wildcard DNS record (`*`, `AAAA`, `100::`) on the apps'
1033 zone, and set the zone in `services/pages/wrangler.jsonc`.
10343. Create an API token with **Workers Scripts: Edit** and **Account
1035 Analytics: Read**, and store it:
1036 `npx wrangler secret put CLOUDFLARE_API_TOKEN` in `services/deployments`.
10374. Deploy `services/deployments`, `services/pages`, and the runner.
1038
1039Docker deployments also need the token to have **Containers: Edit** and
1040a Workers Paid plan with Containers; `CONTAINERS` in
1041`services/deployments/wrangler.jsonc` is `on`, and `off` turns them off
1042for an account without Containers. See
1043[Docker deployments](/guides/deploy-to-cloudflare/#docker-deployments)
1044for what to check on a new account.
1045
1046Custom domains also need Cloudflare for SaaS on the apps' zone. Turn it on
1047under the zone's **SSL/TLS → Custom Hostnames**, give the token **SSL and
1048Certificates: Edit** on the zone, and run `scripts/setup-custom-domains.sh`:
1049it creates the `g1t-domains` KV namespace, the fallback origin's DNS record
1050(`domains`, `AAAA`, `100::`, proxied) and sets it as the fallback origin.
1051The dispatcher's `*/*` route on the zone, in `services/pages/wrangler.jsonc`,
1052is what brings custom domains' traffic to it.
1053
1054Without a card processor configured, every feature is on and nothing is
1055charged.