Skip to content
328 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships1---
2title: Run g1t yourself
3description: Start the core forge on your own machine with Docker Compose.
4---
5
6g1t is MIT licensed. You can run the core forge on your own machine:
7accounts, workspaces, repositories, git over HTTP, issues and pull
8requests, the site to browse them, and the REST API and MCP server. Your
9repositories are plain bare git repositories on a Docker volume.
10
11This is an early version. It is for trying g1t out and for small teams on
12a private network, not yet for an installation on the open internet.
13
14## What works and what is off
15
16| Feature | Self-hosted |
17| --- | --- |
18| Sign up, sign in, email confirmation | Works. Mail goes to the bundled Mailpit inbox. |
19| Workspaces, members, access tokens | Works |
20| Repositories: create, push and clone over HTTP, browse code, commits | Works. A fresh clone's pack is kept in the bundled object store, so the next clone of the same commit is served from there. |
21| Issues, comments, labels | Works |
The docs describe g1t as the place your coding agents work, not as an agent of its own. The guides to g1t's hosted agents are gone: agents, their abilities, access, budgets, memory, routines, sessions and skills, talking to agents, working with g1t, Customize, model providers, the AI Gateway, the context hub, spend, plans and the chat app's agent pages. Each old address redirects to the page that now covers what is left of it.22| Pull requests from a branch or from a fork, merged onto the default branch | Works, when the pull request is up to date with the default branch. |
23| The merge queue | Takes pull requests and shows them waiting. Testing and landing them needs g1t's runner, which is off: take a pull request out of the queue, or turn the queue off, to merge it. |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships24| [The REST API](/reference/api/), OAuth and [MCP](/reference/mcp/) | Work, on a port of their own: `http://localhost:8789`, with the MCP server at `http://localhost:8789/mcp` |
25| [Container images](/guides/containers/): `docker login`, push and pull at your `PUBLIC_URL` | Works, kept in the bundled object store, with no limit on a layer's size or on pulls |
26| Site search | Works |
27| A status page of your own | Works, at `http://localhost:8788` ([below](#the-status-page)) |
28| Webhooks, integrations | Work, retries included |
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.29| Sign in with GitHub, import from GitHub | Off until you register a GitHub App of your own ([below](#sign-in-with-github-and-import-from-github)). |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships30| Deployments on `g1t.page` | Off |
Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry.31| [Observability](/guides/observability/): logs, traces, metrics and errors sent with OpenTelemetry | Works. Send OTLP to `http://localhost:8789/otlp` with a project's ingest token. |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships32| Billing | Off. Nothing is charged, and no usage limit stops work. |
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today33| Site analytics | Off. Only g1t.sh sends page analytics (to HeyCatch, as its [privacy policy](https://g1t.sh/policies/privacy#how-the-site-is-used) describes); your installation sends none. |
The phone app with your own g1t, on g1t.sh/download until Google Play, and Node 24 (#5)34| [The phone app](/guides/mobile/) | Works: Chat, notifications, Code. Point it here from your download page's QR code ([below](#the-phone-app)). |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships35| Git over SSH and the `g1t` CLI | Not available yet |
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.36| Scheduled jobs | Run on their schedules inside the g1t container: webhook retries, purging deleted repositories, the packages sweep, security sweeps, audit log retention, access request summaries and emptying artifacts left in the trash for 30 days. Actions schedules (`on: schedule`) are not run. |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships37
38What hosted g1t cannot do yet either is on
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.39[What g1t can't do yet](/about/limitations/). What runs inside the
40installation, and what stands in for each part of g1t.sh, is in
41[How a self-hosted g1t runs](/guides/self-hosting-architecture/).
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships42
43## Before you start
44
45- Docker with Compose v2 (`docker compose version`).
46- About 4 GB of free disk space for the images.
47- Ports 8787, 8788, 8789 and 8025 free on your machine.
48
49## Start g1t
50
511. Get the source:
52
53 ```sh
54 git clone https://g1t.sh/flagon-io/g1t.git
55 cd g1t
56 ```
57
582. Build and start it. The first build compiles every service and takes a
59 while:
60
61 ```sh
62 docker compose -f deploy/self-host/docker-compose.yml up --build -d
63 ```
64
653. Open [http://localhost:8787](http://localhost:8787) and create an
66 account.
674. Open the Mailpit inbox at [http://localhost:8025](http://localhost:8025)
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)68 and enter the code from the confirmation email on the page the site
69 shows you, or follow the link in the same email. Set `IDENTITY_KEY`
70 (the setup does) so codes are kept as keyed hashes.
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships715. Create a workspace, then a repository.
72
73## Push and clone
74
75The remote is the site's address, then the workspace and repository:
76
77```sh
78git remote add origin http://localhost:8787/<workspace>/<repo>.git
79git push -u origin main
80```
81
82Git asks for a username and password: use your g1t username and password,
83or an access token, as described in [Git](/guides/git/#authentication).
84Public repositories clone without signing in:
85
86```sh
87git clone http://localhost:8787/<workspace>/<repo>.git
88```
89
90## Use the API and MCP
91
92The API answers at `http://localhost:8789`, the same routes as
93`https://api.g1t.sh` (see the [API reference](/reference/api/)). Make an
94access token under **Settings → Access tokens**, then:
95
96```sh
97curl -H "Authorization: Bearer $G1T_TOKEN" http://localhost:8789/user
98```
99
The docs describe g1t as the place your coding agents work, not as an agent of its own. The guides to g1t's hosted agents are gone: agents, their abilities, access, budgets, memory, routines, sessions and skills, talking to agents, working with g1t, Customize, model providers, the AI Gateway, the context hub, spend, plans and the chat app's agent pages. Each old address redirects to the page that now covers what is left of it.100The MCP server is at `http://localhost:8789/mcp`. Connect a coding agent
101to it as [Connect a coding agent](/guides/bring-your-own-agent/) shows,
102with this address in place of `https://mcp.g1t.sh`:
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships103
104```sh
105claude mcp add --transport http g1t http://localhost:8789/mcp
106```
107
108Applications that sign people in with OAuth find everything at
109`http://localhost:8789/.well-known/oauth-authorization-server`: the issuer
110is the API's address, and people approve on your site, at
111`PUBLIC_URL/oauth/authorize`. The site's clone box, agent setup and
112access token examples show your own addresses.
113
114## Check an installation
115
116`deploy/self-host/smoke.sh` checks an installation from end to end:
117
1181. It signs up a new account, confirms it through Mailpit, makes a
119 workspace and a repository, pushes and clones (twice, the second from
120 the clone pack cache), opens an issue and reads the code back through
121 the site.
1222. It makes an access token and calls the API, the OAuth metadata and the
123 MCP server with it.
1243. It publishes an npm package to your installation's registry and
125 installs it back.
1264. It opens a pull request from a branch and one from a fork, through the
127 API, and merges both onto `main`.
1285. It turns the merge queue on, merges a pull request into it, takes it
129 out again, and merges it with the queue off.
130
131```sh
132bash deploy/self-host/smoke.sh
133```
134
135To also run every scheduled job once, give it the command that does so
136inside the container:
137
138```sh
139SCHEDULER_ONCE="docker compose -f deploy/self-host/docker-compose.yml exec -T g1t \
140 node deploy/self-host/scheduler.mjs --once /data/generated/schedules.json" \
141 bash deploy/self-host/smoke.sh
142```
143
144It prints `All checks passed` when every step worked. It needs `curl`,
145`git`, `node` and `npm`; `PACKAGES=off` skips the npm package.
146
147## Settings
148
149Set these in the environment, or in a `.env` file next to
150`docker-compose.yml`:
151
152| Variable | Default | What it does |
153| --- | --- | --- |
154| `PUBLIC_URL` | `http://localhost:8787` | The address people use. Links in email, clone addresses and the site's link previews point here. |
155| `G1T_PORT` | `8787` | The port the site is published on |
156| `API_PORT` | `8789` | The port the API and the MCP server are published on |
157| `API_URL` | `PUBLIC_URL`'s host on `API_PORT` | The address of the API, as people and applications reach it. It is also the OAuth issuer. Set it when the API is behind a proxy, for example `https://api.git.example.com`. |
158| `MCP_URL` | `API_URL/mcp` | The address of the MCP server. |
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address159| `USERCONTENT_URL` | `PUBLIC_URL/-/usercontent` | Where [raw files](/guides/git/#raw-files) and avatars are served. Set it to a host of its own (another domain, not a subdomain of `PUBLIC_URL`'s, for example `https://files.example.net`) that your proxy sends to the same port as `PUBLIC_URL`, keeping the `Host` header, so a file in a repository can never reach the site's session cookie. Unset, they are served under `PUBLIC_URL`, still as plain text or images that cannot run script. |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships160| `MAILPIT_PORT` | `8025` | The port of the Mailpit inbox |
161| `MAIL_FROM` | `g1t <noreply@localhost>` | The sender of g1t's email |
162| `MAIL_URL` | `http://mailpit:8025` | The Mailpit server g1t sends mail through |
163| `S3_ENDPOINT`, `S3_BUCKET`, `S3_REGION`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` | the bundled RustFS, bucket `g1t-packages` | Where packages' files are kept: any S3-compatible store. Change the two keys before first start; RustFS is made with them. |
164| `S3_PUBLIC_ENDPOINT` | (none) | The store's address as clients reach it. When set, large layers are downloaded from it directly with a signed URL. |
165| `PACK_S3_BUCKET` | `g1t-git-packs` | The bucket on the same store that packs for fresh clones are kept in, so the next clone of the same commit is not built again. The bundled store deletes packs after 7 days, and uploads left unfinished after a day; on another store, give the bucket a lifecycle rule that does the same. |
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.166| `DOCS_S3_BUCKET` | `g1t-docs-files` | The bucket on the same store that images and files added to pages and [artifacts](/guides/artifacts/) are kept in. |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships167| `RUSTFS_IMAGE` | `rustfs/rustfs:1.0.1` | The image the bundled object store runs: [RustFS](https://rustfs.com), an S3-compatible server. |
168| `AWS_CLI_IMAGE` | `amazon/aws-cli:2.37.10` | The image `storage-setup` makes the buckets and the packs' lifecycle rule with. |
169| `BACKUP_S3_BUCKET` | `g1t-backups` | The bucket on the same store that nightly repository backups (a `git bundle` of each repository whose branches or tags changed) are kept in. The bundles are cut by g1t's runner, which this installation does not run yet, so the bucket stays empty for now: copy the volumes, as below. |
170| `STATUS_PORT` | `8788` | The port the status page is published on |
Merge branch 'worktree-agent-a6da31375506fc440'171| `INVITES_PER_USER` | `20` | How many friends each person may invite by email with [Invite a friend](/guides/authentication/#invite-a-friend). `0` turns it off. Anyone may register with or without an invite. |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships172| `STATUS_PROBE_REPO` | (none) | A public repository, `workspace/repo`, whose branches the status page lists every minute as a clone would. Empty: git is not checked. |
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.173| `TURNSTILE_SITE_KEY`, `TURNSTILE_SECRET` | (none) | A [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) widget's site key and secret key, to check that a person fills in the sign-up form. With either unset, sign-up has no such check. |
The phone app with your own g1t, on g1t.sh/download until Google Play, and Node 24 (#5)174| `PHONE_PUSH` | `on` | `off`: send [the phone app](#the-phone-app) no notifications, so their words never pass through Expo's push service. Browser and desktop notifications are not affected. |
175
176## The phone app
177
178[The g1t phone app](/guides/mobile/) works with your installation as it
179does with g1t.sh: the same sign-in (OAuth, approved on your site), Chat
180live, notifications and Code. Nothing is set up for it here.
181
1821. Make the installation reachable from a phone: `PUBLIC_URL` and
183 `API_URL` set to addresses the phone can open, such as
184 `https://git.example.com` and `https://api.git.example.com` behind your
185 proxy. Store builds of the app open `https` addresses only.
1862. People open `PUBLIC_URL/download` and scan the code under **On your
187 phone**, or tap **Open in the g1t app** on the phone. The link is
188 `g1t://connect?server=<PUBLIC_URL>`; you can hand it out yourself.
1893. The app reads `PUBLIC_URL/.well-known/g1t.json` for where the API is,
190 and signs in there.
191
192Notifications reach phones through the push service of g1t's app on Expo,
193which hands them to Google's (Android) or Apple's (iOS): your installation
194sends each one there, with its title and a line of the message, as g1t.sh
195does. No account or key is needed. To send phones nothing, set
196`PHONE_PUSH=off`: the app then tells people your g1t sends no
197notifications, and everything else works.
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships198
199## The status page
200
201The `status` service runs the same status page as
202[status.g1t.sh](/guides/status/), in a process of its own, so it keeps
203answering when the site does not. Open
204[http://localhost:8788](http://localhost:8788).
205
206Every minute it loads the site's sign-in page from inside Compose, and,
207with `STATUS_PROBE_REPO` set, lists that repository's branches. It keeps
20890 days of history on its own volume, `g1t-status`. Parts an installation
The docs describe g1t as the place your coding agents work, not as an agent of its own. The guides to g1t's hosted agents are gone: agents, their abilities, access, budgets, memory, routines, sessions and skills, talking to agents, working with g1t, Customize, model providers, the AI Gateway, the context hub, spend, plans and the chat app's agent pages. Each old address redirects to the page that now covers what is left of it.209of your own does not check (the API, MCP, docs, deployments and billing)
210are left off its page.
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships211
212The links to **Status** in the site's footer and account menu still point
213to status.g1t.sh; pointing them at your own status page is not a setting
214yet.
215
216To deliver email to real inboxes, have Mailpit relay it through your SMTP
217server. The settings are in `docker-compose.yml`, under `mailpit`.
218
219Sign-in cookies are marked `Secure`. Browsers accept them on
220`http://localhost`. On any other address, put g1t behind HTTPS (a reverse
221proxy such as Caddy or nginx with a certificate) and set `PUBLIC_URL` to
222the `https://` address.
223
224## Sign in with GitHub and import from GitHub
225
226g1t.sh's GitHub App works only for g1t.sh. To offer **Continue with
227GitHub** and **Import from GitHub** on your own g1t, register an app of
228your own. Without one, neither button appears.
229
2301. On GitHub, open **Settings → Developer settings → GitHub Apps → New
231 GitHub App** (or the same under an organization's settings).
2322. Fill it in, with `PUBLIC_URL` standing for your g1t's address:
233
234 | Setting | Value |
235 | --- | --- |
236 | Callback URL | `PUBLIC_URL/auth/github/callback` |
237 | Expire user authorization tokens | On |
238 | Request user authorization (OAuth) during installation | Off |
239 | Enable Device Flow | Off |
240 | Setup URL | `PUBLIC_URL/integrations/github/setup` |
241 | Redirect on update | On |
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.242 | Webhook | On, with the URL `API_URL/hooks/github` and a secret you choose, once GitHub can reach your API. Otherwise off: imports work without it. |
243 | Repository permissions | Contents: Read; Metadata: Read; Issues: Read |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships244 | Account permissions | Email addresses: Read |
245
2463. Create it, then on its page note the **App ID**, the **Client ID** and
247 the slug (the last part of its public address,
248 `github.com/apps/<slug>`). Generate a **client secret** and a **private
249 key**, which downloads a `.pem` file.
2504. Set these before starting g1t, in the environment or in `.env`:
251
252 | Variable | Value |
253 | --- | --- |
254 | `GITHUB_APP_ID` | The App ID |
255 | `GITHUB_APP_SLUG` | The slug |
256 | `GITHUB_APP_CLIENT_ID` | The Client ID |
257 | `GITHUB_APP_CLIENT_SECRET` | The client secret |
258 | `GITHUB_APP_PRIVATE_KEY` | The `.pem` file's contents, as downloaded. Line breaks may be written as `\n`. |
259 | `GITHUB_APP_WEBHOOK_SECRET` | The webhook secret, if the webhook is on |
260
2615. Restart g1t: `docker compose -f deploy/self-host/docker-compose.yml up -d`.
262
263g1t makes its own key for the GitHub tokens it keeps (`IDENTITY_KEY`, in
264the `g1t-data` volume) on first start. What the app can do, and what comes
265across from GitHub, is in [GitHub](/guides/github/).
266
267## Where your data lives
268
269| Volume | Holds |
270| --- | --- |
271| `g1t_g1t-data` | Accounts, workspaces, issues and every other record, as SQLite files; the keys that seal stored secrets (`keys.env`) |
272| `g1t_g1t-git` | Your repositories, one bare git repository each |
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.273| `g1t_g1t-objects` | The bundled object store (RustFS): container images' layers and other package files in `g1t-packages`, the `g1t-backups` bucket, the clone packs in `g1t-git-packs`, and files added to pages and artifacts in `g1t-docs-files` |
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships274| `g1t_g1t-secrets` | The key the site and the git store share |
275
276To back up, stop g1t and copy the volumes:
277
278```sh
279docker compose -f deploy/self-host/docker-compose.yml stop
280docker run --rm -v g1t_g1t-data:/data -v g1t_g1t-git:/git -v "$PWD":/backup \
281 debian:bookworm-slim tar czf /backup/g1t-backup.tgz /data /git
282docker compose -f deploy/self-host/docker-compose.yml start
283```
284
The docs describe g1t as the place your coding agents work, not as an agent of its own. The guides to g1t's hosted agents are gone: agents, their abilities, access, budgets, memory, routines, sessions and skills, talking to agents, working with g1t, Customize, model providers, the AI Gateway, the context hub, spend, plans and the chat app's agent pages. Each old address redirects to the page that now covers what is left of it.285Keep `keys.env` with the backup. Without it, saved webhook, integration
286and Actions secrets cannot be opened.
Artifacts: an hourly view of operations and errors, and SSH must meter its git before it ships287
288## Upgrade
289
290Pull the new source and rebuild. Database changes are applied on start,
291and changes already applied are skipped:
292
293```sh
294git pull
295docker compose -f deploy/self-host/docker-compose.yml up --build -d
296```
297
298### Installations started before 7 October 2026
299
300These kept packages' files and backups in MinIO, in the `g1t_g1t-packages`
301volume. The bundled store is now RustFS, in `g1t_g1t-objects`, and starts
302empty. After the upgrade above, copy the old objects across (use your own
303`S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY` if you changed them):
304
305```sh
306docker run -d --name g1t-old-store --network g1t_default \
307 -v g1t_g1t-packages:/data -e MINIO_ROOT_USER=g1t \
308 -e MINIO_ROOT_PASSWORD=g1t-packages-secret pgsty/minio server /data
309docker run --rm --network g1t_default -e AWS_ACCESS_KEY_ID=g1t \
310 -e AWS_SECRET_ACCESS_KEY=g1t-packages-secret -e AWS_DEFAULT_REGION=us-east-1 \
311 --entrypoint sh amazon/aws-cli:2.37.10 -c '
312 for b in g1t-packages g1t-backups; do
313 aws --endpoint-url http://g1t-old-store:9000 s3 sync "s3://$b" "/tmp/$b" &&
314 aws --endpoint-url http://rustfs:9000 s3 sync "/tmp/$b" "s3://$b"
315 done'
316docker rm -f g1t-old-store
317```
318
319The clone packs are not copied: they are a cache, and are made again on
320the next clone. Once your images and packages pull, remove the old volume
321with `docker volume rm g1t_g1t-packages`.
322
323## Stop and remove
324
325```sh
326docker compose -f deploy/self-host/docker-compose.yml down # keeps your data
327docker compose -f deploy/self-host/docker-compose.yml down -v # deletes it
328```

This file's history is long; its oldest lines are credited to the oldest commit read.