Merge branch 'worktree-agent-a0d1a64e2033a58ed' into claude/cool-newton-7nm8r9
| 41 | 41 | | `root-dir` | The project's setting | Where the project is in the checkout. | | |
| 42 | 42 | | `build-command` | The project's setting, else what the build finds | What builds it. | | |
| 43 | 43 | | `output-dir` | The project's setting, else the folder the framework writes, else the first of `dist`, `build`, `out`, `public`, `_site`, `.output/public` | The folder a static build writes. | | |
| 44 | + | | `build-as` | The project's setting, else `auto` | What it is built as: `auto`, `static`, `workers` or `docker`. A container is built with the `docker` on the job's `PATH`. | | |
| 45 | + | | `dockerfile` | The project's setting, else `Dockerfile` or `Containerfile` | The Dockerfile a container is built from, relative to the project. | | |
| 44 | 46 | | `sha` | The run's commit | The commit this is a build of, for its `g1t / deploy` check. | | |
| 45 | 47 | | `token` | `${{ github.token }}` | The job's own token. It needs `deployments: write`; any other token is refused. | | |
| 46 | 48 |
| 19 | 19 | description: The folder a static build writes. The project's own setting, else the folder the framework writes, else the first of dist, build, out, public, _site or .output/public. | |
| 20 | 20 | required: false | |
| 21 | 21 | default: "" | |
| 22 | + | build-as: | |
| 23 | + | description: What it is built as, auto, static, workers or docker. The project's own setting unless you say; auto finds it from the project's files. | |
| 24 | + | required: false | |
| 25 | + | default: "" | |
| 26 | + | dockerfile: | |
| 27 | + | description: The Dockerfile a container is built from, relative to the project. The project's setting, else Dockerfile or Containerfile. | |
| 28 | + | required: false | |
| 29 | + | default: "" | |
| 22 | 30 | sha: | |
| 23 | 31 | description: The commit this is a build of, for the commit's g1t / deploy check. The run's commit unless you say. | |
| 24 | 32 | required: false | |
| ⋯ | |||
| 49 | 57 | INPUT_ROOT_DIR: ${{ inputs.root-dir }} | |
| 50 | 58 | INPUT_BUILD_COMMAND: ${{ inputs.build-command }} | |
| 51 | 59 | INPUT_OUTPUT_DIR: ${{ inputs.output-dir }} | |
| 60 | + | INPUT_BUILD_AS: ${{ inputs.build-as }} | |
| 61 | + | INPUT_DOCKERFILE: ${{ inputs.dockerfile }} | |
| 52 | 62 | INPUT_SHA: ${{ inputs.sha }} | |
| 53 | 63 | INPUT_TOKEN: ${{ inputs.token }} | |
| 54 | 64 | run: bash "$GITHUB_ACTION_PATH/deploy.sh" | |
| 93 | 93 | export ROOT_DIR="$(pick "${INPUT_ROOT_DIR:-}" root_dir)" | |
| 94 | 94 | export BUILD_COMMAND="$(pick "${INPUT_BUILD_COMMAND:-}" build_command)" | |
| 95 | 95 | export OUTPUT_DIR="$(pick "${INPUT_OUTPUT_DIR:-}" output_dir)" | |
| 96 | + | export BUILD_AS="$(pick "${INPUT_BUILD_AS:-}" build_as)" | |
| 97 | + | export DOCKERFILE="$(pick "${INPUT_DOCKERFILE:-}" dockerfile)" | |
| 96 | 98 | export BUILD_ENV="$(field build_env <<<"${answer}")" | |
| 97 | 99 | export BUILD_SECRETS="$(field build_secrets <<<"${answer}")" | |
| 98 | 100 | unset GIT_REMOTE |
| 24 | 24 | | `root-dir` | The checkout | Where the project is in the checkout. | | |
| 25 | 25 | | `build-command` | None | A build command of your own, as `g1t/deploy` takes it. | | |
| 26 | 26 | | `output-dir` | None | An output folder of your own, as `g1t/deploy` takes it. | | |
| 27 | + | | `build-as` | `auto` | What it is built as: `auto`, `static`, `workers` or `docker`, as `g1t/deploy` takes it. | | |
| 28 | + | | `dockerfile` | None | A Dockerfile's path of your own, as `g1t/deploy` takes it. | | |
| 27 | 29 | ||
| 28 | 30 | ## Outputs | |
| 29 | 31 | ||
| 30 | 32 | | Output | | | |
| 31 | 33 | | --- | --- | | |
| 32 | − | | `kind` | `workers` (a `wrangler.jsonc`, `wrangler.json` or `wrangler.toml`, or a framework's Cloudflare adapter), `static` (a site a build writes), `html` (the files as they are) or `none` (nothing to serve, or a framework set up to need a server). | | |
| 33 | − | | `framework` | The framework it is built with, from the table below. Empty when there is none. | | |
| 34 | + | | `kind` | `workers` (a `wrangler.jsonc`, `wrangler.json` or `wrangler.toml`, or a framework's Cloudflare adapter), `static` (a site a build writes), `html` (the files as they are), `container` (a `Dockerfile`, a framework set up to need a server, or a server g1t writes a Dockerfile for) or `none` (nothing to serve). | | |
| 35 | + | | `framework` | The framework it is built with, from the table below; for a container without one, `dockerfile`, or the server g1t writes a Dockerfile for (`node`, `django`, `flask`, `fastapi`, `python`, `go`, `rails`, `rack`, `laravel`, `php`). Empty when there is none. | | |
| 36 | + | | `dockerfile` | A container's own Dockerfile, relative to the project. Empty when g1t writes one, and for anything that is not a container. | | |
| 37 | + | | `port` | The port a container listens on: its Dockerfile's `EXPOSE`, else `8080`. | | |
| 34 | 38 | | `install` | How dependencies are installed, from the lockfile: `npm ci`, `pnpm install --frozen-lockfile`, and so on, and `bundle install` for Jekyll. Empty when there is nothing to install. | | |
| 35 | 39 | | `build` | What builds it: your command, the package's `build` script (`npm run build`), the framework's own command, or `wrangler deploy --dry-run` for Workers. Empty when nothing does. | | |
| 36 | 40 | | `output-dir` | The folder served, relative to the project (`.` for the project itself): yours, a Workers project's assets directory, the folder the framework writes, or the first of `dist`, `build`, `out`, `public`, `_site`, `.output/public` that exists. Empty when a build has yet to write it, as for a Cloudflare adapter's Worker. | | |
| 37 | 41 | ||
| 38 | − | When `kind` is `none` because of how a framework is set up, such as a | |
| 39 | − | Next.js app without `output: 'export'`, the step's log says what to | |
| 40 | − | change. | |
| 42 | + | When `kind` is `none`, the step's log says what to change or add, such as | |
| 43 | + | a `Dockerfile`. [Docker deployments](https://docs.g1t.sh/guides/deployments/#docker-deployments) | |
| 44 | + | has the order in which a project's kind is decided. | |
| 41 | 45 | ||
| 42 | 46 | ## Frameworks | |
| 43 | 47 |
| 15 | 15 | description: An output folder of your own, as g1t/deploy takes it. | |
| 16 | 16 | required: false | |
| 17 | 17 | default: "" | |
| 18 | + | build-as: | |
| 19 | + | description: What it is built as, auto, static, workers or docker, as g1t/deploy takes it. | |
| 20 | + | required: false | |
| 21 | + | default: "" | |
| 22 | + | dockerfile: | |
| 23 | + | description: A Dockerfile's path of your own, as g1t/deploy takes it. | |
| 24 | + | required: false | |
| 25 | + | default: "" | |
| 18 | 26 | ||
| 19 | 27 | outputs: | |
| 20 | 28 | kind: | |
| 21 | − | description: workers (a Wrangler config, or a framework's Cloudflare adapter), static (a site a build writes), html (the files as they are) or none (nothing to serve, or a framework set up to need a server). | |
| 29 | + | description: workers (a Wrangler config, or a framework's Cloudflare adapter), static (a site a build writes), html (the files as they are), container (a Dockerfile, a framework set up to need a server, or a server g1t writes a Dockerfile for) or none (nothing to serve). | |
| 22 | 30 | value: ${{ steps.detect.outputs.kind }} | |
| 23 | 31 | install: | |
| 24 | 32 | description: How its dependencies are installed, such as npm ci. Empty without a package.json. | |
| ⋯ | |||
| 30 | 38 | description: The folder that is served, relative to the project (. for the project itself). Empty when a build has yet to write it. | |
| 31 | 39 | value: ${{ steps.detect.outputs.output-dir }} | |
| 32 | 40 | framework: | |
| 33 | − | description: The framework it is built with, such as vite, astro, nextjs, sveltekit or hugo. Empty when there is none. | |
| 41 | + | description: The framework it is built with, such as vite, astro, nextjs, sveltekit or hugo; for a container without one, dockerfile or the server g1t writes a Dockerfile for (node, flask, rails…). Empty when there is none. | |
| 34 | 42 | value: ${{ steps.detect.outputs.framework }} | |
| 43 | + | dockerfile: | |
| 44 | + | description: A container's own Dockerfile, relative to the project. Empty when g1t writes one, or for anything else. | |
| 45 | + | value: ${{ steps.detect.outputs.dockerfile }} | |
| 46 | + | port: | |
| 47 | + | description: The port a container listens on, its Dockerfile's EXPOSE or 8080. Empty for anything else. | |
| 48 | + | value: ${{ steps.detect.outputs.port }} | |
| 35 | 49 | ||
| 36 | 50 | runs: | |
| 37 | 51 | using: composite | |
| ⋯ | |||
| 42 | 56 | ROOT_DIR: ${{ inputs.root-dir }} | |
| 43 | 57 | BUILD_COMMAND: ${{ inputs.build-command }} | |
| 44 | 58 | OUTPUT_DIR: ${{ inputs.output-dir }} | |
| 59 | + | BUILD_AS: ${{ inputs.build-as }} | |
| 60 | + | DOCKERFILE: ${{ inputs.dockerfile }} | |
| 45 | 61 | run: | | |
| 46 | 62 | set -euo pipefail | |
| 47 | 63 | runner="$(bash "$GITHUB_ACTION_PATH/runner.sh")" | |
| 81 | 81 | DeploymentsOp::CreateDeploymentStatus => "Add a status to a reported deployment: state (queued, in_progress, success, failure, error or inactive), description, environment_url (where it is served) and log_url (where its output can be read). The deployment takes its state, and any address it gives. A success with auto_inactive (true unless you say) makes the environment's older successful deployments inactive. The commit's `deploy / <environment>` check follows: pending while queued or in progress, then success, failure or error. A g1t.page build's statuses come from the build and cannot be added to. Needs the Write role.", | |
| 82 | 82 | DeploymentsOp::ListEnvironments => "List the environments a repository's deployments went to, those people use directly first (production by name before others), then the most recently deployed, and after them those with protection rules but no deployment yet. Each has its name, url (where its current deployment is served), production_environment, transient_environment, deployments_count, latest (its newest deployment, whatever its state), current (its newest successful deployment that is still active) and updated_at, and, when it has rules, protection_rules (required_reviewers, wait_timer, branch_policy), deployment_branch_policy, branch_policies and can_admins_bypass. total_count counts deployments across every environment. Needs the Read role.", | |
| 83 | 83 | DeploymentsOp::GetEnvironment => "Get one environment by name, matched without regard to case, with its current and latest deployments and its protection rules (see update_environment). An environment with rules but no deployment yet is found too. A name with slashes is URL-encoded in the path. Needs the Read role.", | |
| 84 | − | DeploymentsOp::StartDeploymentBuild => "From a running g1t Actions job, open a g1t.page build of one of the repository's projects, which the job then builds in its own checkout with `g1t-runner deploy` (the g1t/deploy action does both). Only the job's own token (G1T_TOKEN) may call it, with `deployments: write` (or `pages: write`) in its `permissions:`. project is the project's slug; it may be left out when the repository has one project with Deployments on. The run decides what is built: a run on the project's production branch (a push, a dispatch or a schedule there) builds production, a pull request's run builds that pull request's preview, and a run on another branch builds that branch's preview; a tag's run is refused. sha is the commit built, the run's unless you say. A pull request from a fork or from someone without the Write role, and a run that had to be approved, get no build secrets. The workspace's plan must include Deployments and its spend limit must not be reached; the build's time is the job's Actions time and is not charged again. Returns deploy_id and token (DEPLOY_ID and DEPLOY_TOKEN for `g1t-runner deploy`, the token good for this build alone until it finishes), kind (production or preview), project, branch, number, commit, url (the app's address), trusted, the project's root_dir, build_command and output_dir, and build_env and build_secrets (the project's variables and secrets for builds of that kind). The commit's `g1t / deploy` check is pending until the build reports.", | |
| 84 | + | DeploymentsOp::StartDeploymentBuild => "From a running g1t Actions job, open a g1t.page build of one of the repository's projects, which the job then builds in its own checkout with `g1t-runner deploy` (the g1t/deploy action does both). Only the job's own token (G1T_TOKEN) may call it, with `deployments: write` (or `pages: write`) in its `permissions:`. project is the project's slug; it may be left out when the repository has one project with Deployments on. The run decides what is built: a run on the project's production branch (a push, a dispatch or a schedule there) builds production, a pull request's run builds that pull request's preview, and a run on another branch builds that branch's preview; a tag's run is refused. sha is the commit built, the run's unless you say. A pull request from a fork or from someone without the Write role, and a run that had to be approved, get no build secrets. The workspace's plan must include Deployments and its spend limit must not be reached; the build's time is the job's Actions time and is not charged again. Returns deploy_id and token (DEPLOY_ID and DEPLOY_TOKEN for `g1t-runner deploy`, the token good for this build alone until it finishes), kind (production or preview), project, branch, number, commit, url (the app's address), trusted, the project's root_dir, build_command and output_dir, build_as (auto, static, workers or docker) and dockerfile (its Dockerfile's path, or null to find `Dockerfile` or `Containerfile`), and build_env and build_secrets (the project's variables and secrets for builds of that kind). The commit's `g1t / deploy` check is pending until the build reports.", | |
| 85 | 85 | } | |
| 86 | 86 | } | |
| 87 | 87 |
| 549 | 549 | } | |
| 550 | 550 | ||
| 551 | 551 | // A sandbox building a deployment, reporting with its build's token, | |
| 552 | − | // which is not a g1t token. The body goes through as it is: it can | |
| 553 | − | // carry a Worker's bundled code. | |
| 552 | + | // which is not a g1t token, or a container app's Worker reporting how | |
| 553 | + | // long its container ran, with its own token. The body goes through as | |
| 554 | + | // it is: it can carry a Worker's bundled code. | |
| 554 | 555 | if method == "POST" && !on_mcp | |
| 555 | − | && let Some(rest) = path.strip_prefix("/deployments/jobs/") | |
| 556 | + | && let Some(rest) = path | |
| 557 | + | .strip_prefix("/deployments/jobs/") | |
| 558 | + | .map(|rest| format!("jobs/{rest}")) | |
| 559 | + | .or_else(|| path.strip_prefix("/deployments/apps/").filter(|rest| rest.ends_with("/meter")).map(|rest| format!("apps/{rest}"))) | |
| 556 | 560 | { | |
| 557 | − | let target = format!("https://deployments/jobs/{rest}"); | |
| 561 | + | let target = format!("https://deployments/{rest}"); | |
| 558 | 562 | let body = request.bytes().await?; | |
| 559 | 563 | let headers = worker::Headers::new(); | |
| 560 | 564 | headers.set("content-type", "application/json")?; |
| 11889 | 11889 | "root_dir": "", | |
| 11890 | 11890 | "build_command": null, | |
| 11891 | 11891 | "output_dir": null, | |
| 11892 | + | "build_as": "auto", | |
| 11893 | + | "dockerfile": null, | |
| 11892 | 11894 | "build_env": { | |
| 11893 | 11895 | "PUBLIC_API_URL": "https://api.example.com" | |
| 11894 | 11896 | }, |
| 364 | 364 | ||
| 365 | 365 | ## Deployments | |
| 366 | 366 | ||
| 367 | − | ### Static sites and Workers only | |
| 367 | + | ### Containers are not switched on yet | |
| 368 | 368 | ||
| 369 | − | Deployments run static sites and Workers projects. You can't deploy a | |
| 370 | − | long-running server, a container, or an app that needs a process that stays | |
| 371 | − | up. | |
| 369 | + | [Docker deployments](/guides/deployments/#docker-deployments) are built, | |
| 370 | + | but off on g1t.page: a project that runs as a container fails to deploy, | |
| 371 | + | saying so, and one with a site besides its `Dockerfile` is built as that | |
| 372 | + | site. | |
| 372 | 373 | ||
| 373 | − | - **Why.** Apps run on Cloudflare Workers, which run only while they answer a | |
| 374 | − | request. That is why an app nobody visits costs nothing. | |
| 374 | + | - **Why.** Cloudflare's container APIs (applications in a Workers for | |
| 375 | + | Platforms namespace, registry logins) have to be checked against g1t's | |
| 376 | + | own account first. | |
| 375 | 377 | - **Instead.** Build a framework app as a static export or with its | |
| 376 | 378 | Cloudflare adapter, as [Frameworks](/guides/deployments/#frameworks) | |
| 377 | − | lists; a build set up to need a server fails and says which. Or deploy | |
| 378 | − | the server from a workflow to wherever it runs today, with its | |
| 379 | − | credentials in [secrets](/guides/secrets-and-variables/). | |
| 379 | + | lists, or set **Build as** to Static site. Or deploy the server from a | |
| 380 | + | workflow to wherever it runs today, with its credentials in | |
| 381 | + | [secrets](/guides/secrets-and-variables/). | |
| 382 | + | ||
| 383 | + | ### A container is one instance, and keeps nothing | |
| 384 | + | ||
| 385 | + | A container app runs one instance at a time, sleeps after five minutes | |
| 386 | + | without a request, and starts from its image each time it wakes. | |
| 387 | + | ||
| 388 | + | - **Why.** Each app's container is one Durable Object's, and Cloudflare | |
| 389 | + | Containers' disks last only as long as the container runs. | |
| 390 | + | - **Instead.** Keep data in a database or a bucket the app reaches over | |
| 391 | + | the network. A larger **Container size** gives one instance more room. | |
| 392 | + | ||
| 393 | + | ### What a container sends out is not counted | |
| 394 | + | ||
| 395 | + | The data a container sends back to visitors is metered; what it sends to | |
| 396 | + | other hosts on the internet (a database elsewhere, an API) is not. | |
| 397 | + | ||
| 398 | + | - **Why.** It leaves the container without passing through g1t. | |
| 399 | + | - **Instead.** Nothing to do: it is not charged. | |
| 380 | 400 | - **Status.** A runtime for servers is planned. | |
| 381 | 401 | ||
| 382 | 402 | ### Some Workers bindings are not provisioned |
| 344 | 344 | nothing else expires in the bucket. Reset deletes the agent's object at | |
| 345 | 345 | once. | |
| 346 | 346 | ||
| 347 | + | ## Docker deployments | |
| 348 | + | ||
| 349 | + | Container apps ([Docker deployments](/guides/deployments/#docker-deployments)) | |
| 350 | + | are off until `CONTAINERS` is `on` in `services/deployments/wrangler.jsonc`. | |
| 351 | + | The code follows Cloudflare's documentation and Wrangler's own client for | |
| 352 | + | these APIs, but none of them has been called against g1t's account yet. | |
| 353 | + | Before turning it on, check each of these with a test project: | |
| 354 | + | ||
| 355 | + | 1. **The token.** `CLOUDFLARE_API_TOKEN` of `services/deployments` has | |
| 356 | + | **Containers: Edit** (and the account has Containers, on Workers Paid). | |
| 357 | + | 2. **Registry logins.** | |
| 358 | + | `POST /accounts/{id}/containers/registries/registry.cloudflare.com/credentials` | |
| 359 | + | with `{ "permissions": ["push"], "expiration_minutes": 15 }` answers | |
| 360 | + | inside the API's usual `{ success, result }` with a `username` and | |
| 361 | + | `password`, and `docker push` succeeds with that login alone, with no | |
| 362 | + | `pull` (Docker checks for layers before it uploads them). If it needs | |
| 363 | + | `pull`, do not add it: a pull login reads every app's image. Find | |
| 364 | + | another way first, such as Cloudflare scoping logins to a repository. | |
| 365 | + | 3. **The registry's hosts.** A push from a guarded sandbox reaches only | |
| 366 | + | `registry.cloudflare.com` (on the deploy builds' list in | |
| 367 | + | `services/runner/src/egress.ts`). If its layers upload to another host, | |
| 368 | + | add that host there. | |
| 369 | + | 4. **Checking a digest.** g1t asks | |
| 370 | + | `HEAD https://registry.cloudflare.com/v2/<account>/g1t-<app>/manifests/sha256:…` | |
| 371 | + | with a pull login of its own, following a `WWW-Authenticate: Bearer` | |
| 372 | + | challenge. It answers 200 for an image that was pushed. | |
| 373 | + | 5. **The app's Worker.** A script uploaded to the dispatch namespace with | |
| 374 | + | `exports: { "G1tApp": { "type": "durable-object", "storage": "sqlite" } }` | |
| 375 | + | and `containers: [{ "class_name": "G1tApp" }]` in its metadata is taken, | |
| 376 | + | and taken again on the next deploy with nothing changed about the class. | |
| 377 | + | 6. **Its Durable Object namespace.** | |
| 378 | + | `GET /accounts/{id}/workers/durable_objects/namespaces` lists it with | |
| 379 | + | `class` `G1tApp`, `script` the app's name and `dispatch_namespace` | |
| 380 | + | `g1t-deployments`. | |
| 381 | + | 7. **The Container application.** `POST /accounts/{id}/containers/applications` | |
| 382 | + | with `scheduling_policy` `default`, the image by digest, `instance_type`, | |
| 383 | + | `max_instances` 1 and `durable_objects.namespace_id` creates it; a | |
| 384 | + | rollout (`…/rollouts`, `strategy` `rolling`, `step_percentage` 100, | |
| 385 | + | `kind` `full_auto`) moves it to a new image; `DELETE` removes it. | |
| 386 | + | 8. **Starting.** `ctx.container.start({ env, enableInternet: true })` | |
| 387 | + | works under the default policy from a Worker in a dispatch namespace, | |
| 388 | + | the container gets its environment, and `getTcpPort(port).fetch()` | |
| 389 | + | reaches it; the dispatcher's limits (50 ms of CPU, 50 subrequests) leave | |
| 390 | + | room for a page that waits on a container starting. | |
| 391 | + | 9. **Sleeping and billing.** Five minutes after the last request the | |
| 392 | + | container stops (the app's object stops it itself), Cloudflare's billable | |
| 393 | + | usage for Containers stops growing, and `container_meters` in the | |
| 394 | + | deployments database has the minutes the app's Worker reported, which | |
| 395 | + | should be at least what Cloudflare billed for the same time. | |
| 396 | + | 10. **Pausing and taking down.** A workspace over its limit gets its | |
| 397 | + | container app's Worker put back paused, which stops the container, and | |
| 398 | + | taking an app down deletes its application before its Worker. | |
| 399 | + | ||
| 400 | + | Then set `CONTAINERS` to `on`, deploy `services/deployments`, and the | |
| 401 | + | container build of a project with a `Dockerfile` goes live; the | |
| 402 | + | [limitations page](/about/limitations/#containers-are-not-switched-on-yet) | |
| 403 | + | and the caution in the deployments guide come out in the same change. | |
| 404 | + | ||
| 347 | 405 | ## Rolling back | |
| 348 | 406 | ||
| 349 | 407 | - **One unit, at once:** `npx wrangler rollback` in its folder. Revert the |
| 11 | 11 | deploys nothing while the remote leads it: pushes copied in from the remote | |
| 12 | 12 | don't build, and its **Deployments** page comes with a takeover. | |
| 13 | 13 | ||
| 14 | − | Apps run on Cloudflare Workers, on `g1t.page`: | |
| 14 | + | Apps run on Cloudflare Workers, on `g1t.page`, and a project with a | |
| 15 | + | `Dockerfile` (or a server g1t can write one for) runs as a container on | |
| 16 | + | Cloudflare Containers behind one; see [Docker deployments](#docker-deployments): | |
| 15 | 17 | ||
| 16 | 18 | | | Address | | |
| 17 | 19 | | --- | --- | | |
| ⋯ | |||
| 50 | 52 | report your own, and hear of them by webhook. | |
| 51 | 53 | ||
| 52 | 54 | An app runs only while it answers a request. One nobody visits runs | |
| 53 | − | nothing and costs nothing, and the next visit wakes it in milliseconds. | |
| 55 | + | nothing and costs nothing, and the next visit wakes it in milliseconds. A | |
| 56 | + | container wakes in seconds, and sleeps again after five minutes without a | |
| 57 | + | request. | |
| 54 | 58 | ||
| 55 | 59 | Deployments are part of the [g1t plan](/guides/usage-and-billing/#the-g1t-plan), | |
| 56 | 60 | and are opt-in per project. A project set to deploy on g1t, with | |
| ⋯ | |||
| 96 | 100 | | --- | --- | | |
| 97 | 101 | | `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`. | | |
| 98 | 102 | | A framework from the table below | 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**. | | |
| 103 | + | | 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. | | |
| 104 | + | | A `Dockerfile` or `Containerfile`, and no framework above | Builds the image and runs it as a **container**. | | |
| 105 | + | | A Node.js, Python, Go, Ruby or PHP server, with no `Dockerfile` | Writes a Dockerfile for it, shows it in the build's log, and runs it as a **container**. See [Docker deployments](#docker-deployments). | | |
| 99 | 106 | | A `build` script in `package.json` | Installs dependencies, runs `npm run build`, and serves the output as a **static site**. | | |
| 100 | 107 | | An `index.html` and nothing to build | Serves it as it is. | | |
| 108 | + | | Nothing g1t recognises | Fails, saying to add a `Dockerfile`. | | |
| 101 | 109 | ||
| 102 | 110 | **Settings → Deployments** shows what g1t detected at the project's last | |
| 103 | − | finished build (**Workers project**, **Static site** or **Plain HTML**), | |
| 104 | − | and what running it costs. Before the first build it says **Detected at | |
| 105 | − | the first build**. The framework is named at the top of the build's log, | |
| 106 | − | as `Detected Astro: framework=astro.` | |
| 111 | + | finished build and what it was built with, such as **Static site · | |
| 112 | + | Astro**, **Workers project**, **Container · Dockerfile** or **Container · | |
| 113 | + | Flask, with a Dockerfile g1t wrote**, and what running it costs. Before | |
| 114 | + | the first build it says **Detected at the first build**. The framework is | |
| 115 | + | named at the top of the build's log, as `Detected Astro: framework=astro.` | |
| 116 | + | **Build as**, under the same settings, overrides what is detected. | |
| 107 | 117 | ||
| 108 | 118 | Dependencies are installed by the lockfile that is there: `npm ci`, | |
| 109 | 119 | `pnpm install --frozen-lockfile`, `yarn install` or `bun install`, and | |
| ⋯ | |||
| 138 | 148 | | Jekyll | `_config.yml` and a `Gemfile` | `bundle install`, then `bundle exec jekyll build` | `_site`, or `destination` | | |
| 139 | 149 | | mdBook | `book.toml` | `mdbook build` | `book`, or `build.build-dir` | | |
| 140 | 150 | ||
| 141 | − | - **Frameworks that need a server fail early, saying what to change.** | |
| 142 | − | g1t.page runs static sites and Workers, not servers that stay up. A | |
| 143 | − | Next.js app without `output: 'export'`, an Astro site with | |
| 144 | − | `output: 'server'` and no Cloudflare adapter, a SvelteKit app with | |
| 145 | − | `adapter-auto` or `adapter-node`, and a React Router or Remix app that | |
| 146 | − | renders on the server each fail before anything is installed, with the | |
| 147 | − | change that makes them deploy: a static export, or the framework's | |
| 148 | − | Cloudflare adapter. An **Output directory** of your own skips the check. | |
| 151 | + | - **Frameworks that need a server run as containers.** A Next.js app | |
| 152 | + | without `output: 'export'`, an Astro site with `@astrojs/node`, a | |
| 153 | + | SvelteKit app with `adapter-node`, and a React Router or Remix app that | |
| 154 | + | renders on the server are built from their `Dockerfile`, or one g1t | |
| 155 | + | writes ([Docker deployments](#docker-deployments)). A static export or | |
| 156 | + | the framework's Cloudflare adapter costs less, since it runs nothing | |
| 157 | + | while no one visits; the build's log says so. One g1t cannot run as a | |
| 158 | + | server, such as SvelteKit with `adapter-auto`, fails before anything is | |
| 159 | + | installed, saying what to change or to add a `Dockerfile`. An **Output | |
| 160 | + | directory** of your own, or **Build as** Static site, builds it as a | |
| 161 | + | static site instead. | |
| 149 | 162 | - **A framework's Worker.** Astro's and SvelteKit's Cloudflare adapters, | |
| 150 | 163 | OpenNext, and `@cloudflare/vite-plugin` with a Wrangler config build a | |
| 151 | 164 | Worker. g1t runs the build, then bundles what it wrote with | |
| ⋯ | |||
| 192 | 205 | lists each one it left out. Code that needs them should check that the | |
| 193 | 206 | binding is there. | |
| 194 | 207 | ||
| 195 | − | What Deployments cannot run yet, such as long-running servers, is on | |
| 208 | + | ### Docker deployments | |
| 209 | + | ||
| 210 | + | Any app that runs in a container deploys as one, whatever its framework or | |
| 211 | + | age: give it a `Dockerfile`, or let g1t write one. It runs on | |
| 212 | + | [Cloudflare Containers](https://developers.cloudflare.com/containers/) | |
| 213 | + | behind the app's Worker, at the same addresses, custom domains and | |
| 214 | + | previews as any other app. | |
| 215 | + | ||
| 216 | + | :::caution[Not switched on yet] | |
| 217 | + | Docker deployments are built but off on g1t.page until Cloudflare's | |
| 218 | + | container APIs are checked against g1t's account. Until then a container | |
| 219 | + | build fails with "Docker deployments are not switched on for g1t.page | |
| 220 | + | yet", and a project that is a container only for having a `Dockerfile`, | |
| 221 | + | beside a site g1t could serve, is built as that site, as before. | |
| 222 | + | ::: | |
| 223 | + | ||
| 224 | + | What a project is built as is decided in this order: | |
| 225 | + | ||
| 226 | + | 1. **Build as**, under **Settings → Deployments**: **Static site**, | |
| 227 | + | **Workers** or **Docker** is what it is built as, whatever else it has. | |
| 228 | + | 2. **Auto** (the default) finds it from its files: | |
| 229 | + | 1. A Wrangler config is a Workers project. | |
| 230 | + | 2. A framework that builds a static site or a Worker is that, even | |
| 231 | + | beside a `Dockerfile`: it costs nothing while no one visits. | |
| 232 | + | 3. A framework set up to need a server is a container, from its | |
| 233 | + | `Dockerfile`, or one g1t writes for it. | |
| 234 | + | 4. A project with no framework g1t knows is a container when it has a | |
| 235 | + | `Dockerfile` (or `Containerfile`, or the path set as its | |
| 236 | + | **Dockerfile**), or is a server g1t recognises. | |
| 237 | + | 5. Otherwise a static site or plain HTML, as above; with nothing to | |
| 238 | + | serve, the build fails, saying to add a `Dockerfile`. | |
| 239 | + | ||
| 240 | + | g1t writes a Dockerfile for these, when there is none: | |
| 241 | + | ||
| 242 | + | | The project has | Runs with | | |
| 243 | + | | --- | --- | | |
| 244 | + | | `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 | | |
| 245 | + | | Next.js, Astro with `@astrojs/node`, SvelteKit with `adapter-node`, React Router or Remix | `next start`, `node ./dist/server/entry.mjs`, `node build`, `react-router-serve` or `remix-serve`, after the build | | |
| 246 | + | | `requirements.txt`, `pyproject.toml` or `Pipfile`, and `manage.py` (Django) | `gunicorn <project>.wsgi` on `python:3.12-slim` | | |
| 247 | + | | The same, with Flask and an `app.py`, `main.py`, `server.py`, `wsgi.py` or `asgi.py` | `gunicorn <module>:app` | | |
| 248 | + | | The same, with FastAPI or Starlette | `uvicorn <module>:app` | | |
| 249 | + | | The same, with neither | `python <module>.py` | | |
| 250 | + | | `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 | | |
| 251 | + | | A `Gemfile` with Rails | `rails server`, after `assets:precompile`, on `ruby:3.3-slim` | | |
| 252 | + | | A `Gemfile` and a `config.ru` | `rackup` | | |
| 253 | + | | `composer.json`, and `artisan` with `public/index.php` (Laravel), or an `index.php` | Apache with PHP 8.3, after `composer install` | | |
| 254 | + | ||
| 255 | + | The Dockerfile g1t writes is printed whole in the build's log. To change | |
| 256 | + | how the app is built or run, commit it as `Dockerfile` and edit it. It | |
| 257 | + | leaves `.git`, `node_modules`, `.venv` and `__pycache__` out of the image. | |
| 258 | + | ||
| 259 | + | What a container gets, and what it must do: | |
| 260 | + | ||
| 261 | + | - **Listen on `0.0.0.0` at `PORT`.** `PORT` is the port its Dockerfile's | |
| 262 | + | first `EXPOSE` names (in its last stage), else `8080`. The first request | |
| 263 | + | waits up to 90 seconds for it to answer there; one that never does gets | |
| 264 | + | a 503 page saying so. | |
| 265 | + | - **The project's [secrets and variables](#secrets-and-variables)** are | |
| 266 | + | its environment variables, set by g1t when it starts, never baked into | |
| 267 | + | the image. The image's build gets none of them: a step that needs one | |
| 268 | + | belongs in the app's start. | |
| 269 | + | - **One instance, which sleeps.** It starts on the first request, and | |
| 270 | + | stops after five minutes without one; asleep, it costs nothing, and the | |
| 271 | + | next request starts it again in a few seconds. Its disk starts from the | |
| 272 | + | image each time: keep data in a database or a bucket. | |
| 273 | + | - **Its size** is **Container size** under **Settings → Deployments**: | |
| 274 | + | `lite` (the default) to `standard-4`, as [What it costs](#what-it-costs) | |
| 275 | + | lists. The image may be as large as the size's disk, 2 GB for `lite`; | |
| 276 | + | a larger image fails the build, saying how large it was. | |
| 277 | + | - **Built for `linux/amd64`**, with Docker, in the build's sandbox (or the | |
| 278 | + | workflow job's Docker, for [`g1t-runner deploy`](#deploying-from-a-workflow)), | |
| 279 | + | with network access as any build has. **Build command** is not used: | |
| 280 | + | the Dockerfile builds it. | |
| 281 | + | ||
| 282 | + | What Deployments cannot run yet is on | |
| 196 | 283 | [What g1t can't do yet](/about/limitations/#deployments). | |
| 197 | 284 | ||
| 198 | 285 | ## Previews of branches | |
| ⋯ | |||
| 538 | 625 | | --- | --- | --- | | |
| 539 | 626 | | Production | On | Deploy the default branch on every push. | | |
| 540 | 627 | | Previews | On | A preview for every branch with an open pull request. | | |
| 541 | − | | Build command | The project's own | Runs instead of `npm run build`, or before bundling a Workers project. | | |
| 628 | + | | Build as | Auto | **Auto** (found from its files), **Static site**, **Workers** or **Docker**; see [Docker deployments](#docker-deployments). | | |
| 629 | + | | Dockerfile | `Dockerfile` or `Containerfile` | For a container: its Dockerfile's path, inside the project. | | |
| 630 | + | | Container size | `lite` | For a container: the instance it runs at, with its price an hour awake. | | |
| 631 | + | | Build command | The project's own | Runs instead of `npm run build`, or before bundling a Workers project. Not used for a container. | | |
| 542 | 632 | | Output directory | Found by itself | What a static site serves. | | |
| 543 | 633 | | Idle days | 7 | 1 to 90. A preview no one visits this long comes down. | | |
| 544 | 634 | | Build with a workflow | Off | Build through the deploy workflow g1t manages for the project; see [build with a workflow](#build-with-a-workflow). | | |
| ⋯ | |||
| 556 | 646 | ||
| 557 | 647 | The build gets them as environment variables, with secrets hidden in its | |
| 558 | 648 | log. The running app gets them as bindings, `env.KEY`, put in place by g1t | |
| 559 | − | rather than the build. Secrets reach a preview only when the pull | |
| 649 | + | rather than the build; a container gets them as its environment variables | |
| 650 | + | when it starts, and its image's build gets none. Secrets reach a preview only when the pull | |
| 560 | 651 | request's author has Write or higher on the repository, whether a member | |
| 561 | 652 | or an [outside collaborator](/guides/access-and-roles/#outside-collaborators), | |
| 562 | 653 | or it is g1t working on its own. For a pull request g1t made, whoever | |
| ⋯ | |||
| 586 | 677 | | Requests, to all of the workspace's apps | $0.30 a million | $0.36 a million | | |
| 587 | 678 | | CPU time your code spends computing (waiting on the network is not counted) | $0.02 a million ms | $0.024 a million ms | | |
| 588 | 679 | | 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 | | |
| 680 | + | | A container, while it is awake, by the minute (see the sizes below) | From $0.0129 an hour (`lite`) | From $0.0155 an hour | | |
| 681 | + | | Data a container sends | $0.05 a GB, at most | $0.06 a GB | | |
| 589 | 682 | ||
| 590 | − | Builds are charged when they finish. Requests, CPU time and custom domains | |
| 591 | − | are counted through the month, so your limit and **Billing** follow them as | |
| 592 | − | they happen, and charged once, on the first sweep after the month ends, as | |
| 593 | − | one line: *Deployments in 2026-10: 1,240,000 requests, 3,100,000 CPU ms, 2 | |
| 594 | − | custom domains*. | |
| 683 | + | A container's price is its size's memory, disk and vCPU while it is awake, | |
| 684 | + | with the Durable Object in front of it, at Cloudflare's | |
| 685 | + | [Containers prices](https://developers.cloudflare.com/containers/platform/pricing/) | |
| 686 | + | plus 20%. Cloudflare bills CPU only as it is used; g1t charges every vCPU | |
| 687 | + | as busy while the container is awake, so a container that mostly waits | |
| 688 | + | costs g1t less than it is charged. Data sent is charged at the most a GB | |
| 689 | + | costs anywhere ($0.05; $0.025 in North America and Europe), since g1t | |
| 690 | + | cannot tell where it went. Each minute it is awake is charged from its | |
| 691 | + | start, so a container woken by one request, and stopped five minutes after | |
| 692 | + | it, is charged five minutes. | |
| 693 | + | ||
| 694 | + | | Size | vCPU | Memory | Disk, and the largest image | An hour awake | Awake all month (730 hours) | | |
| 695 | + | | --- | --- | --- | --- | --- | --- | | |
| 696 | + | | `lite` | 1/16 | 256 MiB | 2 GB | $0.0155 | $11.28 | | |
| 697 | + | | `basic` | 1/4 | 1 GiB | 4 GB | $0.0404 | $29.46 | | |
| 698 | + | | `standard-1` | 1/2 | 4 GiB | 8 GB | $0.0956 | $69.77 | | |
| 699 | + | | `standard-2` | 1 | 6 GiB | 12 GB | $0.1616 | $117.95 | | |
| 700 | + | | `standard-3` | 2 | 8 GiB | 16 GB | $0.2708 | $197.68 | | |
| 701 | + | | `standard-4` | 4 | 12 GiB | 20 GB | $0.4880 | $356.24 | | |
| 702 | + | ||
| 703 | + | **Settings → Deployments** shows these with the price book's prices of the | |
| 704 | + | day. A container that is visited all day never sleeps, and costs the last | |
| 705 | + | column; one visited for an hour a day costs about a thirtieth of it. | |
| 595 | 706 | ||
| 707 | + | Builds are charged when they finish. Requests, CPU time, custom domains | |
| 708 | + | and container time are counted through the month, so your limit and | |
| 709 | + | **Billing** follow them as they happen, and charged once, on the first | |
| 710 | + | sweep after the month ends, as one line: *Deployments in 2026-10: | |
| 711 | + | 1,240,000 requests, 3,100,000 CPU ms, 2 custom domains, 41.5 container | |
| 712 | + | hours and 2.3 GB sent*. | |
| 713 | + | ||
| 596 | 714 | A static site, or plain HTML, runs no code when it is visited: its files | |
| 597 | 715 | are served as they are, so serving it adds no requests or CPU time. Its | |
| 598 | 716 | builds are metered. A Workers project's code runs on every request, so its | |
| 599 | − | requests and CPU time are metered too. | |
| 717 | + | requests and CPU time are metered too. A container's Worker is metered the | |
| 718 | + | same way, and its container by the minute while it is awake. | |
| 600 | 719 | ||
| 601 | 720 | An app that no one visits costs nothing. Previews still come down when | |
| 602 | 721 | their pull request closes and after their idle days, to keep the list of | |
| 603 | 722 | what is up short. | |
| 604 | 723 | ||
| 605 | 724 | The only things that stop a workspace's apps are its own spend limit | |
| 606 | − | (apps pause with a notice, and come back once there is room) and the plan | |
| 607 | − | ending (they come down). | |
| 725 | + | (apps pause with a notice, and come back once there is room; a paused | |
| 726 | + | container is stopped and starts no more) and the plan ending (they come | |
| 727 | + | down). | |
| 608 | 728 | ||
| 609 | 729 | Deployments used to be a $5 plan of their own. A Deployments subscription | |
| 610 | 730 | bought before keeps working until its current period ends, and is not | |
| ⋯ | |||
| 653 | 773 | upload them and nothing else. Files Cloudflare already has are skipped. | |
| 654 | 774 | 4. The sandbox sends the Worker's code to g1t, which puts the app in | |
| 655 | 775 | g1t's [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/) | |
| 656 | − | namespace, under the name in its address. | |
| 776 | + | namespace, under the name in its address. A container build goes as | |
| 777 | + | [below](#containers). | |
| 657 | 778 | 5. A request to `*.g1t.page` reaches g1t's dispatcher, which runs the app | |
| 658 | 779 | by the name in the hostname. Nothing else is looked up. A request to a | |
| 659 | 780 | custom domain reaches the same dispatcher through Cloudflare for SaaS; | |
| ⋯ | |||
| 663 | 784 | `g1t.page`, not `g1t.sh`, so they share no cookies or origin with the site | |
| 664 | 785 | you sign in to. | |
| 665 | 786 | ||
| 787 | + | ### Containers | |
| 788 | + | ||
| 789 | + | 1. The build finds it is a container, and asks g1t where to push its | |
| 790 | + | image (the build's `registry` step). g1t mints a login to Cloudflare's | |
| 791 | + | registry, `registry.cloudflare.com`, that can **push only** and lasts | |
| 792 | + | **15 minutes**, and names the app's own repository, | |
| 793 | + | `<account>/g1t-<app>`, with the build's id as its tag. Asked before | |
| 794 | + | anything is built, so a build that could not push is not run. | |
| 795 | + | 2. The sandbox starts a Docker Engine of its own (as a workflow job's), | |
| 796 | + | builds the image for `linux/amd64`, checks its size against the | |
| 797 | + | container size's disk, pushes it with that login, and reports the | |
| 798 | + | image by its digest. | |
| 799 | + | 3. g1t runs only an image by digest in that app's repository, and asks | |
| 800 | + | the registry, with a pull login of its own, that the digest is there. | |
| 801 | + | It puts the app's Worker in the namespace: g1t's own code, never the | |
| 802 | + | project's, with a Durable Object class that holds the container, the | |
| 803 | + | project's secrets and variables, and a token for the app to report its | |
| 804 | + | running time with. Then it creates the app's Container application | |
| 805 | + | (at most one instance, at the project's size, attached to that class), | |
| 806 | + | or rolls it out to the new image. | |
| 807 | + | 4. A request reaches the app's Worker through the dispatcher, as any | |
| 808 | + | app's does. The Worker hands it to the app's Durable Object, which | |
| 809 | + | starts the container if it is asleep, with the environment, waits for | |
| 810 | + | it to answer on its port, and passes the request to it. Every minute it | |
| 811 | + | is awake, the object charges the next minute ahead and reports it to g1t | |
| 812 | + | (`POST /deployments/apps/<app>/meter` on the API, with its token); five | |
| 813 | + | minutes after the last request it stops the container. | |
| 814 | + | 5. A preview's container comes down with its pull request, its | |
| 815 | + | application and Worker deleted together. | |
| 816 | + | ||
| 817 | + | Why it is built this way, from Cloudflare's documentation and Wrangler's | |
| 818 | + | own code (October 2026): | |
| 819 | + | ||
| 820 | + | - **Workers for Platforms runs containers.** A user Worker in a dispatch | |
| 821 | + | namespace may bind Containers, as a Durable Object | |
| 822 | + | ([WfP bindings](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/configuration/bindings/)). | |
| 823 | + | Wrangler's `deploy --dispatch-namespace` deploys containers by finding | |
| 824 | + | the Durable Object namespace of the class in that dispatch namespace and | |
| 825 | + | creating a Container application attached to it | |
| 826 | + | (`POST /accounts/{id}/containers/applications`, with | |
| 827 | + | `durable_objects.namespace_id`), then rolling out new images | |
| 828 | + | (`…/applications/{id}/rollouts`). So each app is a Worker of its own, | |
| 829 | + | with a class of its own, isolated as every app is, and the dispatcher | |
| 830 | + | routes to it unchanged. A single host Worker of g1t's could not run every | |
| 831 | + | app's image: a Durable Object starts only the images its Worker's | |
| 832 | + | configuration names. | |
| 833 | + | - **The default scheduling policy.** It takes one image and size for the | |
| 834 | + | whole application, set through the API, which suits one app per | |
| 835 | + | application; the `durable_object` policy is in beta, and needs the | |
| 836 | + | image map uploaded with the Worker. | |
| 837 | + | - **Registry logins.** Cloudflare mints registry credentials with | |
| 838 | + | `POST /accounts/{id}/containers/registries/registry.cloudflare.com/credentials` | |
| 839 | + | and `{ "permissions": ["push"], "expiration_minutes": 15 }`, as Wrangler | |
| 840 | + | does for its own pushes. They cannot be held to one repository, so g1t | |
| 841 | + | holds the build to its app's: it names the repository, runs nothing but a | |
| 842 | + | digest in it, and never gives a build a login that can pull, so no build | |
| 843 | + | can read another app's image. g1t's own Cloudflare token never reaches | |
| 844 | + | the sandbox. | |
| 845 | + | - **Sizes and limits.** Containers' instance types are `lite` (1/16 vCPU, | |
| 846 | + | 256 MiB, 2 GB) through `standard-4` (4 vCPU, 12 GiB, 20 GB); an image | |
| 847 | + | may be as large as its instance's disk, and an account holds 50 GB of | |
| 848 | + | images ([limits](https://developers.cloudflare.com/containers/platform/limits/)). | |
| 849 | + | - **Scale to zero.** Containers are billed every 10 ms while running, and | |
| 850 | + | stop being billed once asleep. The Durable Object stops its container | |
| 851 | + | itself after five minutes without a request, rather than leaving it to | |
| 852 | + | the inactivity timeout, since the object's own minute alarm, which | |
| 853 | + | meters it, would otherwise keep it awake. | |
| 854 | + | ||
| 666 | 855 | ## Troubleshooting | |
| 667 | 856 | ||
| 668 | 857 | | You see | Do | | |
| ⋯ | |||
| 671 | 860 | | 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. | | |
| 672 | 861 | | "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). | | |
| 673 | 862 | | "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**. | | |
| 674 | − | | "found nothing to serve" | Add a `build` script, an `index.html`, or a Workers config; or set **Output directory**. | | |
| 863 | + | | "found nothing to serve" | Add a `Dockerfile`, a `build` script, an `index.html`, or a Workers config; or set **Output directory**. | | |
| 864 | + | | "Docker deployments are not switched on for g1t.page yet" | The project is a container, which g1t.page does not run yet. Set **Build as** to Static site or Workers if it can be built as one. See [Docker deployments](#docker-deployments). | | |
| 865 | + | | "**Build as** is Docker, but this project has no Dockerfile" | Add a `Dockerfile`, or set **Dockerfile** to its path. | | |
| 866 | + | | "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**. | | |
| 867 | + | | 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. | | |
| 675 | 868 | | "the output directory `x` does not exist after the build" | The build wrote elsewhere: check its log, then fix **Output directory**. | | |
| 676 | 869 | | "`d1_databases` is not provisioned on g1t.page yet" | The app deployed without that binding. See [Workers projects](#workers-projects). | | |
| 677 | 870 | | "`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). | | |
| ⋯ | |||
| 698 | 891 | `npx wrangler secret put CLOUDFLARE_API_TOKEN` in `services/deployments`. | |
| 699 | 892 | 4. Deploy `services/deployments`, `services/pages`, and the runner. | |
| 700 | 893 | ||
| 894 | + | Docker deployments also need the token to have **Containers: Edit**, a | |
| 895 | + | Workers Paid plan with Containers, and `CONTAINERS` set to `on` in | |
| 896 | + | `services/deployments/wrangler.jsonc`; see | |
| 897 | + | [Docker deployments](/guides/deploy-to-cloudflare/#docker-deployments) | |
| 898 | + | for what to check first. | |
| 899 | + | ||
| 701 | 900 | Custom domains also need Cloudflare for SaaS on the apps' zone. Turn it on | |
| 702 | 901 | under the zone's **SSL/TLS → Custom Hostnames**, give the token **SSL and | |
| 703 | 902 | Certificates: Edit** on the zone, and run `scripts/setup-custom-domains.sh`: | |
| 157 | 157 | | App requests | Million | $0.30 | $0.36 | | |
| 158 | 158 | | App CPU time | Million CPU milliseconds | $0.02 | $0.024 | | |
| 159 | 159 | | Custom domains | Domain-month | $0.10 | $0.12 | | |
| 160 | + | | [Container apps](/guides/deployments/#docker-deployments), while awake | Second, by the minute: vCPU, memory, disk and the Durable Object, by the size | From $0.0129 an hour (`lite`) | Cost + 20%: from $0.0155 an hour | | |
| 161 | + | | Data container apps send | GB | $0.05 | $0.06 | | |
| 160 | 162 | | Private storage, past the free 1 GB | GB-month | $0.50 | $0.60 | | |
| 161 | 163 | | [Actions cache](#actions-cache) storage | GB-month | $0.015 | $0.018 | | |
| 162 | 164 | | Git operations, past the free 50,000 a month | 1,000 | $0.15 | $0.18 | | |
| ⋯ | |||
| 299 | 301 | | The [merge queue](/guides/merge-queue/) | [Sandbox time](#sandbox-time) | | |
| 300 | 302 | | [Workflow](/guides/actions/) jobs | [Sandbox time](#sandbox-time), more on a [larger machine](#workflow-jobs-on-larger-machines) | | |
| 301 | 303 | | [Actions cache](#actions-cache) | What it holds, on the plan only | | |
| 302 | − | | [Deployments](/guides/deployments/) | Builds, requests, CPU time and custom domains, from the first. Projects and previews are not charged. | | |
| 304 | + | | [Deployments](/guides/deployments/) | Builds, requests, CPU time, custom domains and container time, from the first. Projects and previews are not charged. | | |
| 303 | 305 | | [Private repository storage](#storage-search-embeddings-and-scans) | Past the free 1 GB, on the plan only | | |
| 304 | 306 | | [Git operations](#git-operations) | Past the free 50,000 a month, on the plan only | | |
| 305 | 307 | | [Search embeddings](#storage-search-embeddings-and-scans) | For private text, on the plan only | | |
| ⋯ | |||
| 374 | 376 | | Deploy builds | Per second | | |
| 375 | 377 | | App requests and CPU | Per million requests, per million CPU milliseconds | | |
| 376 | 378 | | Custom domains | Per domain-month | | |
| 379 | + | | Container apps | Per vCPU-second, GiB-second of memory and GB-second of disk while awake, a second of the Durable Object in front of each, and per GB sent | | |
| 377 | 380 | | Private storage past the free 1 GB | Per GB-month | | |
| 378 | 381 | | Actions cache storage | Per GB-month | | |
| 379 | 382 | | Git operations past the free 50,000 a month | Per 1,000 | | |
| ⋯ | |||
| 1083 | 1086 | | Runs on your own model provider | Older months only: the flat fee runs on your own provider used to carry. | | |
| 1084 | 1087 | | Sandbox time | Each sandbox's time. | | |
| 1085 | 1088 | | Self-hosted runner time | Each job on your own runners, at $0. | | |
| 1086 | − | | Deployments | Builds as they finish; each month's requests, CPU time and custom domains when it closes. | | |
| 1089 | + | | Deployments | Builds as they finish; each month's requests, CPU time, custom domains and container time when it closes. | | |
| 1087 | 1090 | | Private storage | Storage past the free 1 GB, once a month. | | |
| 1088 | 1091 | | Actions cache storage | What `actions/cache` and artifacts held, on the plan, once a month. | | |
| 1089 | 1092 | | Git operations | Operations past the free 50,000, once a month. | | |
| 107 | 107 | | [Security](/guides/security/) | Secret push protection, history scanning and pull requests that upgrade vulnerable dependencies. | <Status is="live" /> | | |
| 108 | 108 | | [Deployments](/guides/deployments/) | A live preview of every pull request on g1t.page, production on merge, and custom domains. | <Status is="live" /> | | |
| 109 | 109 | | Preview data | Each preview gets its own copy of the database and bucket, deleted when the pull request closes. | <Status is="coming" /> | | |
| 110 | + | | [Docker deployments](/guides/deployments/#docker-deployments) | Any app with a Dockerfile, or a Node.js, Python, Go, Ruby or PHP server, runs as a container that sleeps when no one visits. | <Status is="coming" /> | | |
| 110 | 111 | | Rollback and always on | One click back to any deploy, request logs, and container apps kept warm. | <Status is="coming" /> | | |
| 111 | 112 | | [GitHub](/guides/github/) and [mirroring](/guides/mirroring/) | Import repositories, keep them mirrored, or push changes back. | <Status is="live" /> | | |
| 112 | 113 | | Git over SSH | `git@g1t.sh` remotes. Use HTTPS until then. | <Status is="coming" /> | |
Binary or large file; its contents are not shown.
| 1 | + | import assert from "node:assert/strict"; | |
| 2 | + | import { test } from "node:test"; | |
| 3 | + | ||
| 4 | + | import { detectedLabel, sizeLabel } from "./deploy-kind.ts"; | |
| 5 | + | ||
| 6 | + | const NAMES = { astro: "Astro", dockerfile: "Dockerfile", flask: "Flask", nextjs: "Next.js" }; | |
| 7 | + | ||
| 8 | + | test("what a build found is said with what it was built with", () => { | |
| 9 | + | assert.equal(detectedLabel("static", "astro", NAMES), "Static site · Astro"); | |
| 10 | + | assert.equal(detectedLabel("container", "dockerfile", NAMES), "Container · Dockerfile"); | |
| 11 | + | assert.equal(detectedLabel("container", "flask", NAMES), "Container · Flask, with a Dockerfile g1t wrote"); | |
| 12 | + | assert.equal(detectedLabel("workers", null, NAMES), "Workers project"); | |
| 13 | + | assert.equal(detectedLabel("html", "unknown", NAMES), "Plain HTML"); | |
| 14 | + | }); | |
| 15 | + | ||
| 16 | + | test("a container size reads plainly", () => { | |
| 17 | + | assert.equal(sizeLabel({ vcpu: 1 / 16, memoryGib: 0.25, diskGb: 2 }), "1/16 vCPU, 256 MiB, 2 GB disk"); | |
| 18 | + | assert.equal(sizeLabel({ vcpu: 2, memoryGib: 8, diskGb: 16 }), "2 vCPU, 8 GiB, 16 GB disk"); | |
| 19 | + | }); |
| 1 | + | /** | |
| 2 | + | * What Settings, Deployments says a project is, from its last build: its | |
| 3 | + | * kind and what it was built with (`Static site · Astro`, `Container · | |
| 4 | + | * Dockerfile`), and what running it costs, in a line. No imports beyond | |
| 5 | + | * types, so it can be tested under Node. | |
| 6 | + | */ | |
| 7 | + | ||
| 8 | + | import type { BuildAs, DetectedKind } from "@g1t/contracts"; | |
| 9 | + | ||
| 10 | + | export const KIND_LABELS: Record<DetectedKind, string> = { | |
| 11 | + | static: "Static site", | |
| 12 | + | html: "Plain HTML", | |
| 13 | + | workers: "Workers project", | |
| 14 | + | container: "Container", | |
| 15 | + | }; | |
| 16 | + | ||
| 17 | + | export const KIND_COSTS: Record<DetectedKind, string> = { | |
| 18 | + | static: "Serving its files runs no code, so it adds no requests or CPU time. Builds are metered.", | |
| 19 | + | html: "Its files are served as they are and run no code, so serving adds no requests or CPU time. Builds are metered.", | |
| 20 | + | workers: "Its code runs on every request: requests and CPU time are metered, and so are builds.", | |
| 21 | + | container: | |
| 22 | + | "Its container runs only while it is awake: from the first request until it has had none for a few minutes, metered by the minute at its size, with what it sends. Builds are metered.", | |
| 23 | + | }; | |
| 24 | + | ||
| 25 | + | /** `Static site · Astro`, `Container · Dockerfile`, or the kind alone when nothing names what it was built with. */ | |
| 26 | + | export function detectedLabel(kind: DetectedKind, framework: string | null | undefined, names: Record<string, string>): string { | |
| 27 | + | const built = framework ? (names[framework] ?? null) : null; | |
| 28 | + | if (!built) return KIND_LABELS[kind]; | |
| 29 | + | // A server g1t wrote a Dockerfile for says so. | |
| 30 | + | const written = kind === "container" && framework !== "dockerfile" ? `${built}, with a Dockerfile g1t wrote` : built; | |
| 31 | + | return `${KIND_LABELS[kind]} · ${written}`; | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | /** The Build as choices, as the select shows them. */ | |
| 35 | + | export const BUILD_AS_OPTIONS: { value: BuildAs; label: string }[] = [ | |
| 36 | + | { value: "auto", label: "Auto: found from its files" }, | |
| 37 | + | { value: "static", label: "Static site" }, | |
| 38 | + | { value: "workers", label: "Workers" }, | |
| 39 | + | { value: "docker", label: "Docker" }, | |
| 40 | + | ]; | |
| 41 | + | ||
| 42 | + | /** `1/16 vCPU, 256 MiB, 2 GB disk`. */ | |
| 43 | + | export function sizeLabel(size: { vcpu: number; memoryGib: number; diskGb: number }): string { | |
| 44 | + | const vcpu = size.vcpu >= 1 ? String(size.vcpu) : `1/${Math.round(1 / size.vcpu)}`; | |
| 45 | + | const memory = size.memoryGib >= 1 ? `${size.memoryGib} GiB` : `${Math.round(size.memoryGib * 1024)} MiB`; | |
| 46 | + | return `${vcpu} vCPU, ${memory}, ${size.diskGb} GB disk`; | |
| 47 | + | } |
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
This change is too large to show in full.