| 1 | --- |
| 2 | title: Run g1t yourself |
| 3 | description: Start the core forge on your own machine with Docker Compose. |
| 4 | --- |
| 5 | |
| 6 | g1t is MIT licensed. You can run the core forge on your own machine: |
| 7 | accounts, workspaces, repositories, git over HTTP, issues and pull |
| 8 | requests, the site to browse them, and the REST API and MCP server. Your |
| 9 | repositories are plain bare git repositories on a Docker volume. |
| 10 | |
| 11 | This is an early version. It is for trying g1t out and for small teams on |
| 12 | a 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 MinIO, so the next clone of the same commit is served from there. | |
| 21 | | Issues, comments, labels | Works | |
| 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` | |
| 25 | | [Container images](/guides/containers/): `docker login`, push and pull at your `PUBLIC_URL` | Works, kept in the bundled MinIO, 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 | |
| 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. | |
| 30 | | g1t's agent: changes, plans and reviews | Off | |
| 31 | | Context hub search | Off | |
| 32 | | Deployments on `g1t.page` | Off | |
| 33 | | Billing | Off. Nothing is charged, and no usage limit stops work. | |
| 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. | |
| 36 | |
| 37 | What hosted g1t cannot do yet either is on |
| 38 | [What g1t can't do yet](/about/limitations/). |
| 39 | |
| 40 | ## Before you start |
| 41 | |
| 42 | - Docker with Compose v2 (`docker compose version`). |
| 43 | - About 4 GB of free disk space for the images. |
| 44 | - Ports 8787, 8788, 8789 and 8025 free on your machine. |
| 45 | |
| 46 | ## Start g1t |
| 47 | |
| 48 | 1. Get the source: |
| 49 | |
| 50 | ```sh |
| 51 | git clone https://g1t.sh/flagon-io/g1t.git |
| 52 | cd g1t |
| 53 | ``` |
| 54 | |
| 55 | 2. 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 | |
| 62 | 3. Open [http://localhost:8787](http://localhost:8787) and create an |
| 63 | account. |
| 64 | 4. Open the Mailpit inbox at [http://localhost:8025](http://localhost:8025) |
| 65 | and follow the link in the confirmation email. |
| 66 | 5. Create a workspace, then a repository. |
| 67 | |
| 68 | ## Push and clone |
| 69 | |
| 70 | The remote is the site's address, then the workspace and repository: |
| 71 | |
| 72 | ```sh |
| 73 | git remote add origin http://localhost:8787/<workspace>/<repo>.git |
| 74 | git push -u origin main |
| 75 | ``` |
| 76 | |
| 77 | Git asks for a username and password: use your g1t username and password, |
| 78 | or an access token, as described in [Git](/guides/git/#authentication). |
| 79 | Public repositories clone without signing in: |
| 80 | |
| 81 | ```sh |
| 82 | git clone http://localhost:8787/<workspace>/<repo>.git |
| 83 | ``` |
| 84 | |
| 85 | ## Use the API and MCP |
| 86 | |
| 87 | The API answers at `http://localhost:8789`, the same routes as |
| 88 | `https://api.g1t.sh` (see the [API reference](/reference/api/)). Make an |
| 89 | access token under **Settings → Access tokens**, then: |
| 90 | |
| 91 | ```sh |
| 92 | curl -H "Authorization: Bearer $G1T_TOKEN" http://localhost:8789/user |
| 93 | ``` |
| 94 | |
| 95 | The MCP server is at `http://localhost:8789/mcp`. Connect an agent to it |
| 96 | as [Bring your own agent](/guides/bring-your-own-agent/) shows, with this |
| 97 | address in place of `https://mcp.g1t.sh`: |
| 98 | |
| 99 | ```sh |
| 100 | claude mcp add --transport http g1t http://localhost:8789/mcp |
| 101 | ``` |
| 102 | |
| 103 | Applications that sign people in with OAuth find everything at |
| 104 | `http://localhost:8789/.well-known/oauth-authorization-server`: the issuer |
| 105 | is the API's address, and people approve on your site, at |
| 106 | `PUBLIC_URL/oauth/authorize`. The site's clone box, agent setup and |
| 107 | access token examples show your own addresses. |
| 108 | |
| 109 | ## Check an installation |
| 110 | |
| 111 | `deploy/self-host/smoke.sh` checks an installation from end to end: |
| 112 | |
| 113 | 1. It signs up a new account, confirms it through Mailpit, makes a |
| 114 | workspace and a repository, pushes and clones (twice, the second from |
| 115 | the clone pack cache), opens an issue and reads the code back through |
| 116 | the site. |
| 117 | 2. It makes an access token and calls the API, the OAuth metadata and the |
| 118 | MCP server with it. |
| 119 | 3. It opens a pull request from a branch and one from a fork, through the |
| 120 | API, and merges both onto `main`. |
| 121 | 4. It turns the merge queue on, merges a pull request into it, takes it |
| 122 | out again, and merges it with the queue off. |
| 123 | |
| 124 | ```sh |
| 125 | bash deploy/self-host/smoke.sh |
| 126 | ``` |
| 127 | |
| 128 | To also run every scheduled job once, give it the command that does so |
| 129 | inside the container: |
| 130 | |
| 131 | ```sh |
| 132 | SCHEDULER_ONCE="docker compose -f deploy/self-host/docker-compose.yml exec -T g1t \ |
| 133 | node deploy/self-host/scheduler.mjs --once /data/generated/schedules.json" \ |
| 134 | bash deploy/self-host/smoke.sh |
| 135 | ``` |
| 136 | |
| 137 | It prints `All checks passed` when every step worked. It needs `curl`, |
| 138 | `git` and `node`. |
| 139 | |
| 140 | ## Settings |
| 141 | |
| 142 | Set these in the environment, or in a `.env` file next to |
| 143 | `docker-compose.yml`: |
| 144 | |
| 145 | | Variable | Default | What it does | |
| 146 | | --- | --- | --- | |
| 147 | | `PUBLIC_URL` | `http://localhost:8787` | The address people use. Links in email, clone addresses and the site's link previews point here. | |
| 148 | | `G1T_PORT` | `8787` | The port the site is published on | |
| 149 | | `API_PORT` | `8789` | The port the API and the MCP server are published on | |
| 150 | | `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`. | |
| 151 | | `MCP_URL` | `API_URL/mcp` | The address of the MCP server. | |
| 152 | | `MAILPIT_PORT` | `8025` | The port of the Mailpit inbox | |
| 153 | | `MAIL_FROM` | `g1t <noreply@localhost>` | The sender of g1t's email | |
| 154 | | `MAIL_URL` | `http://mailpit:8025` | The Mailpit server g1t sends mail through | |
| 155 | | `REGISTRATION_MODE` | `open` | `open`: anyone can make an account. `invite`: every new account needs an [invite](/guides/authentication/#invites), as on g1t.sh. | |
| 156 | | `INVITES_PER_USER` | `5` | How many invites each person can have out, while `REGISTRATION_MODE` is `invite` | |
| 157 | | `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. | |
| 158 | | `S3_ENDPOINT`, `S3_BUCKET`, `S3_REGION`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` | the bundled MinIO, bucket `g1t-packages` | Where packages' files are kept: any S3-compatible store. Change the two keys before first start; MinIO is made with them. | |
| 159 | | `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. | |
| 160 | | `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 MinIO deletes packs after 7 days; on another store, give the bucket a rule that expires objects under `packs/` and unfinished multipart uploads. | |
| 161 | | `MINIO_IMAGE` | `pgsty/minio:latest` | The MinIO server image the bundled store runs. MinIO no longer publishes its own images; this is a community build of the same server. | |
| 162 | | `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. | |
| 163 | | `STATUS_PORT` | `8788` | The port the status page is published on | |
| 164 | | `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. | |
| 165 | | `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. | |
| 166 | |
| 167 | ## The status page |
| 168 | |
| 169 | The `status` service runs the same status page as |
| 170 | [status.g1t.sh](/guides/status/), in a process of its own, so it keeps |
| 171 | answering when the site does not. Open |
| 172 | [http://localhost:8788](http://localhost:8788). |
| 173 | |
| 174 | Every minute it loads the site's sign-in page from inside Compose, and, |
| 175 | with `STATUS_PROBE_REPO` set, lists that repository's branches. It keeps |
| 176 | 90 days of history on its own volume, `g1t-status`. Parts an installation |
| 177 | of your own does not check (the API, MCP, docs, deployments, the model |
| 178 | proxy and billing) are left off its page. |
| 179 | |
| 180 | The links to **Status** in the site's footer and account menu still point |
| 181 | to status.g1t.sh; pointing them at your own status page is not a setting |
| 182 | yet. |
| 183 | |
| 184 | To deliver email to real inboxes, have Mailpit relay it through your SMTP |
| 185 | server. The settings are in `docker-compose.yml`, under `mailpit`. |
| 186 | |
| 187 | Sign-in cookies are marked `Secure`. Browsers accept them on |
| 188 | `http://localhost`. On any other address, put g1t behind HTTPS (a reverse |
| 189 | proxy such as Caddy or nginx with a certificate) and set `PUBLIC_URL` to |
| 190 | the `https://` address. |
| 191 | |
| 192 | ## Sign in with GitHub and import from GitHub |
| 193 | |
| 194 | g1t.sh's GitHub App works only for g1t.sh. To offer **Continue with |
| 195 | GitHub** and **Import from GitHub** on your own g1t, register an app of |
| 196 | your own. Without one, neither button appears. |
| 197 | |
| 198 | 1. On GitHub, open **Settings → Developer settings → GitHub Apps → New |
| 199 | GitHub App** (or the same under an organization's settings). |
| 200 | 2. Fill it in, with `PUBLIC_URL` standing for your g1t's address: |
| 201 | |
| 202 | | Setting | Value | |
| 203 | | --- | --- | |
| 204 | | Callback URL | `PUBLIC_URL/auth/github/callback` | |
| 205 | | Expire user authorization tokens | On | |
| 206 | | Request user authorization (OAuth) during installation | Off | |
| 207 | | Enable Device Flow | Off | |
| 208 | | Setup URL | `PUBLIC_URL/integrations/github/setup` | |
| 209 | | Redirect on update | On | |
| 210 | | 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**. | |
| 211 | | Repository permissions | Contents: Read and write; Metadata: Read; Issues: Read | |
| 212 | | Account permissions | Email addresses: Read | |
| 213 | |
| 214 | 3. Create it, then on its page note the **App ID**, the **Client ID** and |
| 215 | the slug (the last part of its public address, |
| 216 | `github.com/apps/<slug>`). Generate a **client secret** and a **private |
| 217 | key**, which downloads a `.pem` file. |
| 218 | 4. Set these before starting g1t, in the environment or in `.env`: |
| 219 | |
| 220 | | Variable | Value | |
| 221 | | --- | --- | |
| 222 | | `GITHUB_APP_ID` | The App ID | |
| 223 | | `GITHUB_APP_SLUG` | The slug | |
| 224 | | `GITHUB_APP_CLIENT_ID` | The Client ID | |
| 225 | | `GITHUB_APP_CLIENT_SECRET` | The client secret | |
| 226 | | `GITHUB_APP_PRIVATE_KEY` | The `.pem` file's contents, as downloaded. Line breaks may be written as `\n`. | |
| 227 | | `GITHUB_APP_WEBHOOK_SECRET` | The webhook secret, if the webhook is on | |
| 228 | |
| 229 | 5. Restart g1t: `docker compose -f deploy/self-host/docker-compose.yml up -d`. |
| 230 | |
| 231 | g1t makes its own key for the GitHub tokens it keeps (`IDENTITY_KEY`, in |
| 232 | the `g1t-data` volume) on first start. What the app can do, and what comes |
| 233 | across from GitHub, is in [GitHub](/guides/github/). |
| 234 | |
| 235 | ## Where your data lives |
| 236 | |
| 237 | | Volume | Holds | |
| 238 | | --- | --- | |
| 239 | | `g1t_g1t-data` | Accounts, workspaces, issues and every other record, as SQLite files; the keys that seal stored secrets (`keys.env`) | |
| 240 | | `g1t_g1t-git` | Your repositories, one bare git repository each | |
| 241 | | `g1t_g1t-packages` | Container images' layers and other package files, the `g1t-backups` bucket and the clone packs in `g1t-git-packs` (MinIO) | |
| 242 | | `g1t_g1t-secrets` | The key the site and the git store share | |
| 243 | |
| 244 | To back up, stop g1t and copy the volumes: |
| 245 | |
| 246 | ```sh |
| 247 | docker compose -f deploy/self-host/docker-compose.yml stop |
| 248 | docker run --rm -v g1t_g1t-data:/data -v g1t_g1t-git:/git -v "$PWD":/backup \ |
| 249 | debian:bookworm-slim tar czf /backup/g1t-backup.tgz /data /git |
| 250 | docker compose -f deploy/self-host/docker-compose.yml start |
| 251 | ``` |
| 252 | |
| 253 | Keep `keys.env` with the backup. Without it, saved webhook, integration and |
| 254 | Actions secrets cannot be opened. |
| 255 | |
| 256 | ## Upgrade |
| 257 | |
| 258 | Pull the new source and rebuild. Database changes are applied on start, |
| 259 | and changes already applied are skipped: |
| 260 | |
| 261 | ```sh |
| 262 | git pull |
| 263 | docker compose -f deploy/self-host/docker-compose.yml up --build -d |
| 264 | ``` |
| 265 | |
| 266 | ## Stop and remove |
| 267 | |
| 268 | ```sh |
| 269 | docker compose -f deploy/self-host/docker-compose.yml down # keeps your data |
| 270 | docker compose -f deploy/self-host/docker-compose.yml down -v # deletes it |
| 271 | ``` |