> 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/self-hosted/prerequisites.md).

# Prerequisites

Before you run `helm install lunar`, the external dependencies below must be in place. The [install walkthrough](/install/lunar-hub/self-hosted/install-walkthrough.md) assumes you have them.

If you want a total picture of what you're about to deploy, read the [overview](/install/lunar-hub/self-hosted/overview.md) first.

{% hint style="info" %}
Lunar Hub is supported on Kubernetes only. Bare-metal and Docker installations are not supported.
{% endhint %}

{% hint style="info" %}
**Install the** [**Lunar CLI**](/install/cli.md) **first.** The walkthrough uses it on your workstation to derive the cluster's GHCR image-pull secret from your licence — before `helm install`.
{% endhint %}

## Before you begin

Make sure you have the following available before continuing.

| Step                                                | What you need             | Detail                                                                                                                                                                                                         |
| --------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| —                                                   | Workstation tools         | [`lunar`](/install/cli.md), `kubectl`, `helm` 3.x                                                                                                                                                              |
| [2](#step-2-check-your-kubernetes-cluster)          | Kubernetes cluster        | 1.29+, with an ingress controller and a default StorageClass                                                                                                                                                   |
| [2](#step-2-check-your-kubernetes-cluster)          | DNS hostnames + TLS certs | One hostname for the Hub (reachable from your Git platform — github.com, GHES, gitlab.com, or your GitLab instance), plus a second for Grafana if you let the chart run it for you (reachable from your users) |
| [3](#step-3-provision-postgresql)                   | PostgreSQL instance       | 16+, where you can create a dedicated owner role                                                                                                                                                               |
| [4](#step-4-provision-s3-compatible-object-storage) | S3-compatible buckets     | Two private buckets (logs + resources) with IAM to read and write them                                                                                                                                         |
| [5](#step-5-connect-your-git-platform)              | Git platform admin access | GitHub org admin (or a personal account) to create a GitHub App, and/or GitLab group Maintainer/Owner to create a group access token                                                                           |
| [6](#step-6-plan-your-kubernetes-secrets)           | Hub license key           | Provided by Earthly; mounted by the chart as `lunar-hub-licence`                                                                                                                                               |

{% hint style="info" %}
If you're standing up EKS from scratch, [`earthly/lunar-terraform-quickstart`](https://github.com/earthly/lunar-terraform-quickstart) is a working reference module you can fork if desired.
{% endhint %}

## Step 1 — Plan your Kubernetes namespaces

We recommend splitting the install across two namespaces:

* **Control-plane namespace** (e.g. `lunar`). This is the release namespace. It hosts the Lunar Hub, the Operator, and — if you use the chart's bundled Grafana — Grafana. These are the parts that need to stay up, and will be updated by Helm.
* **Run Pods namespace** (e.g. `lunar-scripts`). This hosts the short-lived pods the operator spawns to execute cataloger, collector, and policy batches. We recommend this separate namespace because:
  * this workload is ephemeral, and can be rather "bursty" in number and resource requirements. You can tune resources and limits independently here.
  * this code is user-supplied (e.g. your plugins, scripts, third-party catalogers), not Lunar's. This is a different trust boundary, where you can tighten RBAC, egress, and resource limits independently.

Both namespaces must exist **before** `helm install`. The chart will not create the run-pods namespace for you. Point the operator at it with `operator.snippetNamespace`.

Single-namespace installs also work — leave `operator.snippetNamespace` unset and everything runs in the release namespace. This is fine for trying things out, or small setups; but is *not recommended* for production configurations.

```bash
kubectl create namespace lunar
kubectl create namespace lunar-scripts
```

## Step 2 — Check your Kubernetes cluster

| Requirement            | Detail                                                                                                                                                                                                                                                                                                                                                                    |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Kubernetes**         | 1.29 or newer.                                                                                                                                                                                                                                                                                                                                                            |
| **Helm**               | 3.x.                                                                                                                                                                                                                                                                                                                                                                      |
| **StorageClass**       | A StorageClass must be available — your cluster default is probably fine. The chart provisions a 10 GiB `ReadWriteOnce` PVC for Hub state. You can tune details if needed via `hub.persistence.*` ([chart README](https://github.com/earthly/charts/blob/main/README.md)).                                                                                                |
| **Ingress controller** | Must support gRPC backend routing. See [Ingress](https://github.com/earthly/charts/blob/main/README.md#ingress) in the chart README for an NGINX-tested example.                                                                                                                                                                                                          |
| **DNS**                | A hostname for the Hub (e.g. `lunar.example.com`) pointing at your ingress controller's external IP — your Git platform, CI integrations, and Lunar CLI users must reach it. If you let the chart run Grafana for you, add a second hostname for it (e.g. `grafana.lunar.example.com`) for your team's browser access; not needed if you point Lunar at your own Grafana. |
| **TLS certificate**    | The Hub (and the bundled Grafana, when enabled) listen plaintext. Terminate TLS at your ingress or an upstream load balancer for each hostname.                                                                                                                                                                                                                           |

## Step 3 — Provision PostgreSQL

Lunar needs a single PostgreSQL database. Schema migrations run as a pre-rollout Job on each release, and the Hub manages several of its own schemas (including the default `public`). We recommend that you give it a dedicated DB where its role is the owner.

| Requirement         | Detail                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Version**         | PostgreSQL 16 or newer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| **Connectivity**    | Reachable from both the Hub and Operator pod's network. The chart does **not** include Postgres.                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| **Role**            | Dedicated DB role for the Hub. Minimum: **database owner** (to create schemas, and grants on created objects) **plus cluster-level `CREATEROLE`** (to create the read-only `sqlapi_user` and `grafana_user` roles during migration). `SUPERUSER` also works.                                                                                                                                                                                                                                                                         |
| **Extensions**      | Optional: `pg_stat_statements` enabled in `shared_preload_libraries`. The Hub's diagnostics bundle uses it when present. Setting `shared_preload_libraries` requires a Postgres restart (or parameter-group reboot on RDS / Cloud SQL), so it's easier to enable at provisioning time than later.                                                                                                                                                                                                                                    |
| **Connection pool** | The Hub is stateless and runs as multiple replicas; each opens \~85 connections by default (`maxOpenConns + maxPoolConns + operatorPoolSize`), so total connections scale with `hub.replicaCount`. Size your Postgres (or PgBouncer) for `replicaCount × per-replica pool`. See [Scaling](/install/lunar-hub/self-hosted/day-2-operations.md#scaling) in Day 2 Operations for tuning context.                                                                                                                                        |
| **SSL**             | The Hub negotiates TLS by default via `hub.db.connectionOptions: "sslmode=require"`. Most managed Postgres (RDS, Aurora, Cloud SQL) ships with TLS forced and will connect out of the box. Plain Postgres deployments without TLS must set `hub.db.connectionOptions: "sslmode=disable"` explicitly. **Format is libpq KV pairs, space-separated** — to pass extra options write `"sslmode=require connect_timeout=10"` (NOT `&`-separated URL query). The default is not merged in when overridden, so include `sslmode=` yourself. |
| **Backups**         | Use your existing Postgres backup process; backups are your responsibility. All authoritative Hub and Operator state lives entirely in Postgres.                                                                                                                                                                                                                                                                                                                                                                                     |

{% hint style="info" %}
**Shared Postgres cluster?** Because `CREATEROLE` is a cluster-level attribute, Postgres has no mechanism to scope it to a single database. If granting it cluster-wide is too broad, pre-create the `sqlapi_user` role yourself (any password) and leave `HUB_SQLAPI_PASSWORD` unset. The migration's `IF NOT EXISTS` check skips role creation, leaving only schema-level grants — which the Hub's role can do as database owner.
{% endhint %}

## Step 4 — Provision S3-compatible object storage

Lunar needs two private S3 buckets, both writable by the Hub:

* A **Logs bucket**, which contains per-run log files. These can be short-lived; a 30-day lifecycle rule is reasonable here.
* A **Resources bucket**, which contains run-bundle archives fetched by init containers. Keep these as long as you might re-run historical catalogers, collectors, or policies.

Both buckets must block public access. Content may include credentials, user script source code, or PII surfaced from CI runs. The Hub serves all reads via time-limited pre-signed URLs.

{% hint style="warning" %}
**Object lifecycles are your responsibility.** Lunar never deletes from either bucket. Set S3 lifecycle rules yourself to cap storage growth. Check with your compliance requirements before settling on retention windows, since logs and run bundles may contain information that falls under your organization's data-retention policies.
{% endhint %}

The Hub *only* calls `PutObject`, `GetObject`, and `HeadObject` on both buckets, and issues pre-signed `GET`/`PUT` URLs for both. Minimum IAM policy on AWS:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": [
        "arn:aws:s3:::your-logs-bucket/*",
        "arn:aws:s3:::your-resources-bucket/*"
      ]
    }
  ]
}
```

### Region and credentials

The Hub picks up AWS credentials via the standard [SDK credential chain](https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html) — the chart stays out of the credentials business. Region must be set explicitly:

```yaml
hub:
  extraEnv:
    - name: AWS_REGION
      value: us-east-1
```

Common patterns (see the [chart README](https://github.com/earthly/charts/blob/main/README.md#object-storage--aws-credentials) for full YAML):

* **EKS**. Annotate the chart's service account with an [IAM role for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html).
* **Static credentials**. Inject `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` from a secret via `hub.extraEnv`. Not recommended for production.
* **IMDS, pod identity, external secrets operators**. All the usual AWS-SDK-friendly mechanisms work.

### Non-AWS backends

MinIO, Cloudflare R2, and GCS in S3-compatibility mode all work — set `AWS_ENDPOINT_URL_S3` via `hub.extraEnv`:

```yaml
hub:
  extraEnv:
    - name: AWS_REGION
      value: auto
    - name: AWS_ENDPOINT_URL_S3
      value: https://your-minio.example.com
```

Lunar uses virtual-host-style S3 addressing only. Most MinIO, R2, and GCS deployments handle this out of the box. Path-style-only backends aren't currently supported.

## Step 5 — Connect your Git platform

Lunar needs credentials for the Git platform your components live on. At least one is required: the Hub refuses to start with neither configured. Set up both if you run both.

{% hint style="info" %}
Read [Git Platforms](/install/git-platforms.md) before you start. Most of Lunar behaves identically on GitHub and GitLab; merge gating is one exception.
{% endhint %}

{% tabs %}
{% tab title="GitHub" %}
The Hub authenticates as a **GitHub App** you create and install on your org. The hosted setup tool creates one in a couple of clicks:

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 right permissions and events.
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.

{% hint style="info" %}
**Need to create the App by hand?** On GitHub Enterprise Server, or when `earthly.dev` is unreachable from your browser, follow [manual setup](/install/git-platforms/github.md#manual-setup-alternative), which produces the same App. The [permissions and events it is granted](/install/git-platforms/github.md#permissions) are documented for security review whichever flow you use.
{% endhint %}

Capture these four before continuing:

| What                                                                   | Source                                                                                                                     |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Owner (the GitHub org or user the App is installed on, e.g. `earthly`) | The `<org>` in the install URL                                                                                             |
| App ID (numeric, e.g. `3635822`)                                       | Setup tool result page, or the App settings page                                                                           |
| Installation ID (numeric)                                              | The trailing number in the URL after install: `https://github.com/organizations/<org>/settings/installations/<INSTALL_ID>` |
| PEM private key                                                        | Downloaded when the key was generated. GitHub shows it once, so save it now                                                |

They become `HUB_GITHUB_APP_OWNER`, `HUB_GITHUB_APP_ID`, `HUB_GITHUB_APP_INSTALL_ID`, and `HUB_GITHUB_APP_PRIVATE_KEY` (or the chart equivalents under `hub.github.app.*`) in [install Step 4](/install/lunar-hub/self-hosted/install-walkthrough.md#git-platform-credentials). All four are required together; a partial App configuration is rejected at startup.

Fronting several orgs from one Hub takes a different shape. See [multiple organizations](/install/git-platforms/github.md#multiple-organizations).
{% endtab %}

{% tab title="GitLab" %}
GitLab has no App model, so the Hub authenticates with a **group access token**, one per top-level group, which also covers every subgroup and project beneath it. [GitLab → Create the token](/install/git-platforms/gitlab.md#create-the-token) walks through creating it, and through the one choice that has consequences: which account it belongs to.

Capture these before continuing:

| What               | Detail                                                                     |
| ------------------ | -------------------------------------------------------------------------- |
| Group access token | `api` scope, `Maintainer` or `Owner` role, created on your top-level group |
| Group path         | The top-level group the token was created on, e.g. `acme`                  |
| Host               | `gitlab.com`, or your instance hostname if self-managed                    |

They become one `HUB_GITLAB_TOKENS` entry in [install Step 4](/install/lunar-hub/self-hosted/install-walkthrough.md#git-platform-credentials). Serving several top-level groups from one Hub? See [multiple groups](/install/git-platforms/gitlab.md#multiple-groups).

{% hint style="warning" %}
**Self-managed GitLab must be listed by host.** Lunar decides whether a host is GitLab from this configuration, so note your instance hostname now. An omitted host is treated as GitHub, and every operation against it then fails confusingly. `gitlab.com` is recognized without configuration.
{% endhint %}
{% endtab %}
{% endtabs %}

## Step 6 — Plan your Kubernetes secrets

Several Kubernetes secrets come into play at install time. You create the first four below; the chart auto-generates the rest (with `helm.sh/resource-policy: keep`, so they survive upgrades and uninstalls).

| Secret                     | Who creates it             | Contents                                                                                                                                                                                                     |
| -------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `lunar-db`                 | You                        | DB `username` and `password`                                                                                                                                                                                 |
| `lunar-github-app`         | You (GitHub only)          | `private-key` — the PEM from Step 5                                                                                                                                                                          |
| `lunar-gitlab-token`       | You (GitLab only)          | `token` — the group access token from Step 5, mounted as a file                                                                                                                                              |
| `lunar-hub-licence`        | You                        | `hub-licence.jwt` — Hub licence token                                                                                                                                                                        |
| `regcred`                  | You (derived from licence) | GHCR image-pull credentials — generated by [`lunar licence pull-secret`](/install/lunar-hub/self-hosted/install-walkthrough.md#pulling-images-from-ghcr) from your licence JWT                               |
| `<release>-auth-token`     | Chart (auto-generated)     | Shared bearer token for the CLI and CI tracers                                                                                                                                                               |
| `<release>-github-webhook` | Chart (auto-generated)     | Webhook signing secret for GitHub — the Hub registers it automatically when it creates per-repo webhooks                                                                                                     |
| `<release>-gitlab-webhook` | Chart (auto-generated)     | The same, for GitLab project hooks. Override it per group with a `webhook_secret` in `HUB_GITLAB_TOKENS`, or per App with one in `HUB_GITHUB_APPS` on the GitHub side                                        |
| `<release>-grafana-admin`  | Chart (auto-generated)     | Grafana admin username and password — **only when the chart runs Grafana for you** ([Option A](/install/lunar-hub/self-hosted/install-walkthrough.md#grafana))                                               |
| `<release>-grafana-db`     | Chart (auto-generated)     | Read-only `grafana_user` DB-role password (used by the dashboards' datasource) — whenever the provisioning Job runs (bundled **or** your own Grafana). BYO via `grafana.provisioning.dbPassword.secretName`. |

Pointing Lunar at **your own Grafana** ([Option B or C](/install/lunar-hub/self-hosted/install-walkthrough.md#grafana))? The chart doesn't generate `<release>-grafana-admin` — you create a secret holding your Grafana service-account token (or admin username/password). The `<release>-grafana-db` secret above is still chart-managed. See [Install Step 4 → Grafana](/install/lunar-hub/self-hosted/install-walkthrough.md#grafana).

{% hint style="info" %}
**Your licence also covers the CI tracer.** The credential the chart uses to pull Lunar's container images — derived from your licence with [`lunar licence pull-secret`](/install/lunar-hub/self-hosted/install-walkthrough.md#pulling-images-from-ghcr) for the `regcred` secret above — is also what the Hub uses to fetch the CI tracer binary for your runners. You don't need a separate credential for the CI tracer.

If your licence doesn't include CI tracer distribution, the Lunar CLI reports a clear error the first time a runner tries to download the tracer. If you plan to use the CI tracer, make sure your licence covers it — contact Earthly if you're unsure.
{% endhint %}

**GitOps alternative.** If your setup needs deterministic secret management, you can pre-create any chart-managed secret and point the chart at it (e.g. `hub.github.webhookSecret.secretName`). See the [chart README](https://github.com/earthly/charts/blob/main/README.md) for the full list of `*.secretName` values you can override.

The [install walkthrough](/install/lunar-hub/self-hosted/install-walkthrough.md#step-3-create-kubernetes-secrets) has the exact `kubectl create secret` commands for the three user-created secrets.

## Step 7 — Size for capacity

The chart sets **no** default resource requests or limits. The numbers below are reasonable starting points, but you should monitor and adjust them based on your specific needs.

| Component | CPU request | Memory request |
| --------- | ----------- | -------------- |
| Hub       | 500m        | 1 Gi           |
| Operator  | 100m        | 128 Mi         |

**Run pods** are short-lived batch pods spawned by the operator. Each pod contains N **user containers** (one per script in the batch) plus an init container and a sidecar. Per-user-container resources come from `operator.snippetContainerSpec*` — the operator's built-in defaults request 250m / 256 Mi for collectors and catalogers, 50m / 128 Mi for policies.

**Batch size** is per script type via `operator.batchMaxCount*` (defaults: 10 for collectors and catalogers, 20 for policies — policies pack denser because each container is lighter). Concurrent batch pods are capped by `operator.maxConcurrent` (default `10`), shared across script types.

Hub workers that feed run pods can be capped with `HUB_MAX_WORKERS_COLLECT`, `HUB_MAX_WORKERS_POLICY`, `HUB_MAX_WORKERS_CRON_COLLECT`, and `HUB_MAX_WORKERS_CATALOGER`; `0` means unlimited. Tune these when increasing `operator.maxConcurrent`. The Hub and operator have separate Postgres pool caps for operator work via `HUB_MAX_OPERATOR_POOL_SIZE` and `OPERATOR_MAX_POOL_SIZE`.

The run-pods namespace needs headroom for:

```
maxConcurrent × batchMaxCount(type) × per-user-container requests
```

At defaults that's \~25 GiB of concurrent-run memory per script type (10 pods × 10 collectors × 256 Mi, or 10 × 20 × 128 Mi for policies).

If you want sizing guidance once you're past initial install, please reach out.

## Next steps

When your prerequisites are in place:

* [Install walkthrough](/install/lunar-hub/self-hosted/install-walkthrough.md) — step-by-step from zero to a working Hub.
* [Chart README](https://github.com/earthly/charts/blob/main/README.md) — complete `values.yaml` reference.


---

# 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/self-hosted/prerequisites.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.
