gitstalk
Docs: Self-hosting and custom hosts

Self-hosting and custom hosts live

All of Gitstalk's source is public, and the whole forge deploys to one Cloudflare account with a handful of commands. Run staging and production side by side, put it on your own domain, and set your own limits.

What you get and what it needs

You get the same forge as the hosted service: the git proxy and engine, pre-land check sandboxes, GitHub Actions and automations, the web app, the MCP server for agents and these docs. You own the Cloudflare resources.

  • A Cloudflare account on Workers Paid, with Containers, Durable Objects and Artifacts, and Workers AI for Ask and automations. Workers AI calls go through an AI Gateway named default: provision creates it when your API token may, and otherwise tells you to create it under AI, AI Gateway.
  • On your machine: Node 24 or later, pnpm 11, stable Rust, and Docker (it builds the container images).

What you will own

  1. People and agentsbrowser, git, MCP
  2. web, mcp, site, sshWorkers
  3. gatewayengine in Durable Objects, runner containers
  4. StorageArtifacts, D1, KV, R2, Queues
Piece Cloudflare product
Gateway: git proxy, engine, registry, repository RPC Workers, Durable Objects (SQLite)
Runner: squash, merge, revert, run the suite (the check sandboxes) Containers (Rust)
Actions executor: one container per workflow job Workers, Containers
Git storage Artifacts
Accounts, repositories, access, the activity index D1
OAuth grants; public repositories' files and diffs cached at the edge KV, Cache API
Repository events to the index Queues
Actions logs and dependency snapshots R2
Web app (Next.js App Router on vinext), site and docs Workers
MCP server Workers, Workers AI
SSH (not offered publicly yet) Workers, Durable Objects, Containers, Spectrum

Deploy

The repository holds no account's ids. Each package's wrangler.jsonc is a template, and each deployment you run is an environment: one directory, environments/<name>/, with your account, a name suffix and the packages to run. Never wrangler deploy a template; the package's deploy script refuses on purpose.

  1. Create the environment

    Copy the example and fill in your account.

    Terminal
    pnpm install
    pnpm -F @gitstalk/gateway exec wrangler login
    cp -r environments/example environments/staging

    Edit environments/staging/env.jsonc. Find your account id with pnpm -F @gitstalk/gateway exec wrangler whoami. Wrangler is a dependency of each package, not of the repository root, so a bare npx wrangler there downloads whatever version is newest instead.

  2. Provision

    Creates what the environment needs if it is missing (D1 databases, the KV namespaces for OAuth grants and the edge cache, R2 buckets, the events queue and the Artifacts namespace), records their ids in resources.json, generates the Workers' secrets into secrets/ (never printed, never committed) and applies the D1 migrations. Run it again after an update that adds a resource or a migration.

    Terminal
    pnpm env:provision staging
  3. Push the secrets

    Sends the generated values to the Workers. They are never printed. Before the first deploy there are no Workers yet, so it only says so: deploy uploads each Worker's secrets with its first version.

    Terminal
    pnpm env:secrets staging
  4. Deploy

    Stops if a required secret is missing, then deploys the Actions executor, the gateway with its runner container, the MCP server, the site, the web app and SSH, in that order. Add --dry-run to check without uploading, or --only mcp to deploy one package.

    Terminal
    pnpm env:deploy staging
  5. Smoke test

    Checks health, the sign-up and sign-in pages, MCP metadata, robots.txt and the docs on the live origins. Set GITSTALK_TOKEN to a personal token and it also checks whoami and a clone.

    Terminal
    pnpm env:smoke staging

Every Worker and resource is named <prefix>-<name><suffix>: the prefix defaults to gitstalk, and with the suffix -staging you get gitstalk-gateway-staging. An empty suffix keeps the base names. D1, KV, R2, queues, Artifacts namespaces, Analytics Engine datasets and rate-limit namespaces all belong to the account, so on an account that already runs Gitstalk give the new environment its own prefix or suffix, and its own namespace_id for the web's SIGNIN_RATE_LIMIT (the example's overrides.web.ratelimits). A kill switch for benchmark races: POST /v1/admin/halt with the admin token refuses new race runs and starts (DELETE on the same path resumes them). It does not stop repositories. The gateway also has an hourly cron trigger that deletes race repositories older than a day; it is part of the gateway's config and needs nothing provisioned.

The environment file

account_idThe Cloudflare account every command targets.
"00000000000000000000000000000000"
suffixAdded to every Worker and resource name.
"-staging"
workers_dev_subdomainWhere Workers answer without custom domains.
"your-subdomain"
urlsOptional custom origins, one per package.
{ "web": "https://git.example.com", … }
search_indexingLet search engines index this deployment.
false (default)
Placeholders only. Your real account id stays in your private fork, never in public docs.

prefix renames the Workers, packages lists what to run, and overrides merges into each package's generated config, such as smaller container pools for a test stack. Search engines stay out unless you say otherwise: every environment answers /robots.txt with Disallow: / and marks its pages X-Robots-Tag: noindex. For the one deployment that should be found, set "search_indexing": true.

Your own domain and custom host

Without urls, every Worker answers on its workers.dev address. To use your own hostnames, put the zone in the same Cloudflare account and name one origin per package. Each becomes a Workers Custom Domain of that Worker; Cloudflare creates the DNS record and the certificate on deploy. The hostnames must not have DNS records already. Do not look them up before the first deploy: a resolver that answered "no such name" keeps that answer for up to 30 minutes, so the smoke test fails with fetch failed on your machine while the hosts already work elsewhere.

"urls": {
  "web": "https://git.example.com",
  "site": "https://git.example.com",
  "mcp": "https://mcp.git.example.com",
  "gateway": "https://api.git.example.com",
  "ssh": "https://ssh.git.example.com"
}

What changes when you set urls

What Becomes
Clone URL https://git.example.com/<owner>/<repo>.git (the web origin)
MCP URL https://mcp.git.example.com/mcp
OAuth issuer and Actions OIDC issuer The MCP and gateway origins
Docs link The site origin, /docs/
Passkeys Belong to the web app's hostname. Pick it before people sign up; changing it later means people register passkeys again.
Edge cache The web app keeps public repositories' files and diffs in each data centre's cache too. That cache does nothing on workers.dev, where only the global KV copy is used.

When site and web share an origin, the web app hands the site's paths (/docs/, its files, and / for someone signed out) to the site Worker over a service binding.

Point agents at your host

The plugin's marketplace entry points at the hosted MCP server, so on your own host add the server directly with your MCP URL. Your deployment's /signup/agent page prints these lines for you.

Claude Code
claude mcp add --transport http gitstalk https://mcp.git.example.com/mcp && claude mcp login gitstalk
Codex
codex mcp add gitstalk --url https://mcp.git.example.com/mcp && codex mcp login gitstalk
Connect git
curl -fsSL https://git.example.com/setup.sh | sh -s -- detect

A script served by your deployment already points at it. For a copy you downloaded elsewhere, set GITSTALK_WEB=https://git.example.com.

Secrets each Worker needs

Names only. Provision generates them, and never replaces one a Worker already has.

Worker Required secrets
Gateway ADMIN_TOKEN, RUN_TOKEN_SECRET, ACTIONS_SECRETS_KEY, OIDC_REQUEST_SECRET, OIDC_SIGNING_KEYS
Actions executor ADMIN_TOKEN
Web app DEMO_PASSWORD, EMAIL_SIGNIN_KEY (optional: TURNSTILE_SECRET_KEY)
SSH SSH_HOST_KEY
MCP server, site none

Email sign-in codes need EMAIL_SENDER_DOMAIN in the web package's overrides.web.vars: a zone in your account onboarded to Email Sending. Left empty, sign-in is passkeys only. PLATFORM_ADMINS (comma-separated handles) sets who sees the admin area; by default nobody does.

Limits you set

The hosted beta's limits are these defaults; see Limits and plans. When you self-host, you set every one.

Setting Default Where
Pre-land checks at once per repository (one sandbox each) 32 (1–64) operator setting preland_sandboxes on a repository's engine (not an env.jsonc field)
Validations at once 2 (1–16) operator setting ci_slots on a repository's engine
Runner containers in the shared pool 48 max_instances and RUNNER_MAX_INSTANCES in the gateway's config
Suite time limit 300 s timeout-minutes in checks.yml
Actions minutes per repository per month 100 ACTIONS_MONTHLY_MINUTES
Actions job timeout 60 min ACTIONS_JOB_TIMEOUT_MINUTES
Concurrent Actions jobs 4 ACTIONS_CONCURRENT_JOBS

Keep your environments in a private fork

The public repository ignores environments/ except the example. Keep yours in a private repository with the public one as upstream:

Terminal
git clone https://github.com/coop-collective/gitstalk.git my-gitstalk
cd my-gitstalk
git remote rename origin upstream
git remote add origin <your private repository>
git add -f environments/staging/env.jsonc environments/staging/resources.json
git commit -m "environments: staging"
git push -u origin main

git fetch upstream && git merge upstream/main   # later: stay in sync

Your fork only adds files under environments/, which upstream never writes, so the merge does not conflict. Never add secrets/. A scheduled workflow can do the merge for you: fetch upstream, merge (a pull request when it conflicts), push.

Deploying from CI

The deploy workflow in .github/workflows/ deploys staging on every push to main, and production only when you run it by hand or push a production-* tag, through the GitHub environment production, where you can require a reviewer. Each run checks the code, provisions, pushes the secrets, deploys and smoke-tests. It does nothing in a repository without environments/<name>/env.jsonc (or a GITSTALK_ENV_CONFIG variable on the GitHub environment). Each GitHub environment needs two secrets:

  • CLOUDFLARE_API_TOKEN: an account token with edit on Workers Scripts, D1, Workers KV Storage, Workers R2 Storage, Queues, Containers, Artifacts and AI Gateway, read on Account Settings, and for your zones edit on Workers Routes, DNS and SSL and Certificates, read on Zone.
  • GITSTALK_SECRETS_JSON: set with env:secrets --to-github.

Run a race against your own deployment

The race harness can rerun the benchmark against your account. Replay agents use no model tokens; real agents need the claude or codex CLI. Each race deletes its repository when it ends (unless you keep it) and prints the infrastructure cost it metered: $0.35 to $0.42 for each of three 12-agent Gitstalk races, plus $4 to $6 of model spend for the agents. Commands are in the research docs for races on Cloudflare and token-free replay. This is a benchmark, not a user feature; see Cloud agents and sandboxes.

Licence and name

Apache-2.0: you may use, modify and redistribute it, including running it as a service, under the licence's terms. The Gitstalk name and logos are not licensed: a copy you run for others needs its own name and logo (trademarks).

Sources