Skip to content

Architecture

How apps are built and served on g1t.page, how the package registries are routed and stored, and where a project's logs, traces and metrics go.

deployments (services/deployments, TypeScript, D1 g1t-deployments) builds apps and puts them live. pages (services/pages) serves them. Neither runs your app as a server that waits: each app is a Worker, which runs only while it answers a request, so an app nobody visits costs nothing.

Deployments: built in a sandbox, uploaded to a dispatch namespace, served by one Worker that looks up the app by hostnameDiagram: Deployments: built in a sandbox, uploaded to a dispatch namespace, served by one Worker that looks up the app by hostname - repos (a push to a branch); deployments (services/deployments); build sandbox (g1t-runner, MODE=deploy); KV g1t-domains (hostname → app); dispatch namespace (g1t-deployments); telemetry (tail events, per project); Visitors (.g1t.page, your domain); pages (services/pages); Your software (OTLP to otel.g1t.sh) Arrows: - repos ⇢ deployments: git.push (asynchronous) - deployments → build sandbox: start_deploy - deployments → dispatch namespace: the built Worker - deployments → KV g1t-domains: domains - Visitors → pages - pages → KV g1t-domains: which app? - pages → dispatch namespace: APPS.get(app) - dispatch namespace ⇢ telemetry (asynchronous) - Your software → telemetry: OTLPreposa push to a branchdeploymentsservices/deploymentsbuild sandboxg1t-runner, MODE=deployKV g1t-domainshostname → appdispatch namespaceg1t-deploymentstelemetrytail events, per projectVisitors*.g1t.page, your domainpagesservices/pagesYour softwareOTLP to otel.g1t.shgit.pushstart_deploythe built Workerdomainswhich app?APPS.get(app)OTLP
Deployments: built in a sandbox, uploaded to a dispatch namespace, served by one Worker that looks up the app by hostname
Deployments: built in a sandbox, uploaded to a dispatch namespace, served by one Worker that looks up the app by hostname
Deployments: built in a sandbox, uploaded to a dispatch namespace, served by one Worker that looks up the app by hostnameDiagram: Deployments: built in a sandbox, uploaded to a dispatch namespace, served by one Worker that looks up the app by hostname - repos (a push to a branch); deployments (services/deployments); build sandbox (g1t-runner, MODE=deploy); KV g1t-domains (hostname → app); dispatch namespace (g1t-deployments); telemetry (tail events, per project); Visitors (.g1t.page, your domain); pages (services/pages); Your software (OTLP to otel.g1t.sh) Arrows: - repos ⇢ deployments: git.push (asynchronous) - deployments → build sandbox: start_deploy - deployments → dispatch namespace: the built Worker - deployments → KV g1t-domains: domains - Visitors → pages - pages → KV g1t-domains: which app? - pages → dispatch namespace: APPS.get(app) - dispatch namespace ⇢ telemetry (asynchronous) - Your software → telemetry: OTLPreposa push to a branchdeploymentsservices/deploymentsbuild sandboxg1t-runner, MODE=deployKV g1t-domainshostname → appdispatch namespaceg1t-deploymentstelemetrytail events, per projectVisitors*.g1t.page, your domainpagesservices/pagesYour softwareOTLP to otel.g1t.shgit.pushstart_deploythe built Workerdomainswhich app?APPS.get(app)OTLP

Every push to the default branch makes a production deployment, and every open pull request gets a preview. deployments reads pushes from its event queue and asks runner for a sandbox in deploy mode (Actions, runners and sandboxes). The sandbox works out what the app is, preferring what runs nothing while no one visits, in this order (What a project is built as):

  1. a Workers project, from wrangler deploy --dry-run;
  2. a framework it knows, such as Vite, Astro, Next.js or SvelteKit, as the site it builds, even beside a Dockerfile;
  3. a Node.js server with no Dockerfile (Express, Koa, Hono…), as a Worker: the sandbox writes an entry that serves the app’s own server with httpServerHandler from cloudflare:node, bundles it with wrangler and starts it once with wrangler dev before deploying it (Node.js servers as Workers);
  4. any other server it recognises, as a container, from its Dockerfile or one it writes (Docker deployments);
  5. a build script’s site or an index.html, even beside a Dockerfile;
  6. a Dockerfile with nothing else to serve, as a container.

A container is the fallback: a Node.js server that cannot run as a Worker (a native addon, say), or whose Worker does not bundle or start, is deployed as a container instead.

A container’s image is pushed to Cloudflare’s registry with a push-only login deployments mints for the build, and runs on Cloudflare Containers behind a Worker of g1t’s own in the same namespace. A self-hosted g1t whose account has no Containers sets CONTAINERS to off.

The sandbox is given a key that can upload the app’s static files and nothing else. deployments uploads the built Worker itself to the Workers for Platforms dispatch namespace g1t-deployments. A site with only static files gets a Worker that serves just its assets. A workflow can build instead and hand over the result, with POST /repos/{owner}/{repo}/deployments/builds or the g1t/deploy action.

Hostname
Production <project>-<workspace>.g1t.page
A preview <project>-git-<branch>-<workspace>.g1t.page
Your domain A CNAME to domains.g1t.page, a custom hostname on g1t.page’s zone

pages answers every one of those hostnames. It reads the app’s name from the hostname, or looks a custom domain up in the g1t-domains KV, which deployments writes, and hands the request to that app’s Worker with env.APPS.get(app). Each request to an app may use 50 ms of CPU and 50 subrequests. A preview comes down when its pull request closes, or after it has been idle for a number of days you choose (7 by default).

packages (services/packages, Rust, D1 g1t-packages) is every package registry g1t has. The site recognises a registry’s path and passes the request on whole, as it does for git:

Registry Path on g1t.sh
Container images (OCI Distribution 1.1) /v2/, with tokens at /v2/token
npm /-/npm/
Cargo, sparse index /-/cargo/<workspace>/
Composer /-/composer/
Maven /-/maven/
NuGet /-/nuget/<workspace>/v3/index.json
RubyGems /-/rubygems/

Files are stored in the g1t-packages bucket by their digest, so a layer or a tarball pushed twice is kept once. The bucket sits behind the BlobStore port, so a self-hosted installation keeps them in any S3 store. Large downloads are answered with a signed URL to the bucket. Packages has each registry’s setup.

telemetry (services/telemetry, TypeScript) keeps each project’s logs, traces, metrics and errors in a Durable Object of its own, TelemetryStore, in that object’s SQLite storage. A project’s data never shares a database with another’s.

It has three ways in:

Door From
otel.g1t.sh/v1/traces, /v1/logs, /v1/metrics Your software, as OpenTelemetry over HTTP, JSON or protobuf, with a g1to_… token
Tail events Apps on g1t.page: telemetry is pages’ tail consumer, and apps name it as their Tail Worker
/otlp/v1/* on the API The same as otel.g1t.sh, passed on as it is; the only door on a self-hosted installation

A store keeps up to 4 GiB and sheds its oldest day past that. You choose how long raw data is kept, from 1 to 30 days; rollups are kept 90 days. Observability shows what you can do with it.