> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chainloop.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub App (Self-hosted)

> Configure the Chainloop GitHub App on a self-hosted deployment via Helm chart values.

To enable GitHub repository onboarding on a self-hosted Chainloop instance, create a GitHub App and provide its credentials to the Chainloop backend through Helm chart values. This is a one-time, operator-level setup step — once configured, your users connect their GitHub organization from the Chainloop UI following the [Connect GitHub & GitLab](/guides/connect-repository) flow.

## Create the GitHub App

<Steps>
  <Step title="Create the App on GitHub">
    Follow GitHub's [guide to creating GitHub Apps](https://docs.github.com/en/apps/creating-github-apps) to register a new App under your GitHub organization (or personal account, for testing).
  </Step>

  <Step title="Grant the required permissions and events">
    Configure the App's repository and organization permissions and subscribe to the two webhook events Chainloop needs: `pull_request` and `pull_request_review`. See the [cumulative permission set](/reference/github-permissions#cumulative-permission-set) for the full, up-to-date list of scopes, and [subscribed webhook events](/reference/github-permissions#subscribed-webhook-events) for what each event drives.

    <Note>
      On an App you created earlier, check that `pull_request_review` is subscribed. Without it, a pull request is not re-validated when a reviewer approves, requests changes, or has their review dismissed. The event needs no permission beyond **Pull requests: Read**, which the App already has.
    </Note>
  </Step>

  <Step title="Collect the App ID">
    On the App's settings page, note the **App ID** shown near the top. You'll set this as `appId`.
  </Step>

  <Step title="Generate a client secret and collect the client ID">
    Under **General → Client secrets**, click **Generate a new client secret**. GitHub displays it once and never returns it from the API — record it before leaving the page. You'll set it as `clientSecret`, and the **Client ID** shown just above it as `clientId`.
  </Step>

  <Step title="Register the OAuth callback URL">
    Under **Identifying and authorizing users**, add a **Callback URL** of `<backend-external-url>/enroll/github/authorize/callback`. Add it alongside any existing entries without reordering them.

    Leave **Allow wildcard matching** off. Chainloop sends `redirect_uri` explicitly on every authorization request, so ordering and wildcards are unnecessary — and a wildcard would let OAuth codes reach subdomains and sub-paths.

    Leave **Request user authorization (OAuth) during installation** unchecked. See the warning below.
  </Step>

  <Step title="Set the Setup URL">
    Under **Post installation**, set the **Setup URL** to `<backend-external-url>/enroll/github/callback` and keep **Redirect on update** checked. This is what returns a user to Chainloop after they install the App, or after they add or remove repositories on an existing installation.
  </Step>

  <Step title="Generate a private key">
    Under **Private keys**, click **Generate a private key**. GitHub downloads a PEM file — you'll set its contents as `privateKey`.
  </Step>

  <Step title="Note the app slug">
    The App's public URL is of the form `https://github.com/apps/<app-slug>`. Copy the `<app-slug>` segment — you'll set this as `appSlug`.
  </Step>

  <Step title="Set a webhook secret">
    Under **Webhook**, set a **Secret** — a random string used to verify that incoming webhook requests genuinely came from GitHub. You'll set this as `webhookSecret`. Set the **Webhook URL** to `<backend-external-url>/github/events`.
  </Step>
</Steps>

<Warning>
  **Do not enable *Request user authorization (OAuth) during installation*.** Selecting it removes the **Setup URL** field from the App's settings entirely, which breaks both the connection flow and the add/remove-repositories flow — with no error shown in the Chainloop UI or in the backend logs. Because it changes the App registration rather than the deployment, rolling back a Chainloop image does not undo it.
</Warning>

## Chart values

Provide the App's details to the Chainloop backend under `backend.githubApp` in your Helm chart values:

```yaml theme={"dark"}
backend:
  githubApp:
    appId: 123456
    privateKey: |
      -----BEGIN RSA PRIVATE KEY-----
      ...
      -----END RSA PRIVATE KEY-----
    appSlug: my-chainloop-app
    baseUrl: https://github.com
    webhookSecret: ""
    clientId: ""
    clientSecret: ""
```

| Key             | Description                                                                                                 |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| `appId`         | GitHub App ID.                                                                                              |
| `privateKey`    | PEM-encoded RSA private key for signing App JWTs.                                                           |
| `appSlug`       | App slug from the App's public URL (also acts as the enable flag; the config renders only when set).        |
| `baseUrl`       | Optional, defaults to `https://github.com`.                                                                 |
| `webhookSecret` | Shared secret for verifying GitHub webhook signatures (HMAC-SHA256); required for webhook event processing. |
| `clientId`      | **Required.** OAuth client ID from the App's settings page.                                                 |
| `clientSecret`  | **Required.** OAuth client secret, generated under **General → Client secrets**.                            |

<Note>
  `clientId` and `clientSecret` are required whenever `backend.githubApp` is set, together with a resolvable [backend external URL](#backend-external-url). The backend refuses to start without them. Deployments with no GitHub App configured are unaffected.
</Note>

<Note>
  These values apply to both the OSS chart (`chart: platform`) and the Enterprise chart (`chainloop-ee`) — the value paths are identical.
</Note>

## Why the OAuth credentials are required

When a user connects their GitHub organization, Chainloop binds a GitHub App installation to their Chainloop organization. Before creating that binding it confirms the person completing the flow has access to the installation they're binding, by sending them through the App's user-to-server OAuth flow. The App's private key cannot answer that question — it identifies the App, never the human in the browser — and minting a user access token requires the OAuth client credentials.

Users see one extra GitHub authorization prompt the first time an installation is bound. Re-running the flow against an installation the organization already holds skips it, so adding and removing repositories is unaffected. See [installer verification](/reference/github-permissions#installer-verification-at-connection-time) for what Chainloop checks and which token it uses.

## Backend external URL

The OAuth callback URL is derived from the backend's externally reachable origin by appending `/enroll/github/authorize/callback`. GitHub matches `redirect_uri` byte for byte against the registered **Callback URL**, so the two must agree exactly — including scheme, and with no trailing slash on the origin.

You don't set this origin under `githubApp`. The chart derives it from the first of these that is set:

| Value                        | Result                                                                                    |
| ---------------------------- | ----------------------------------------------------------------------------------------- |
| `backend.externalHostname`   | used as-is (a full origin, e.g. `https://api.chainloop.example`)                          |
| `backend.ingress.hostname`   | `https://<hostname>` when `backend.ingress.tls` is enabled, otherwise `http://<hostname>` |
| `backend.httpRoute.hostname` | `https://<hostname>`                                                                      |

The resolved callback URL is logged by the backend at startup, so you can copy it verbatim into the App's **Callback URL** list:

```
initialized GitHub App installer verification callback_url=https://api.chainloop.example/enroll/github/authorize/callback
```

## Rotating the client secret

Rotating `clientSecret` invalidates connection flows that are **in progress** at the moment of the rollout — the key protecting the OAuth state parameter is derived from the secret, so a flow that started before the change fails on its return leg. Affected users simply start the connection again. Installations already bound are unaffected, and no repository data is lost.

## Troubleshooting

| Symptom                                                                              | Cause and fix                                                                                                                                                                           |
| ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Backend panics at startup with `github_app.client_id: must be at least 1 characters` | `clientId` or `clientSecret` is unset while `backend.githubApp` is configured. Set both.                                                                                                |
| Backend fails at startup complaining that `server.http.external_url` is not set      | No [backend external URL](#backend-external-url) could be resolved. Set `backend.externalHostname`, or an ingress or HTTPRoute hostname.                                                |
| GitHub shows its own error page about a `redirect_uri` mismatch                      | The **Callback URL** on the App doesn't byte-match the derived one. Compare it against the URL logged at startup — scheme and trailing slash included.                                  |
| *"This connection session has expired, or it was not started from Chainloop"*        | The **Setup URL** is missing or wrong, **Redirect on update** is unchecked, or **Request user authorization (OAuth) during installation** is enabled (which hides the Setup URL field). |
| *"Your GitHub account does not have access to this App installation"*                | The signed-in GitHub user has no access to that installation. Someone with access to it on GitHub must complete the connection.                                                         |
| *"Your GitHub organization enforces SAML single sign-on"*                            | The user's GitHub session isn't authorized for that organization. Authorize it on GitHub, then start the connection again.                                                              |
| *"Authorization was declined on GitHub"*                                             | The user dismissed the GitHub authorization prompt. Granting it is required to confirm they administer the installation.                                                                |

<Note>
  **GitHub Enterprise Server:** set `baseUrl` to your GHES URL — all features are supported the same way as on github.com. `baseUrl` also determines where the OAuth endpoints and the REST API are reached (`<ghes-host>/login/oauth/*` and `<ghes-host>/api/v3`), so no additional configuration is needed.
</Note>
