> For the complete documentation index, see [llms.txt](https://docs-lunar.earthly.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs-lunar.earthly.dev/install/git-platforms/github.md).

# GitHub

Set up GitHub for Lunar, including the Hub's GitHub App, user sign-in, multi-organization access, GitHub Apps for collectors and catalogers, and merge blocking.

The Hub authenticates with a **GitHub App** installed on your organization. The App vends short-lived installation tokens, which Lunar uses to read repositories and post results, so no long-lived credential is stored anywhere.

You create the App; Lunar never creates one on your behalf. Either flow below produces the same App.

This page covers the GitHub side only. The install steps that consume it are in [Prerequisites → Step 5](/install/lunar-hub/self-hosted/prerequisites.md#step-5-connect-your-git-platform) for self-hosted, or the [Dedicated](/install/lunar-hub/dedicated/setup.md) setup steps.

## Permissions

The App ends up with these permissions however you create it: the setup tool sets them for you, and the [manual flow](#manual-setup-alternative) has you set them by hand. This is the list to hand a security reviewer.

| Permission           | Access | Why                                                                                                                        |
| -------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- |
| `actions`            | read   | Read workflow runs for CI data collection                                                                                  |
| `checks`             | write  | Post policy results as PR checks                                                                                           |
| `contents`           | read   | Fetch config and source for policy evaluation                                                                              |
| `metadata`           | read   | Required by GitHub on every App; also covers the role check behind the [bypass comment](/docs/pr-comments.md#lunar-bypass) |
| `pull_requests`      | write  | Post PR comments and statuses                                                                                              |
| `repository_hooks`   | write  | Auto-register per-repo webhooks                                                                                            |
| `organization_hooks` | write  | Auto-register organization-level webhooks                                                                                  |

**Events:** `push`, `pull_request`, `workflow_run`, `issue_comment`.

## App creation

### Setup tool (recommended)

1. Visit [**earthly.dev/lunar/github-app-setup**](https://earthly.dev/lunar/github-app-setup/).
2. Follow the prompts. The tool uses GitHub's [manifest flow](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest) to register the App with the permissions and events above.
3. **Download the PEM private key when prompted.** GitHub shows it exactly once — if you click past this page, you'll have to generate a new key from the App settings later.
4. Click *Install App on GitHub* and select the org. Choose **All repositories** unless you have a specific reason not to — Lunar's actual monitoring scope is configured in `lunar-config.yml`, so a narrower scope here just means coming back to **Org Settings → GitHub Apps → Lunar → Repository access** every time you add a new repo to Lunar.

The hosted tool proxies the manifest exchange to GitHub and returns the credentials to your browser; we never persist them.

### Manual setup (alternative)

{% hint style="info" %}
**Create the App by hand** when the hosted tool can't reach your environment — either of these:

* **GitHub Enterprise Server.** The App has to be created on your own GHES instance.
* **Air-gapped**, or `earthly.dev` is otherwise unreachable from the browser session you would run the tool in.
  {% endhint %}

Create the App at GitHub's [App creation page](https://github.com/settings/apps/new) (or for an org: **Org Settings → Developer settings → GitHub Apps → New GitHub App**). For GHES, create it on your GHES instance instead. GitHub's [registering-a-github-app docs](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) walk through every form field if you want a reference.

Set the [permissions and events above](#permissions), then fill in the rest:

* **Homepage URL** — any URL works (e.g. your internal Lunar URL, or `https://earthly.dev/lunar`).
* **Webhook** — uncheck "Active." The Hub registers its own per-repo webhooks at runtime; the App-level webhook is unused. URL can be a placeholder (e.g. `https://example.com/placeholder`) — GitHub requires a value but nothing will ever hit it.
* **Webhook secret** — leave blank. Since you unchecked "Active" above, GitHub won't deliver App-level events. The Hub's per-repo webhook signing secret is a separate thing — see [prereqs Step 6](/install/lunar-hub/self-hosted/prerequisites.md#step-6-plan-your-kubernetes-secrets).
* **Where can this app be installed?** — "Only on this account."

Then, once the App exists:

1. **Generate a private key** (App settings → "Private keys" → "Generate a private key"). Save the `.pem` file — GitHub does not show it again.
2. **Install the App** on your organization (App settings → "Install App"). Choose **All repositories** unless you have a specific reason not to, for the same reason as above.

Whichever flow you used, you now hold four things: the owner, the App ID, the installation ID, and the PEM private key. [Prereqs Step 5](/install/lunar-hub/self-hosted/prerequisites.md#step-5-connect-your-git-platform) lists where each one comes from. Confirm you have all four, and that the PEM is saved, before moving on.

## User sign-in app

[`lunar login`](/docs/lunar-cli.md#lunar-login) lets a person use the CLI as themselves. The CLI runs GitHub's Device Flow: it prints a code, the person enters it on github.com or their GHES server, and the CLI trades the resulting user token to the Hub for a session. The flow runs under a GitHub App or OAuth App. Reusing the GitHub App above is simplest: open **Org Settings → Developer settings → GitHub Apps → Lunar → Edit → General**, tick **Enable Device Flow**, save, and copy the **Client ID** from the top of that same page. A separate app also works if you want sign-in kept apart from repository access.

The Hub needs the client ID too, to tell the CLI which app to use and to check that presented tokens were issued to it. Self-hosted, put it in [`HUB_AUTH_OAUTH_APPS`](/install/lunar-hub/self-hosted/install-walkthrough.md#user-logins). On Lunar Dedicated, send it to Earthly during [setup](/install/lunar-hub/dedicated/setup.md#step-5-set-up-user-sign-in).

[`lunar logout`](/docs/lunar-cli.md#lunar-logout) signs the person out. Give the Hub the app's **client secret** as well, and logging out also revokes their GitHub token: self-hosted, mount the secret and reference it from `HUB_AUTH_OAUTH_APPS`; on Dedicated, deposit it alongside the PEM in [step 4](/install/lunar-hub/dedicated/setup.md#step-4-deposit-your-secrets).

### Dashboard sign-in

The same app signs people in to the dashboards when the Hub is Grafana's identity provider ([self-hosted](/install/lunar-hub/self-hosted/install-walkthrough.md#signing-in-through-the-hub), [Dedicated](/install/lunar-hub/dedicated/setup.md#step-5-set-up-user-sign-in)). Two more things on the app's **General** page:

* **The client secret is required**, not optional as it is for the CLI: GitHub's web flow cannot exchange a code without one, where the Device Flow can. Generate it under **Client secrets** and hand it to the Hub as above.
* **Add the redirect URI** `<grafana-url>/oauth/callback` under **Identifying and authorizing users → Callback URL**, where `<grafana-url>` is the address people open the dashboards at (`grafana.url` in the chart; the install's apex hostname on Dedicated).

{% hint style="warning" %}
**GitHub compares the redirect URI byte for byte.** A trailing space pasted into the field, or `http` for `https`, gets every sign-in refused with `redirect_uri_mismatch` and a "Be careful!" page from GitHub, while the Hub's logs show nothing — the request never reached it. Check the field by selecting its text when the page appears.
{% endhint %}

GitHub remembers the person's authorization until they revoke it, so after the first sign-in the next one, after a sign-out or the seven-day session, is a silent redirect while their GitHub session lasts.

## Hub configuration (Dedicated)

Nothing to configure: Earthly runs the Hub and configures it for you. Continue at [Dedicated → Deposit your credentials](/install/lunar-hub/dedicated/setup.md#step-4-deposit-your-secrets), where the PEM goes to your install rather than to Earthly. The self-hosted section below does not apply to you.

## Hub configuration (self-hosted)

Those four values are what the Hub reads. [Install Step 4](/install/lunar-hub/self-hosted/install-walkthrough.md#git-platform-credentials) has the values block to paste; this is the reference behind it.

| Setting         | Chart value                     | Environment variable         | Meaning                                                                                                                                      |
| --------------- | ------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Owner           | `hub.github.app.owner`          | `HUB_GITHUB_APP_OWNER`       | The org or user the App is installed on. Matching is case-insensitive and trimmed.                                                           |
| App ID          | `hub.github.app.id`             | `HUB_GITHUB_APP_ID`          | Numeric. Quote it in YAML, or it renders as scientific notation.                                                                             |
| Installation ID | `hub.github.app.installId`      | `HUB_GITHUB_APP_INSTALL_ID`  | Numeric, from the install URL.                                                                                                               |
| Private key     | mounted from `lunar-github-app` | `HUB_GITHUB_APP_PRIVATE_KEY` | The base64-encoded PEM.                                                                                                                      |
| Base URL        | `hub.github.baseUrl`            | `HUB_GITHUB_BASE_URL`        | GHES only. Your instance's API endpoint.                                                                                                     |
| Host            | (none)                          | `HUB_GITHUB_HOST`            | Defaults to `github.com`. On GHES set it to your instance hostname, so component names (`<host>/org/repo`) match what your manifests author. |

All four App fields are required together. A partial configuration is rejected at startup.

### Multiple organizations

When one Hub fronts several orgs that each install their own Lunar App, register one entry per owner in `HUB_GITHUB_APPS` instead of the single-App variables:

```bash
export HUB_GITHUB_APPS=$(cat <<'EOF'
[
  {"owner": "earthly", "app_id": 123, "private_key_path": "/secrets/github-apps/earthly.pem", "install_id": 100},
  {"owner": "acme",    "app_id": 456, "private_key_path": "/secrets/github-apps/acme.pem",    "install_id": 200}
]
EOF
)
```

Each entry pins its own App credentials and installation; the Hub mints tokens scoped to the right org per request. Owner matching is case-insensitive and trimmed.

For the PEM files, the recommended pattern is one Kubernetes Secret with multiple keys, mounted as a single volume:

```yaml
# Secret
data:
  earthly.pem: <base64-encoded PEM>
  acme.pem:    <base64-encoded PEM>

# Pod
volumes:
- name: github-apps
  secret: { secretName: github-apps }
volumeMounts:
- { name: github-apps, mountPath: /secrets/github-apps, readOnly: true }
```

`HUB_GITHUB_APPS` is mutually exclusive with the single-App env vars (`HUB_GITHUB_APP_OWNER` / `HUB_GITHUB_APP_ID` / `HUB_GITHUB_APP_PRIVATE_KEY` / `HUB_GITHUB_APP_INSTALL_ID`). Use one mode or the other.

### Avoid GitHub rate limiting

GitHub gives every App installation its own REST rate-limit budget, so you can mitigate GitHub rate limits by installing additional Apps. A large organization may need 2–10 Apps to stay within the rate limit.

Create each additional App the same way as the first, through either flow in [App creation](#app-creation).

Register each as another entry in `HUB_GITHUB_APPS` with the same owner, its own `app_id` and `install_id`, and its own PEM:

```bash
export HUB_GITHUB_APPS=$(cat <<'EOF'
[
  {"owner": "earthly", "app_id": 123, "private_key_path": "/secrets/github-apps/earthly.pem",   "install_id": 100},
  {"owner": "earthly", "app_id": 789, "private_key_path": "/secrets/github-apps/earthly-2.pem", "install_id": 300}
]
EOF
)
```

Give each entry a distinct `private_key_path` — the two Apps have different private keys, and pointing both at one file makes the second fail to authenticate.

Lunar Hub spreads **read** traffic evenly across every App registered for an owner, including the lookups that find its existing check run and pull-request comment. Pull-request comments are spread too: any of the Apps may create or edit Lunar's comment. Check runs are created by the App with the lowest App ID for that owner and updated by the App that created each one, because GitHub only allows the App that created a check run to update it.

To spread check-run creation across the Apps too, set `HUB_GITHUB_CHECK_RUNS_LOWEST_APP_ID=false`. Each check run is still updated by the App that created it, but different commits get their "Earthly Lunar" check from different Apps.

{% hint style="warning" %}
**Make "Earthly Lunar" accept any source before turning this on.** A required status check can name the App that must provide it, and GitHub fills in the App that recently posted the check when the rule doesn't say. A pull request whose check another App created then stays blocked. Set the check's source to **Any source** in the branch protection rule or ruleset (`app_id: -1` through the API).
{% endhint %}

On Kubernetes, set this through `hub.github.apps` and give the second entry a `privateKeyFile` so it reads its own key from the App Secret (chart 3.14.0+):

```yaml
hub:
  github:
    apps:
      - owner: earthly
        appId: 123
        installId: 100
      - owner: earthly
        appId: 789
        installId: 300
        privateKeyFile: earthly-2.pem
```

## GitHub Apps for collectors and catalogers

Collectors and catalogers that call the GitHub API can mint short-lived tokens from a GitHub App of their own, separate from the Hub's, instead of reading a personal access token from a secret. [GitHub App Commands](/docs/lunar-cli.md#github-app-commands) covers setting one up.

## Blocking merges

Lunar reports each evaluation as an **`Earthly Lunar` check run** on the commit. On its own the check is informational; it blocks merges when you require it in branch protection: **Settings → Branches → Branch protection rules → Require status checks to pass**, and add `Earthly Lunar` to the required checks. Unlike [GitLab Ultimate's merge gate](/install/git-platforms/gitlab.md#merge-gate), Lunar does not provision this setting for you. Whether the check is required stays your call, per branch rule.

The check run appears once Lunar has policy results for the commit. While a required check is waiting on data, it remains in progress and reads `N of M required checks pending`.

Only policies at a gating enforcement level participate. See [policies](/configuration/lunar-config/policies.md). A blocked pull request can still be let through; see the [PR/MR comments reference](/docs/pr-comments.md).

A check run that went green on a bypass says so in its title, reading `3 required check(s) bypassed` rather than reporting a pass, so a reviewer scanning the checks list can tell the two apart. The comment below it names who authorized each one.

## Next steps

Self-hosted installs carry these four values into Kubernetes secrets at [prereqs Step 6](/install/lunar-hub/self-hosted/prerequisites.md#step-6-plan-your-kubernetes-secrets), and into your chart values at [install Step 4](/install/lunar-hub/self-hosted/install-walkthrough.md#git-platform-credentials). On Dedicated you [deposit the PEM](/install/lunar-hub/dedicated/setup.md#step-4-deposit-your-secrets) into your install's secret drop instead.

For how GitHub and GitLab differ once Lunar is running, see [Git Platforms](/install/git-platforms.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs-lunar.earthly.dev/install/git-platforms/github.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
