Skip to content
301 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.

Running g1t yourself: the design, a docker compose proof, and a guide to what works today1---
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
Merge branch 'worktree-agent-aaf03bdceac799c89'8requests, the site to browse them, and the REST API and MCP server. Your
9repositories are plain bare git repositories on a Docker volume.
Running g1t yourself: the design, a docker compose proof, and a guide to what works today10
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 |
Merge branch 'worktree-agent-af58ac8933b0dd125'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. |
Running g1t yourself: the design, a docker compose proof, and a guide to what works today21| Issues, comments, labels | Works |
Merge branch 'worktree-agent-aaf03bdceac799c89'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. Bringing one up to date first needs g1t's agent, which is off. |
23| The merge queue | Takes pull requests and shows them waiting. Testing and landing them needs g1t's agent, which is off: take a pull request out of the queue, or turn the queue off, to merge it. |
24| [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` |
Merge branch 'worktree-agent-af58ac8933b0dd125'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 |
Running g1t yourself: the design, a docker compose proof, and a guide to what works today26| Site search | Works |
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas27| A status page of your own | Works, at `http://localhost:8788` ([below](#the-status-page)) |
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member28| Webhooks, integrations | Work, retries included |
Merge branch 'worktree-agent-aaf03bdceac799c89'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)). Mirrors sync on GitHub's webhook once GitHub can reach your API, and with **Sync now** either way. |
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent30| g1t's agent: changes, plans and reviews | Off |
Running g1t yourself: the design, a docker compose proof, and a guide to what works today31| Context hub search | Off |
32| Deployments on `g1t.page` | Off |
33| Billing | Off. Nothing is charged, and no usage limit stops work. |
Merge branch 'worktree-agent-aaf03bdceac799c89'34| Git over SSH and the `g1t` CLI | Not available yet |
35| Scheduled jobs | Run on their schedules inside the g1t container: webhook retries, purging deleted repositories, the packages sweep, security sweeps, audit log retention and access request summaries. Actions schedules (`on: schedule`) are not run. |
Running g1t yourself: the design, a docker compose proof, and a guide to what works today36
What g1t can't do yet, and an open letter to Cloudflare37What hosted g1t cannot do yet either is on
38[What g1t can't do yet](/about/limitations/).
39
Running g1t yourself: the design, a docker compose proof, and a guide to what works today40## Before you start
41
42- Docker with Compose v2 (`docker compose version`).
43- About 4 GB of free disk space for the images.
Merge branch 'worktree-agent-aaf03bdceac799c89'44- Ports 8787, 8788, 8789 and 8025 free on your machine.
Running g1t yourself: the design, a docker compose proof, and a guide to what works today45
46## Start g1t
47
481. Get the source:
49
50 ```sh
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look51 git clone https://g1t.sh/flagon-io/g1t.git
Running g1t yourself: the design, a docker compose proof, and a guide to what works today52 cd g1t
53 ```
54
552. Build and start it. The first build compiles every service and takes a
56 while:
57
58 ```sh
59 docker compose -f deploy/self-host/docker-compose.yml up --build -d
60 ```
61
623. Open [http://localhost:8787](http://localhost:8787) and create an
63 account.
644. 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)65 and enter the code from the confirmation email on the page the site
66 shows you, or follow the link in the same email. Set `IDENTITY_KEY`
67 (the setup does) so codes are kept as keyed hashes.
Running g1t yourself: the design, a docker compose proof, and a guide to what works today685. Create a workspace, then a repository.
69
70## Push and clone
71
72The remote is the site's address, then the workspace and repository:
73
74```sh
75git remote add origin http://localhost:8787/<workspace>/<repo>.git
76git push -u origin main
77```
78
79Git asks for a username and password: use your g1t username and password,
80or an access token, as described in [Git](/guides/git/#authentication).
81Public repositories clone without signing in:
82
83```sh
84git clone http://localhost:8787/<workspace>/<repo>.git
85```
86
Merge branch 'worktree-agent-aaf03bdceac799c89'87## Use the API and MCP
88
89The API answers at `http://localhost:8789`, the same routes as
90`https://api.g1t.sh` (see the [API reference](/reference/api/)). Make an
91access token under **Settings → Access tokens**, then:
92
93```sh
94curl -H "Authorization: Bearer $G1T_TOKEN" http://localhost:8789/user
95```
96
97The MCP server is at `http://localhost:8789/mcp`. Connect an agent to it
98as [Bring your own agent](/guides/bring-your-own-agent/) shows, with this
99address in place of `https://mcp.g1t.sh`:
100
101```sh
102claude mcp add --transport http g1t http://localhost:8789/mcp
103```
104
105Applications that sign people in with OAuth find everything at
106`http://localhost:8789/.well-known/oauth-authorization-server`: the issuer
107is the API's address, and people approve on your site, at
108`PUBLIC_URL/oauth/authorize`. The site's clone box, agent setup and
109access token examples show your own addresses.
110
Running g1t yourself: the design, a docker compose proof, and a guide to what works today111## Check an installation
112
Merge branch 'worktree-agent-aaf03bdceac799c89'113`deploy/self-host/smoke.sh` checks an installation from end to end:
Running g1t yourself: the design, a docker compose proof, and a guide to what works today114
Merge branch 'worktree-agent-aaf03bdceac799c89'1151. It signs up a new account, confirms it through Mailpit, makes a
116 workspace and a repository, pushes and clones (twice, the second from
117 the clone pack cache), opens an issue and reads the code back through
118 the site.
1192. It makes an access token and calls the API, the OAuth metadata and the
120 MCP server with it.
Merge branch 'worktree-agent-af58ac8933b0dd125'1213. It publishes an npm package to your installation's registry and
122 installs it back.
1234. It opens a pull request from a branch and one from a fork, through the
Merge branch 'worktree-agent-aaf03bdceac799c89'124 API, and merges both onto `main`.
Merge branch 'worktree-agent-af58ac8933b0dd125'1255. It turns the merge queue on, merges a pull request into it, takes it
Merge branch 'worktree-agent-aaf03bdceac799c89'126 out again, and merges it with the queue off.
127
Running g1t yourself: the design, a docker compose proof, and a guide to what works today128```sh
129bash deploy/self-host/smoke.sh
130```
131
Merge branch 'worktree-agent-aaf03bdceac799c89'132To also run every scheduled job once, give it the command that does so
133inside the container:
Running g1t yourself: the design, a docker compose proof, and a guide to what works today134
Merge branch 'worktree-agent-aaf03bdceac799c89'135```sh
136SCHEDULER_ONCE="docker compose -f deploy/self-host/docker-compose.yml exec -T g1t \
137 node deploy/self-host/scheduler.mjs --once /data/generated/schedules.json" \
138 bash deploy/self-host/smoke.sh
139```
140
141It prints `All checks passed` when every step worked. It needs `curl`,
Merge branch 'worktree-agent-af58ac8933b0dd125'142`git`, `node` and `npm`; `PACKAGES=off` skips the npm package.
Merge branch 'worktree-agent-aaf03bdceac799c89'143
Running g1t yourself: the design, a docker compose proof, and a guide to what works today144## Settings
145
146Set these in the environment, or in a `.env` file next to
147`docker-compose.yml`:
148
149| Variable | Default | What it does |
150| --- | --- | --- |
Merge branch 'worktree-agent-aaf03bdceac799c89'151| `PUBLIC_URL` | `http://localhost:8787` | The address people use. Links in email, clone addresses and the site's link previews point here. |
Running g1t yourself: the design, a docker compose proof, and a guide to what works today152| `G1T_PORT` | `8787` | The port the site is published on |
Merge branch 'worktree-agent-aaf03bdceac799c89'153| `API_PORT` | `8789` | The port the API and the MCP server are published on |
154| `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`. |
155| `MCP_URL` | `API_URL/mcp` | The address of the MCP server. |
Running g1t yourself: the design, a docker compose proof, and a guide to what works today156| `MAILPIT_PORT` | `8025` | The port of the Mailpit inbox |
157| `MAIL_FROM` | `g1t <noreply@localhost>` | The sender of g1t's email |
158| `MAIL_URL` | `http://mailpit:8025` | The Mailpit server g1t sends mail through |
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look159| `REGISTRATION_MODE` | `open` | `open`: anyone can make an account. `invite`: every new account needs an [invite](/guides/authentication/#invites), as on g1t.sh. |
160| `INVITES_PER_USER` | `5` | How many invites each person can have out, while `REGISTRATION_MODE` is `invite` |
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas161| `WAITLIST_NOTIFY_EMAIL` | (none) | Where a summary of new access requests goes, at most every 15 minutes. Empty sends none; requests still wait for you in the database. |
Merge branch 'worktree-agent-af58ac8933b0dd125'162| `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. |
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member163| `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. |
Merge branch 'worktree-agent-af58ac8933b0dd125'164| `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. |
165| `RUSTFS_IMAGE` | `rustfs/rustfs:1.0.1` | The image the bundled object store runs: [RustFS](https://rustfs.com), an S3-compatible server. |
166| `AWS_CLI_IMAGE` | `amazon/aws-cli:2.37.10` | The image `storage-setup` makes the buckets and the packs' lifecycle rule with. |
Merge branch 'worktree-agent-ac5b181a013e54348'167| `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. |
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas168| `STATUS_PORT` | `8788` | The port the status page is published on |
169| `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. |
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look170| `INVITE_STAFF_WORKSPACES` | (none) | Workspace slugs, comma separated, whose owners can make invites without a limit. Set it to your own workspace before you switch to `invite`, so someone can invite the first people. |
Running g1t yourself: the design, a docker compose proof, and a guide to what works today171
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas172## The status page
173
174The `status` service runs the same status page as
175[status.g1t.sh](/guides/status/), in a process of its own, so it keeps
176answering when the site does not. Open
177[http://localhost:8788](http://localhost:8788).
178
179Every minute it loads the site's sign-in page from inside Compose, and,
180with `STATUS_PROBE_REPO` set, lists that repository's branches. It keeps
18190 days of history on its own volume, `g1t-status`. Parts an installation
Merge branch 'worktree-agent-aaf03bdceac799c89'182of your own does not check (the API, MCP, docs, deployments, the model
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas183proxy and billing) are left off its page.
184
185The links to **Status** in the site's footer and account menu still point
186to status.g1t.sh; pointing them at your own status page is not a setting
187yet.
188
Running g1t yourself: the design, a docker compose proof, and a guide to what works today189To deliver email to real inboxes, have Mailpit relay it through your SMTP
190server. The settings are in `docker-compose.yml`, under `mailpit`.
191
192Sign-in cookies are marked `Secure`. Browsers accept them on
193`http://localhost`. On any other address, put g1t behind HTTPS (a reverse
194proxy such as Caddy or nginx with a certificate) and set `PUBLIC_URL` to
195the `https://` address.
196
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look197## Sign in with GitHub and import from GitHub
198
199g1t.sh's GitHub App works only for g1t.sh. To offer **Continue with
200GitHub** and **Import from GitHub** on your own g1t, register an app of
201your own. Without one, neither button appears.
202
2031. On GitHub, open **Settings → Developer settings → GitHub Apps → New
204 GitHub App** (or the same under an organization's settings).
2052. Fill it in, with `PUBLIC_URL` standing for your g1t's address:
206
207 | Setting | Value |
208 | --- | --- |
209 | Callback URL | `PUBLIC_URL/auth/github/callback` |
210 | Expire user authorization tokens | On |
211 | Request user authorization (OAuth) during installation | Off |
212 | Enable Device Flow | Off |
213 | Setup URL | `PUBLIC_URL/integrations/github/setup` |
214 | Redirect on update | On |
Merge branch 'worktree-agent-aaf03bdceac799c89'215 | Webhook | On, with the URL `API_URL/hooks/github` and a secret you choose, once GitHub can reach your API. Otherwise off: mirrors then sync with **Sync now**. |
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look216 | Repository permissions | Contents: Read and write; Metadata: Read; Issues: Read |
217 | Account permissions | Email addresses: Read |
218
2193. Create it, then on its page note the **App ID**, the **Client ID** and
220 the slug (the last part of its public address,
221 `github.com/apps/<slug>`). Generate a **client secret** and a **private
222 key**, which downloads a `.pem` file.
2234. Set these before starting g1t, in the environment or in `.env`:
224
225 | Variable | Value |
226 | --- | --- |
227 | `GITHUB_APP_ID` | The App ID |
228 | `GITHUB_APP_SLUG` | The slug |
229 | `GITHUB_APP_CLIENT_ID` | The Client ID |
230 | `GITHUB_APP_CLIENT_SECRET` | The client secret |
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily231 | `GITHUB_APP_PRIVATE_KEY` | The `.pem` file's contents, as downloaded. Line breaks may be written as `\n`. |
Merge branch 'worktree-agent-aaf03bdceac799c89'232 | `GITHUB_APP_WEBHOOK_SECRET` | The webhook secret, if the webhook is on |
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look233
2345. Restart g1t: `docker compose -f deploy/self-host/docker-compose.yml up -d`.
235
236g1t makes its own key for the GitHub tokens it keeps (`IDENTITY_KEY`, in
237the `g1t-data` volume) on first start. What the app can do, and what comes
238across from GitHub, is in [GitHub](/guides/github/).
239
Running g1t yourself: the design, a docker compose proof, and a guide to what works today240## Where your data lives
241
242| Volume | Holds |
243| --- | --- |
244| `g1t_g1t-data` | Accounts, workspaces, issues and every other record, as SQLite files; the keys that seal stored secrets (`keys.env`) |
245| `g1t_g1t-git` | Your repositories, one bare git repository each |
Merge branch 'worktree-agent-af58ac8933b0dd125'246| `g1t_g1t-objects` | The bundled object store (RustFS): container images' layers and other package files in `g1t-packages`, the `g1t-backups` bucket and the clone packs in `g1t-git-packs` |
Running g1t yourself: the design, a docker compose proof, and a guide to what works today247| `g1t_g1t-secrets` | The key the site and the git store share |
248
249To back up, stop g1t and copy the volumes:
250
251```sh
252docker compose -f deploy/self-host/docker-compose.yml stop
253docker run --rm -v g1t_g1t-data:/data -v g1t_g1t-git:/git -v "$PWD":/backup \
254 debian:bookworm-slim tar czf /backup/g1t-backup.tgz /data /git
255docker compose -f deploy/self-host/docker-compose.yml start
256```
257
258Keep `keys.env` with the backup. Without it, saved webhook, integration and
259Actions secrets cannot be opened.
260
261## Upgrade
262
263Pull the new source and rebuild. Database changes are applied on start,
264and changes already applied are skipped:
265
266```sh
267git pull
268docker compose -f deploy/self-host/docker-compose.yml up --build -d
269```
270
Merge branch 'worktree-agent-af58ac8933b0dd125'271### Installations started before 7 October 2026
272
273These kept packages' files and backups in MinIO, in the `g1t_g1t-packages`
274volume. The bundled store is now RustFS, in `g1t_g1t-objects`, and starts
275empty. After the upgrade above, copy the old objects across (use your own
276`S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY` if you changed them):
277
278```sh
279docker run -d --name g1t-old-store --network g1t_default \
280 -v g1t_g1t-packages:/data -e MINIO_ROOT_USER=g1t \
281 -e MINIO_ROOT_PASSWORD=g1t-packages-secret pgsty/minio server /data
282docker run --rm --network g1t_default -e AWS_ACCESS_KEY_ID=g1t \
283 -e AWS_SECRET_ACCESS_KEY=g1t-packages-secret -e AWS_DEFAULT_REGION=us-east-1 \
284 --entrypoint sh amazon/aws-cli:2.37.10 -c '
285 for b in g1t-packages g1t-backups; do
286 aws --endpoint-url http://g1t-old-store:9000 s3 sync "s3://$b" "/tmp/$b" &&
287 aws --endpoint-url http://rustfs:9000 s3 sync "/tmp/$b" "s3://$b"
288 done'
289docker rm -f g1t-old-store
290```
291
292The clone packs are not copied: they are a cache, and are made again on
293the next clone. Once your images and packages pull, remove the old volume
294with `docker volume rm g1t_g1t-packages`.
295
Running g1t yourself: the design, a docker compose proof, and a guide to what works today296## Stop and remove
297
298```sh
299docker compose -f deploy/self-host/docker-compose.yml down # keeps your data
300docker compose -f deploy/self-host/docker-compose.yml down -v # deletes it
301```

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