Cloudsteading
Product image still needed. This listing has source documentation, but no reviewed screenshot yet.

ripgit

A Git smart-HTTP remote with repository browsing on Durable Objects

ripgit is a self-hosted GitHub/GitLab alternative built on Cloudflare (Durable Objects, KV, Workers). Paid services required. Inspect the source and license in the linked repository.

Source & license

Upstream license: AGPL-3.0

License TL;DR

You can use and change it, even commercially. If people use your modified version over a network, offer them its corresponding source under the AGPL. Sharing copies has source-sharing duties too. Sharing source code is different from sharing users’ content.

Explain AGPL v3 in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@deathbyknowledge

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Paid services required

The committed cpu_ms=300_000 requires Workers Paid, starting at $5 USD/account/month plus KV/DO/compute usage. Configure the provided independently owned GitHub OAuth application/Worker and its secrets. Large repository pushes have explicit size, timeout and unsupported Git-feature limits.

Hosting requirements
  • Never expose the bare backend as a protected private Git service. Use the documented auth Worker and trusted service binding.
  • The README warns about 100MB request bodies, large-push timeouts, inconsistent force pushes and silently dropped annotated tags. These limitations matter before migration.
  • Source and configuration review establishes a deployment path and conditional costs; this candidate was not executed or load-tested.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: 21dba30. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • github ↗

    A self-hostable git remote backed by Cloudflare Durable Objects. One DO per repo, SQLite storage, FTS5 search, delta compression via [xpatch](https://github.com/ImGajeed76/xpatch). Built in Rust with [workers-rs](https://github.com/cloudflare/workers-rs).

  • gitlab ↗

    A self-hostable git remote backed by Cloudflare Durable Objects. One DO per repo, SQLite storage, FTS5 search, delta compression via [xpatch](https://github.com/ImGajeed76/xpatch). Built in Rust with [workers-rs](https://github.com/cloudflare/workers-rs).

  • workers ↗

    name = "ripgit" main = "build/index.js" compatibility_date = "2026-03-18" workers_dev = false # Repo registry — tracks which repos exist per owner so the profile page # can list them. Populated on first successful push. # Create with: wrangler kv namespace create REGISTRY [[kv_namespaces]] binding = "REGISTRY" [build] command = "cargo install -q \"worker-build@^0.7\"

  • kv ↗

    acks which repos exist per owner so the profile page # can list them. Populated on first successful push. # Create with: wrangler kv namespace create REGISTRY [[kv_namespaces]] binding = "REGISTRY" [build] command = "cargo install -q \"worker-build@^0.7\" && worker-build --release" [durable_objects] bindings = [ { name = "REPOSITORY", class_name = "Repository" } ] [[migrations]] tag = "v1" new_sqlite_classes = ["Repository"] [observability] [observability.logs] enabled = true invocation_logs = true

  • durable-objects ↗

    binding = "REGISTRY" [build] command = "cargo install -q \"worker-build@^0.7\" && worker-build --release" [durable_objects] bindings = [ { name = "REPOSITORY", class_name = "Repository" } ] [[migrations]] tag = "v1" new_sqlite_classes = ["Repository"] [observability] [observability.logs] enabled = true invocation_logs = true [limits] cpu_ms = 300_000

  • paid ↗

    name = "ripgit" main = "build/index.js" compatibility_date = "2026-03-18" workers_dev = false # Repo registry — tracks which repos exist per owner so the profile page # can list them. Populated on first successful push. # Create with: wrangler kv namespace create REGISTRY [[kv_namespaces]] binding = "REGISTRY" [build] command = "cargo install -q \"worker-build@^0.7\" && worker-build --release" [durable_objects] bindings = [ { name = "REPOSITORY", class_name = "Repository" } ] [[migrations]] tag = "v1" new_sqlite_classes = ["Repository"] [observability] [observability.logs] enabled = true invocation_logs = true [limits] cpu_ms = 300_000

  • paid ↗

    The Workers Paid plan includes Workers, Pages Functions, Workers KV, Hyperdrive, and Durable Objects usage for a minimum charge of $5 USD per month for an account. The plan includes increased initial usage allotments, with clear charges for usage that exceeds the base plan. There are no additional charges for data transfer (egress) or throughput (bandwidth).

  • paid ↗

    | Keys read | 100,000 / day | 10 million/month, + $0.50/million |

  • paid ↗

    Durable Objects are available both on Workers Free and Workers Paid plans.

  • paid ↗

    | Keys written | 1,000 / day | 1 million/month, + $5.00/million |

  • paid ↗

    | SQL Stored data <sup>5</sup> | 5 GB (total) | 5 GB-month, + $0.20/ GB-month |

  • AGPL-3.0 ↗

    GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you

  • architecture ↗

    name = "ripgit" main = "build/index.js" compatibility_date = "2026-03-18" workers_dev = false # Repo registry — tracks which repos exist per owner so the profile page # can list them. Populated on first successful push. # Create with: wrangler kv namespace create REGISTRY [[kv_namespaces]] binding = "REGISTRY" [build] command = "cargo install -q \"worker-build@^0.7\" && worker-build --release" [durable_objects] bindings = [ { name = "REPOSITORY", class_name = "Repository" } ] [[migrations]] tag = "v1" new_sqlite_classes = ["Repository"] [observability] [observability.logs] enabled = true invocation_logs = true [limits] cpu_ms = 300_000

  • architecture ↗

    name = "ripgit" main = "build/index.js" compatibility_date = "2026-03-18" workers_dev = false # Repo registry — tracks which repos exist per owner so the profile page # can list them. Populated on first successful push. # Create with: wrangler kv namespace create REGISTRY [[kv_namespaces]] binding = "REGISTRY" [build] command = "cargo install -q \"worker-build@^0.7\"

  • architecture ↗

    acks which repos exist per owner so the profile page # can list them. Populated on first successful push. # Create with: wrangler kv namespace create REGISTRY [[kv_namespaces]] binding = "REGISTRY" [build] command = "cargo install -q \"worker-build@^0.7\" && worker-build --release" [durable_objects] bindings = [ { name = "REPOSITORY", class_name = "Repository" } ] [[migrations]] tag = "v1" new_sqlite_classes = ["Repository"] [observability] [observability.logs] enabled = true invocation_logs = true

  • architecture ↗

    binding = "REGISTRY" [build] command = "cargo install -q \"worker-build@^0.7\" && worker-build --release" [durable_objects] bindings = [ { name = "REPOSITORY", class_name = "Repository" } ] [[migrations]] tag = "v1" new_sqlite_classes = ["Repository"] [observability] [observability.logs] enabled = true invocation_logs = true [limits] cpu_ms = 300_000

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

GitHub logoGitHub ↗

Git smart-HTTP remotes, file browsing, history and search; no issue tracker, CI service, GitHub/GitLab application parity or safe migration claim.

See supporting source ↗
GitLab logoGitLab ↗

Git smart-HTTP remotes, file browsing, history and search; no issue tracker, CI service, GitHub/GitLab application parity or safe migration claim.

See supporting source ↗
external SaaS target
varies
external SaaS target
varies

How it works

The shape of ripgit on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.

View upstream source ↗
Public interface
Configured entry points1
ripgit
wrangler.toml
↓
App
ripgit
entry
Cloudflare Workers
Entrypoint: build/index.js
↓

Configuration and workflow sources

Reviewed commit 21dba30423a3. Files were read as data; upstream applications and CI jobs were not executed.

Deployment configuration · 2 files
wrangler.toml ↗

Cloudflare Workers · compatibility 2026-03-18

ripgit · default

Entrypoint: build/index.js

Build: cargo install -q "worker-build@^0.7" && worker-build --release

  • REGISTRY → KV
  • REPOSITORY → Durable Objects · class Repository
examples/github-oauth/wrangler.toml ↗

Cloudflare Workers · example/template, excluded from overview · compatibility 2025-01-01

ripgit-auth · default

Entrypoint: src/index.ts

  • OAUTH_KV → KV
  • RIPGIT → Worker service · service ripgit

Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

Runtime source · handlers, binding usage and workflow steps

Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

examples/github-oauth/src/index.ts ↗
  • L166 · mainHandler calls (conditional paths may differ): preferredPageFormat, resolveActor, handleLogin, handleLogout, handleAuthorize, handleCallback, redirect, renderSettingsAuthRequiredPage, handleSettings, handleCreateToken, url.pathname.match, handleRevokeToken, decodeURIComponent, renderLandingPage, forwardToRipgit
  • L215 · preferredPageFormat calls (conditional paths may differ): url.searchParams.get, request.headers.get, accept.includes
  • L241 · respondPage calls (conditional paths may differ): headers.set
  • L267 · escapeHtml calls (conditional paths may differ): replace, value.replace
  • L281 · renderTextActions calls (conditional paths may differ): action.fields.join, lines.push, lines.join
  • L301 · renderTextHints calls (conditional paths may differ): join, hints.map
  • L310 · renderAuthPageHtml calls (conditional paths may differ): escapeHtml
  • L404 · handleSettings calls (conditional paths may differ): preferredPageFormat, listAgentTokens, respondPage, renderSettingsPageHtml, renderSettingsPageText
  • L425 · handleCreateToken calls (conditional paths may differ): request.formData, trim, form.get, redirect, createAgentToken, handleSettings
  • L440 · handleRevokeToken calls (conditional paths may differ): env.OAUTH_KV.get, Promise.all, env.OAUTH_KV.delete, redirect
  • L460 · renderSettingsAuthRequiredPage calls (conditional paths may differ): renderTextActions, renderTextHints, textNavigationHint, respondPage
  • L488 · renderSettingsPageHtml calls (conditional paths may differ): origin.replace, escapeHtml, join, tokens.map, encodeURIComponent, renderAuthPageHtml, authFooterHtml
  • L576 · renderSettingsPageText calls (conditional paths may differ): origin.replace, renderIndentedBlock, join, encodeURIComponent, actions.push, renderTextActions, renderTextHints, textNavigationHint
  • L676 · renderIndentedBlock calls (conditional paths may differ): join, map, text.split
  • L687 · renderLandingPage calls (conditional paths may differ): respondPage, renderLandingPageHtml, renderLandingPageText
  • L698 · renderLandingPageHtml calls (conditional paths may differ): renderAuthPageHtml, authFooterHtml
  • L731 · renderLandingPageText calls (conditional paths may differ): origin.replace, renderTextActions, renderTextHints, textNavigationHint
  • L821 · handleLogin calls (conditional paths may differ): searchParams.get, crypto.randomUUID, env.OAUTH_KV.put, JSON.stringify, toString, Response.redirect, githubAuthorizeUrl
  • L836 · handleAuthorize calls (conditional paths may differ): env.OAUTH_PROVIDER.parseAuthRequest, crypto.randomUUID, env.OAUTH_KV.put, JSON.stringify, toString, Response.redirect, githubAuthorizeUrl
  • L851 · handleCallback calls (conditional paths may differ): url.searchParams.get, env.OAUTH_KV.get, env.OAUTH_KV.delete, JSON.parse, toString, exchangeCode, fetchGitHubUser, createSession, encodeURIComponent, env.OAUTH_PROVIDER.completeAuthorization, Response.redirect
  • L918 · handleLogout calls (conditional paths may differ): searchParams.get
  • L933 · createAgentToken calls (conditional paths may differ): generateToken, crypto.randomUUID, env.OAUTH_KV.put, JSON.stringify
  • L954 · listAgentTokens calls (conditional paths may differ): env.OAUTH_KV.list, Promise.all, list.keys.map, k.name.slice, env.OAUTH_KV.get, JSON.parse, results.filter
  • L978 · createSession calls (conditional paths may differ): btoa, JSON.stringify, crypto.subtle.importKey, encode, crypto.subtle.sign, join, map, Array.from, padStart, b.toString
  • L1001 · verifySession calls (conditional paths may differ): value.lastIndexOf, value.slice, sigHex.match, Uint8Array.from, pairs.map, parseInt, crypto.subtle.importKey, encode, crypto.subtle.verify, JSON.parse, atob
  • L1037 · getSessionCookie calls (conditional paths may differ): request.headers.get, cookies.match, decodeURIComponent
  • L1047 · resolveActor calls (conditional paths may differ): extractToken, env.OAUTH_KV.get, JSON.parse, env.OAUTH_PROVIDER.unwrapToken, Math.floor, Date.now, getSessionCookie, verifySession
  • L1071 · forwardToRipgit calls (conditional paths may differ): headers.set, actor.scopes.join, headers.delete, env.RIPGIT.fetch
  • L1104 · extractToken calls (conditional paths may differ): request.headers.get, auth.startsWith, trim, auth.slice, atob, decoded.indexOf, decoded.slice
  • L1134 · generateToken calls (conditional paths may differ): crypto.getRandomValues, join, map, Array.from, padStart, b.toString

Environment references: env.OAUTH_KV · env.GITHUB_CLIENT_ID · env.OAUTH_PROVIDER · env.SESSION_SECRET · env.RIPGIT

examples/github-oauth/src/github.ts ↗
  • L15 · githubAuthorizeUrl calls (conditional paths may differ): url.searchParams.set, url.toString
  • L29 · exchangeCode calls (conditional paths may differ): fetch, JSON.stringify, resp.json
  • L64 · fetchGitHubUser calls (conditional paths may differ): fetch, resp.json

Environment references: env.GITHUB_CLIENT_ID · env.GITHUB_CLIENT_SECRET

Build and deployment pipeline · 0 GitHub Actions workflows

Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

No GitHub Actions workflow was found in the collected tree. Deployment may be manual or configured elsewhere.

package.json ↗
  • build:worker: cargo install -q "worker-build@^0.7" && worker-build --release

Full upstream document by @deathbyknowledge · README.md · snapshot 21dba30

ripgit

A self-hostable git remote backed by Cloudflare Durable Objects. One DO per repo, SQLite storage, FTS5 search, delta compression via xpatch. Built in Rust with workers-rs.

Live example: git.theagents.company — deathbyknowledge's repos including agents (~1k commits) and curl (~40k commits)

git remote add origin https://your-worker.workers.dev/username/myproject
git push origin main

Features

  • Standard git remote — git push, git clone, git fetch with any git client
  • Auth via Service Binding — sits behind an auth worker; public read, owner-only write. GitHub OAuth example in examples/github-oauth/
  • Agent-first UI — browsable pages also negotiate text/markdown and text/plain, with explicit actions and curl-friendly paths
  • Web UI — file browser, commit history, diffs, code search, syntax highlighting, branch selector, markdown README, repo settings
  • Full-text search — FTS5 over file content and commit messages. Supports @author:, @message:, @path:, @ext:, @content: query prefixes
  • Raw file serving — /:owner/:repo/raw/:ref/*path
  • Read API — refs, commits, trees, files, diffs, search, stats
  • Repo registry — repos listed on the owner profile page after first push
  • Delta compression — 5–20x compression on real repos depending on file churn
  • One DO per repo — strict isolation, scales horizontally

URLs

/:owner/                   owner profile — lists your repos
/:owner/:repo/             repo home — file tree, README, recent commits
/:owner/:repo/commits      commit history
/:owner/:repo/commit/:sha  commit detail with diff
/:owner/:repo/tree/:ref/*  directory browser
/:owner/:repo/blob/:ref/*  file viewer
/:owner/:repo/raw/:ref/*   raw file bytes
/:owner/:repo/search-ui    full-text search
/:owner/:repo/settings     stats, index rebuilds, config, delete (owner only)

API

All endpoints under /:owner/:repo/.

Endpoint Description
GET /refs List branches and tags
GET /log?ref=main&limit=50 Commit history
GET /commit/:hash Single commit
GET /tree/:hash Directory listing
GET /blob/:hash File content
GET /file?ref=main&path=src/lib.rs File at ref + path
GET /search?q=TODO Code search
GET /search?q=fix&scope=commits Commit message search
GET /diff/:sha Commit diff
GET /compare/base...head Two-commit comparison
GET /stats Compression and storage stats

Text Mode For Agents

Browsable pages support negotiated text mode for curl, scripts, and agents.

curl -H 'Accept: text/markdown' https://your-worker.workers.dev/alice/repo/
curl -H 'Accept: text/plain' https://your-worker.workers.dev/alice/repo/commits
curl 'https://your-worker.workers.dev/alice/repo/tree/main/src?format=md'
  • Accept: text/markdown returns a markdown view
  • Accept: text/plain returns the same content as plain text
  • ?format=md or ?format=text works when you can't keep headers attached while following links
  • Text pages list bare GET paths under headings like Files (GET paths) and spell out POST actions, fields, and requirements in an Actions section

Authentication

ripgit reads identity from trusted X-Ripgit-Actor-* headers, which are only settable by an upstream auth worker via Service Binding (not from the public internet).

  • Anonymous — read access to all repos
  • Authenticated — read + write to repos under your username

GitHub OAuth example

examples/github-oauth/ is a TypeScript Cloudflare Worker that authenticates with GitHub, issues session cookies for browsers and long-lived tokens for agents/scripts, and forwards requests to ripgit via Service Binding. Its landing page and /settings also support the same text-mode negotiation for curl-driven agents.

See examples/github-oauth/README.md for a focused deploy/setup guide.

Local dev:

cd examples/github-oauth
npm install
npm run dev:full   # auth worker on :8787, ripgit as service binding

Visit http://localhost:8787 → sign in → go to /settings → create a token → push:

git remote add origin http://username:TOKEN@localhost:8787/username/myrepo
git push origin main

First-time setup:

  1. Create a GitHub OAuth App — callback URL: http://localhost:8787/oauth/callback
  2. Set GITHUB_CLIENT_ID in examples/github-oauth/wrangler.toml
  3. wrangler secret put GITHUB_CLIENT_SECRET
  4. wrangler secret put SESSION_SECRET
  5. wrangler kv namespace create OAUTH_KV → fill IDs into wrangler.toml

Deploy:

wrangler deploy                          # ripgit worker
cd examples/github-oauth && wrangler deploy   # auth worker

Update the GitHub OAuth App's callback URL to your deployed auth worker URL.

Push test script

./scripts/push-test.sh -u username -t TOKEN -w https://your-worker.dev -r /path/to/repo

Setup (ripgit only, no auth)

Prerequisites: Rust, wrangler, LLVM (for zstd-sys).

brew install llvm
git clone https://github.com/your-org/ripgit
cd ripgit
wrangler kv namespace create REGISTRY   # fill ID into wrangler.toml
wrangler deploy

Without the auth worker in front, all repos are publicly readable and writable by anyone with the URL.

Architecture

browser / git client / agent
  │
  ▼
Auth Worker  (examples/github-oauth — optional, recommended)
  │  validates session/token, sets X-Ripgit-Actor-* headers
  │  Service Binding
  ▼
ripgit Worker  (entry, routing)
  │  /:owner/:repo/* → DO named "{owner}/{repo}"
  │  /:owner/        → profile page (queries REGISTRY KV)
  ▼
Repository Durable Object  (one per repo)
  ├── schema.rs   11 tables + 3 FTS5 virtual tables
  ├── pack.rs     streaming pack parser + pack generator
  ├── git.rs      smart HTTP protocol (receive-pack, upload-pack)
  ├── store.rs    delta compression, commit graph, FTS rebuild
  ├── api.rs      read API
  ├── diff.rs     tree diff + line-level diffs
  └── web.rs      server-rendered HTML (9 pages)
  ▼
SQLite  (up to 10 GB per DO)

KV namespaces:
  REGISTRY  — "repo:{owner}/{repo}" written on first push
  OAUTH_KV  — tokens, sessions (auth worker only)

Pushing large repos

Cloudflare Workers has a 100 MB request body limit. Push large repos incrementally:

./scripts/push-test.sh -u username -t TOKEN -r /path/to/repo -s 200

Or manually in checkpoints:

STEP=250
for FP in $(seq $STEP $STEP $(git rev-list --first-parent --count main)); do
  SHA=$(git rev-list --reverse --first-parent main | sed -n "${FP}p")
  git push origin "${SHA}:refs/heads/main"
done
git push origin main

Known limitations

  • DO storage timeout — pushes with >~10K objects per push can exceed the 30 s timeout; push incrementally
  • 100 MB request body limit — hard Workers platform constraint
  • No force push — may produce inconsistent state
  • No annotated tags — silently dropped; lightweight tags work

License

AGPL-3.0. See LICENSE.

Acknowledgments

Inspired by pgit and the xpatch delta compression library.

Frequently asked about ripgit

What is ripgit?+

ripgit is a self-hosted GitHub/GitLab alternative built on the Cloudflare developer platform. A Git smart-HTTP remote with repository browsing on Durable Objects

What does ripgit replace?+

ripgit is listed as an alternative to GitHub, GitLab. Compare the features and tradeoffs before migrating.

What Cloudflare primitives does ripgit use?+

ripgit is built on Durable Objects, KV, Workers.

How much does ripgit cost to run?+

The committed cpu_ms=300_000 requires Workers Paid, starting at $5 USD/account/month plus KV/DO/compute usage. Configure the provided independently owned GitHub OAuth application/Worker and its secrets. Large repository pushes have explicit size, timeout and unsupported Git-feature limits. Never expose the bare backend as a protected private Git service. Use the documented auth Worker and trusted service binding. The README warns about 100MB request bodies, large-push timeouts, inconsistent force pushes and silently dropped annotated tags. These limitations matter before migration. Source and configuration review establishes a deployment path and conditional costs; this candidate was not executed or load-tested. Check current Cloudflare pricing before deploying.

Is ripgit open source?+

The upstream repository declares the AGPL-3.0 license. Read its terms at https://raw.githubusercontent.com/deathbyknowledge/ripgit/21dba30423a38f0ecb5a9e2ce79bbfc4f2d36669/LICENSE. Source code and contributor credit are available at https://github.com/deathbyknowledge/ripgit.

Discussion · 0

sign in to comment →
No comments yet — be the first.