> 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/lunar-hub/dedicated/setup.md).

# Setup

Earthly provisions and operates your entire install. This page is everything your side does, in order, with the reasoning behind each step.

Read the [Overview](/install/lunar-hub/dedicated/overview.md) first if you want the model, the security posture, and what the service includes. This page is the hands-on companion.

## Your steps at a glance

| Step                                                                          | Who does it                                  | When                                               |
| ----------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------------- |
| [Questionnaire answers](#first-the-questionnaire)                             | Whoever owns the rollout                     | First (everything else keys off them)              |
| [1. Private connectivity](#step-1-private-connectivity-if-you-need-it)        | Your AWS/network team                        | **Start immediately**; this one can have lead time |
| [2. Identify your depositor account](#step-2-identify-your-depositor-account) | Your AWS admin                               | Any time before the deposit                        |
| [3. Set up your Git platform](#step-3-set-up-your-git-platform)               | Your GitHub org admin, or GitLab group owner | Any time before the deposit                        |
| [4. Deposit your secrets](#step-4-deposit-your-secrets)                       | Either, from a terminal                      | Last (needs the packet and the credential)         |

Steps 2 and 3 are quick and independent. **Step 1 is the one that determines your timeline.** It involves DNS changes on your side, plus an AWS domain-ownership check or a request to your Git platform vendor depending on which direction you need, so start it the day you send your answers even though it's needed last.

Earthly builds the install in parallel. The only early input it needs from you is your depositor account ID ([step 2](#step-2-identify-your-depositor-account)).

## First, the questionnaire

Your answers to the [onboarding questionnaire](/install/lunar-hub/dedicated/overview.md#what-earthly-needs-from-you) parameterize the install: region, hub access, internal targets, DNS, sizing, maintenance windows, contacts. Send them to Earthly whenever they're settled; nothing waits on a meeting.

Earthly replies with your **coordinates packet**: tenant name, region, dedicated account ID, deposit role ARN, deposit ExternalId, and your webhook URL. Several steps below need values from it.

## Step 1 — Private connectivity, if you need it

Traffic crosses between your network and the install in both directions, and each direction is its own piece of setup. You may need one, both, or neither.

| Direction                                                            | You need it when                                        | What you build                              |
| -------------------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------- |
| [Lunar reaching your systems](#lunar-reaching-your-internal-systems) | something Lunar must reach isn't on the public internet | an endpoint service in front of each system |
| [Reaching the hub](#your-ci-and-browsers-reaching-the-hub)           | your CI runs in a VPC you control (recommended)         | one interface endpoint and two DNS records  |

Both are AWS PrivateLink, and both are your networking team's work. Each has a page written to be forwarded on its own. Work out which apply the day you send your questionnaire answers, because either can carry lead time you don't control. That's why this is step 1 even though nothing needs it until the end.

### Lunar reaching your internal systems

The Hub calls your Git platform to read repositories, register webhooks, and post results. Collectors, catalogers, and policies run *inside* your install and reach the systems they gather data from, such as registries, ticketing, and internal APIs. If any of those targets isn't reachable from the public internet, Lunar needs a private path to it.

**This includes your Git platform itself.** A self-managed GitHub Enterprise Server or GitLab instance that only resolves inside your network is one of these targets, and an easy one to overlook: the Hub needs the private path before any collector does.

**Start here if that applies to you:** [**PrivateLink to Your Internal Systems**](/install/lunar-hub/dedicated/privatelink-outbound.md)**.** It covers publishing your internal service as an AWS PrivateLink endpoint service, allowlisting Earthly's dedicated account, and associating a verified DNS name so Lunar reaches your service by its real hostname with working TLS.

{% hint style="warning" %}
**This can take time.** The AWS work is straightforward, but it includes publishing a DNS record and waiting for AWS to verify domain ownership, which means a DNS change on your side (and potentially your change-management process). If DNS changes queue behind a change window, that wait alone can add days. Start this in parallel *first*, even though it isn't needed until the end.
{% endhint %}

Not sure it applies? The [Do you need this?](/install/lunar-hub/dedicated/privatelink-outbound.md#do-you-need-this) questions at the top of that page take two minutes and can rule the work out entirely: public targets need nothing, and an internet-facing target can often just allowlist the install's single outbound IP.

### Your CI and browsers reaching the hub

Earthly recommends publishing the install privately, so your CI runners, CLI users, and browsers reach it from your own VPC with nothing exposed to the internet. The alternative is a public hostname behind TLS and token authentication, which needs nothing from you and is the only option when your CI runs on shared hosted runners.

**Start here if that applies to you:** [**PrivateLink to Your Hub**](/install/lunar-hub/dedicated/privatelink-inbound.md)**.** It covers creating the interface endpoint, the two DNS records that point your install's hostnames at it, and reaching the UI from laptops rather than just from CI.

{% hint style="warning" %}
**Settle the webhook path first.** Your Git platform delivers webhooks to the hub, and it isn't on your runner network, so the endpoint your CI uses won't carry that traffic. A platform inside your own network can deliver through an endpoint of its own, and a cloud-hosted one delivers from the internet, which means that one listener stays public. A vendor-managed single-tenant platform is the case to start early: the private connection is something you request from your vendor, on a timeline you don't control.
{% endhint %}

Not sure it applies? The [Do you need this?](/install/lunar-hub/dedicated/privatelink-inbound.md#do-you-need-this) questions at the top of that page will settle it, and the deciding factor is usually where your CI runs: self-hosted runners in AWS are the natural fit, while shared hosted runners can't use a private endpoint at all.

## Step 2 — Identify your depositor account

During setup, your secrets (your Git platform credential, the encryption key, and the webhook secret) are written **directly into your dedicated account's** secret store, never through Earthly's systems. They're deposited by assuming a **write-only deposit role** in the dedicated account. Earthly creates that role while provisioning your account, with your depositor principal baked into its trust policy. The trust policy is fixed at that point, which is why your account ID is the one input Earthly needs early.

**Send Earthly the 12-digit ID of the AWS account you will deposit from.** Earthly trusts that account principal (`arn:aws:iam::<YOUR_ACCOUNT_ID>:root`) plus a deposit-specific ExternalId (included in your coordinates packet).

That does **not** grant everyone in the account access; your IAM administrator still chooses which role or user may call `sts:AssumeRole`. Attach this caller-side policy to the role, user, or permission set your team will use:

```json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "sts:AssumeRole",
    "Resource": "arn:aws:iam::<DEDICATED_ACCOUNT_ID>:role/<TENANT>-secret-deposit"
  }]
}
```

The exact deposit-role ARN for the `Resource` line is also in your coordinates packet. It's fine to prepare the policy with a placeholder and tighten it when the packet arrives.

Notes that save a support round-trip:

* Trusting the account's `:root` ARN is **not** logging in as the AWS root user. The root user cannot assume roles; use a normal IAM or SSO principal.
* Earthly does not trust a specific SSO-generated role ARN, so recreating a permission set does not silently break the deposit trust. Your caller-side policy remains the gate.
* If an SCP or permission boundary blocks cross-account `sts:AssumeRole`, your AWS administrator must allow this one deposit-role ARN.

{% hint style="info" %}
**No AWS account?** Tell Earthly along with your questionnaire answers. When you're ready to deposit, Earthly will mint **one-hour temporary credentials** for the write-only deposit role and deliver them over a secure channel.
{% endhint %}

## Step 3 — Set up your Git platform

Lunar authenticates to your Git platform with a credential that **you** create, on the groups, orgs, and repos it should monitor. Earthly never holds org-admin access, and never handles the credential itself.

[Git Platforms](/install/git-platforms.md) covers what differs between the two platforms. The Hub-configuration sections of those pages are for self-hosted installs only, since Earthly configures the Hub for you.

{% tabs %}
{% tab title="GitHub" %}
Create a GitHub App and install it on your org.

* Use the [hosted setup tool](https://earthly.dev/lunar/github-app-setup/), or [create it by hand](/install/git-platforms/github.md#manual-setup-alternative) on GitHub Enterprise Server, or if your security review needs every permission spelled out first. The [permissions it is granted](/install/git-platforms/github.md#permissions) are listed for that review.
* Set the **webhook URL** from your coordinates packet (`https://hub.<TENANT>.<your-dedicated-domain>/webhooks/github`). It's fine that the install isn't live yet.
* Download the App's **private key** (`.pem`). It's deposited in step 4; it never goes to Earthly, in any channel.
* Leave the App's **"Webhook secret" field empty.** Lunar registers and secures its own repository webhooks from inside your install; the App-level webhook carries no traffic.

**Send Earthly:** the App ID, the installation ID, and the org(s) it's installed on. Those are identifiers, not secrets.
{% endtab %}

{% tab title="GitLab" %}
Create a [group access token](/install/git-platforms/gitlab.md#create-the-token) on each top-level group Lunar should monitor.

* Go to the top-level group, then **Settings → Access tokens → Add new token**. Give it the `api` scope and the `Maintainer` or `Owner` role.
* Pick an expiry and set a calendar reminder for it now. GitLab tokens expire on a fixed date, with no overlap period. See [Rotations and changes](#rotations-and-changes).
* Copy the token when GitLab shows it; it is displayed once. It's deposited in step 4 and never goes to Earthly.
* **There is no webhook URL to set, and no webhook secret to paste back.** Lunar registers a webhook on each project itself and generates the signing secret inside your install, so unlike GitHub there's nothing to configure on the GitLab side.

**Send Earthly:** the top-level group path each token covers, plus your instance hostname if you're self-managed. Those are identifiers, not secrets.

{% hint style="warning" %}
**Self-managed instances must be declared.** Lunar decides whether a host is GitLab from configuration, so if you don't tell Earthly your instance hostname it will be treated as GitHub and every operation against it fails in confusing ways. `gitlab.com` needs no declaration.
{% endhint %}
{% endtab %}
{% endtabs %}

## Step 4 — Deposit your secrets

Run this from a terminal once you have your coordinates packet and your Git platform credential. Expect about 30 minutes end to end. You'll need the **tenant name, region, deposit role ARN, and deposit ExternalId** from the packet. Earthly is glad to join a call if you need help.

{% hint style="info" %}
The deposit ExternalId unlocks only the write-only deposit role. It is not Earthly's deploy ExternalId, and it grants nothing else.
{% endhint %}

Use an AWS profile for a principal carrying the caller-side policy from step 2:

{% tabs %}
{% tab title="GitHub" %}

```bash
AWS_PROFILE=<your-profile> lunar setup bootstrap \
  --tenant <TENANT> --region <REGION> \
  --github-app-pem <path/to/app.pem> \
  --deposit-role-arn <DEPOSIT_ROLE_ARN> \
  --external-id <DEPOSIT_EXTERNAL_ID>
```

{% endtab %}

{% tab title="GitLab" %}
Write the token to a file first, so it never lands in your shell history:

```bash
AWS_PROFILE=<your-profile> lunar setup bootstrap \
  --tenant <TENANT> --region <REGION> \
  --gitlab-group <TOP_LEVEL_GROUP> \
  --gitlab-token <path/to/group-access-token> \
  --deposit-role-arn <DEPOSIT_ROLE_ARN> \
  --external-id <DEPOSIT_EXTERNAL_ID>
```

**Repeat once per top-level group.** Lunar keys tokens by group, so each group's token is deposited separately under its own group path.

Your instance hostname is not part of the deposit — it's configuration, not a secret. Earthly applies it from the identifiers you sent in [step 3](#step-3-set-up-your-git-platform).
{% endtab %}
{% endtabs %}

If Earthly issued one-hour temporary credentials instead (the no-AWS-account path), export the three values in your shell and drop the last two flags; the credentials already *are* the deposit role:

```bash
export AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… AWS_SESSION_TOKEN=…
lunar setup bootstrap \
  --tenant <TENANT> --region <REGION> \
  --github-app-pem <path/to/app.pem>
```

What the command does:

* Generates the **encryption key** and **webhook secret** inside this flow and deposits them write-once. Neither is ever printed — the install consumes them directly.
* Deposits the Git platform credential you supplied: the GitHub App PEM, or the GitLab group access token.
* Verifies each write from the write response. The deposit role is **write-only by design**: it cannot read the secrets back, and the setup flow never returns them to Earthly.
* Leaves nothing to paste back on either platform: Lunar registers repository and project webhooks itself and manages their signing secrets inside your install.

**If you get** `AccessDenied`, in order of likelihood:

1. Your caller doesn't have `sts:AssumeRole` on the deposit-role ARN.
2. Deposit ExternalId typo; it must match the packet exactly.
3. An SCP or permission boundary in your org blocks `sts:AssumeRole` to accounts outside your organization. Loop in whoever owns your SCPs.
4. On the no-AWS-account path: the one-hour credentials expired. Ask Earthly to mint a fresh set; it takes seconds.

## Go live

Earthly completes the install, verifies it end-to-end, and hands you the **hub access token** over a secure channel. Treat it like a root credential. On an internet-reachable install you also get a **Grafana login**; on a private install the UI has no login and is read-only to anyone who can reach your endpoint.

Then, on your side:

**Load your workload secrets**: the API keys your catalogers, collectors, and policies need. These go straight to your hub via the CLI, never through Earthly's systems:

```bash
lunar secret set DATADOG_API_KEY --scope collector   # value read from stdin
```

**Finish DNS, if you delegated a subdomain.** Add the NS records Earthly provides to hand off the zone. Earthly then manages every record and the TLS certificate under it.

**Complete your hub-access step, if you chose PrivateLink.** Create the inbound endpoint and coordinate with Earthly, usually via your networking team. Over the internet there's nothing to do.

**Confirm private connectivity, if you set it up in step 1.** Earthly runs a verification pass from a real Lunar workload (hostname resolution, TLS with hostname verification, an authenticated application request) and only points your install at the internal hostname once all of it passes.

## Rotations and changes

**GitHub App PEM.** Re-run [step 4](#step-4-deposit-your-secrets) with the new file. The encryption key and webhook secret will not rotate, but the PEM will.

**GitLab group access token.** GitLab tokens carry a fixed expiry, and there is no overlap period where old and new both work, so replacing one is a cutover rather than a rollover. Create the replacement in GitLab, re-run [step 4](#step-4-deposit-your-secrets) with it ahead of the expiry date, then revoke the old one. See [expiry and rotation](/install/git-platforms/gitlab.md#expiry-and-rotation).

{% hint style="warning" %}
If a GitLab token lapses, Lunar stops posting results and stops reacting to webhooks for that group. Because the token is read at startup, nothing fails loudly to tell you. Set the calendar reminder when you create the token, not later.
{% endhint %}

If you used the AWS-less fallback, contact Earthly before any credential rotation; fresh one-hour credentials are minted for the re-run and nothing else changes.

**Adding or removing an internal target.** Publish the new endpoint service following [PrivateLink to Your Internal Systems](/install/lunar-hub/dedicated/privatelink-outbound.md) and send Earthly the same six values. Earthly makes a small, reviewed change on its side, with no downtime for your install.


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs-lunar.earthly.dev/install/lunar-hub/dedicated/setup.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
