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

# Install

> Install the self-hosted control plane with Helm, then enroll your first cluster with the Radar CLI.

## Prerequisites

* A Kubernetes cluster reachable from your team's browsers, with schedulable `linux/amd64` nodes for the control-plane workloads (1.27+; [requirements](/docs/cloud/self-hosted/requirements)).
* Helm 3.14 or newer. The configuration-changing upgrade examples use `--reset-then-reuse-values`, introduced in Helm 3.14, to apply new chart defaults while preserving the release's existing user-supplied values.
* A Postgres instance reachable from that cluster ([requirements](/docs/cloud/self-hosted/requirements)).
* A DNS record pointing at the cluster's Ingress, with a TLS cert.
* A Skyhook-issued license JWT (your account team mints one at signing).
* A 32+ byte secret for sealing session cookies. Generate with `openssl rand -base64 48`.

## 1. Add the chart repo

```bash theme={null}
helm repo add skyhook https://skyhook-io.github.io/helm-charts
helm repo update
```

## 2. Decide on a Postgres source

Pick one of three patterns. Examples below all use `--set` for clarity; production should land these in a `values.yaml` you check in (excluding secrets).

<Tabs>
  <Tab title="Managed (RDS / Cloud SQL / etc.)">
    Easiest. Drop the DSN into a Secret you provision yourself, then point the chart at it.

    ```bash theme={null}
    kubectl create secret generic radar-hub-pg \
      --from-literal=dsn='postgres://radar:strongpassword@db.acme.internal:5432/radar_hub?sslmode=require'
    ```

    ```bash theme={null}
    helm install radar-hub skyhook/radar-hub \
      --namespace radar-hub --create-namespace \
      --set postgres.existingSecret=radar-hub-pg \
      ... # the rest below
    ```
  </Tab>

  <Tab title="CloudNativePG">
    If you already run a CNPG `Cluster`, the chart reads its app-user Secret directly:

    ```bash theme={null}
    helm install radar-hub skyhook/radar-hub \
      --namespace radar-hub --create-namespace \
      --set postgres.cnpg.cluster=acme-pg \
      --set postgres.cnpg.secretName=acme-pg-app \  # optional; defaults to <cluster>-app
      ... # the rest below
    ```

    The chart never installs the operator - bring your own.
  </Tab>

  <Tab title="Inline DSN (eval only)">
    Lowest friction for a pilot. The DSN ends up in plaintext in the rendered Secret unless you pre-seal with Sealed-Secrets / SOPS:

    ```bash theme={null}
    helm install radar-hub skyhook/radar-hub \
      --namespace radar-hub --create-namespace \
      --set postgres.dsn='postgres://radar:pw@host:5432/db?sslmode=disable' \
      ... # the rest below
    ```
  </Tab>
</Tabs>

## 3. Install

A representative invocation:

```bash theme={null}
helm install radar-hub skyhook/radar-hub \
  --namespace radar-hub --create-namespace \
  --set hub.publicURL=https://radar.acme.example \
  --set hub.cookiePassword="$(openssl rand -base64 48)" \
  --set license.key="$RADAR_HUB_LICENSE" \
  --set postgres.existingSecret=radar-hub-pg \
  --set auth.breakGlass.email=admin@acme.example \
  --set auth.breakGlass.password=changeme123 \
  --set ingress.enabled=true \
  --set ingress.className=nginx \
  --set ingress.host=radar.acme.example \
  --set ingress.tls.enabled=true \
  --set ingress.tls.secretName=radar-acme-tls
```

What happens:

1. Helm renders the manifests and creates the chart-managed Secret.
2. The `radar-hub` Pod starts. Its `migrate` initContainer runs `/migrate` against your Postgres and applies any pending schema migrations transactionally before the main `hub` container starts.
3. Once migrations finish, the main `hub` and `radar-hub-web` containers come up. The control plane logs license-verification status on first start (look for `license verified` or `license is expired (warn-only ...)` or `license verification failed`).
4. The control plane's first-boot probe says `self-hosted install detected with no orgs yet` - that's expected; the singleton org gets created on your first break-glass login.

Watch it come up:

```bash theme={null}
kubectl -n radar-hub get pods -w
kubectl -n radar-hub logs deploy/radar-hub-hub -c migrate
kubectl -n radar-hub logs deploy/radar-hub-hub -c hub --tail=50
```

## 4. First sign-in

Open `https://radar.acme.example` in a browser. The login page renders the break-glass form when no OIDC is configured (and a "Sign in with SSO" button when it is).

Sign in with the email + password you set in step 3. The control plane:

* Creates the singleton org (named from `--set hub.orgName=...`, default `Default`).
* Records you as the org's first owner.
* Sets the sealed session cookie and redirects you to the web app.

You should now see the cluster list (empty), the deployment-shape config in the help menu (custom docs URL etc.), and NO Billing tab in Settings.

## 5. Connect your first cluster

The recommended path is the Radar CLI. It inspects the selected Kubernetes cluster and opens this control plane's consent page. For a fresh install or native Helm release, it proves that your kubeconfig identity can make the required changes and then installs or updates Radar. For a supported GitOps-managed release, it generates a source-of-truth handoff instead of mutating the live cluster.

<Warning>
  CLI enrollment requires Radar CLI 1.8.3 or newer and `radar-hub` chart 1.1.0 or newer using matching Hub and Web image tags. Older control-plane images do not implement the consent device flow, and mixed Hub/Web tags are unsupported.
</Warning>

Before you begin:

* Install [Radar CLI 1.8.3 or newer](/docs/install) on the operator workstation. Verify the command is present with `kubectl radar cloud install --help`.
* Sign in to `https://radar.acme.example` with an account allowed to add clusters to the singleton organization. If the organization limits cluster creation to owners, an owner must approve the consent page.
* Give the CLI a kubeconfig identity that can read the existing Radar deployment and Helm release, create the target namespace when needed, and create or update the rendered Radar resources, including its Secret and cluster-scoped RBAC.
* Allow the workstation to reach `https://skyhook-io.github.io/helm-charts`, the customer cluster to pull the Radar image, and both the workstation and customer cluster to trust the TLS certificate for `https://radar.acme.example`.

The examples below use `kubectl radar`, which works with every supported CLI installation. Homebrew also provides the shorter `radar` alias.

Pass the intended kubeconfig context explicitly when you know it:

```bash theme={null}
kubectl radar cloud install \
  --hub-url https://radar.acme.example \
  --context prod-us-east-1
```

The CLI prints the exact target and plan before changing anything. For a fresh install or native Helm adoption, it runs a server-side Kubernetes dry run plus explicit permission checks before creating a control-plane cluster or minting a token, so a missing Kubernetes permission fails before the browser approval step. A GitOps handoff does not mutate the live cluster; readable, unambiguous ownership is its local safety gate.

For a fresh install or native Helm adoption, add `--dry-run` to validate discovery, chart preparation, and the planned Kubernetes mutation permissions without opening the consent flow or changing either system. For GitOps, `--dry-run` validates discovery, verified controller ownership, and the exact chart handoff; it cannot prove write access to the source repository or permissions the reconciler will use.

If you omit `--context`, the CLI prints the current kubeconfig context and asks you to confirm it. `-y` / `--yes` skips only that current-context confirmation; it never grants consent to adopt an existing installation. `--namespace` defaults to `radar`, and `--release` defaults to `radar`. Either flag by itself sets the fresh-install target while keeping cluster-wide discovery enabled; one unambiguous existing Radar installation still takes precedence and supplies its actual namespace and release.

Pass both `--namespace` and `--release` when you deliberately want to force an exact target. Exact targeting disables broader discovery, so do not pass both flags merely to restate the defaults when you want the CLI to find an existing installation elsewhere in the cluster.

The browser page names the target organization and cluster. Approve it there. For a fresh install or native Helm adoption, leave the CLI running while it provisions Radar and confirms the outbound tunnel. For GitOps, apply the printed handoff through the source of truth while the CLI waits for the reconciled agent. On success it prints the cluster name, a direct web-app link, and a context-pinned rollout command.

By default, a fresh install, native Helm adoption, and GitOps handoff target the latest published stable Radar chart. This can upgrade an older Radar release and clears an explicit image tag so the selected chart's stable Radar version runs. The plan calls this out before approval. Use `--chart-version <version>` to select a particular published stable chart. The selected chart must be version 1.5.4 or newer. Native Helm adoption refuses a chart downgrade; GitOps handoff does the same when the existing chart version is available from the live workload metadata.

### Existing native Helm installation

When the selected cluster already has an official Radar Helm release, the CLI offers to adopt that release in place. Adoption preserves its existing values and image repository, adds the self-hosted connection settings through a fixed token Secret, and performs an atomic Helm upgrade.

Interactive adoption requires a separate confirmation after the plan. Self-upgrade support is enabled by default so an owner can later initiate an upgrade from the web app. Use `--no-self-upgrade` if your operating model requires every upgrade to go through the terminal or GitOps.

Adoption preserves the existing `rbac.helm`, `rbac.secrets`, `rbac.podExec`, `rbac.portForward`, and `rbac.metrics` settings by default. Pass `--enable-cloud-features` only when you explicitly want to turn all five capabilities on. Fresh installs already enable them.

```bash theme={null}
kubectl radar cloud install \
  --hub-url https://radar.acme.example \
  --context prod-us-east-1
```

For non-interactive automation, `--adopt-existing` is the separate assertion that the existing release may be changed; `-y` alone remains insufficient:

```bash theme={null}
kubectl radar cloud install \
  --hub-url https://radar.acme.example \
  --context prod-us-east-1 \
  --adopt-existing
```

After a successful adoption, the CLI prints a rollback sequence that restores the previous Helm revision before removing the connection token Secret or connected control-plane cluster. Keep that sequence with your change record.

### GitOps-managed installation

When an official Radar chart Deployment carries exactly one verified Argo CD or Flux ownership signal, the CLI does not run an imperative Helm upgrade that the reconciler would undo. It prints the detected owner, the selected stable chart target, and a values handoff containing the connection Secret reference and `cloud.*` settings. Approve the browser consent, commit the generated changes to the source of truth, and let the reconciler apply them. The CLI can remain open for up to five minutes to confirm the tunnel, or you can verify the cluster later in the web app.

The GitOps handoff forces `rbac.selfUpgrade=false` because a live image patch would drift from Git. It leaves the other feature RBAC settings unchanged unless you pass `--enable-cloud-features`, which adds the five explicit capability opt-ins to the merge fragment.

An ambiguous or only weakly inferred GitOps owner is not mutated automatically. Resolve the ownership ambiguity or update the source manually.

### Manual Helm alternative

Use the web app's **Connect a cluster** wizard when you need to integrate the values into another workflow yourself. It mints the same `rhc_*` cluster credential and generates the Helm values for this self-hosted endpoint:

```bash theme={null}
helm repo add skyhook https://skyhook-io.github.io/helm-charts
helm repo update skyhook
kubectl create namespace radar --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic radar-cloud-config -n radar \
  --from-literal=token=<one-time-token> \
  --dry-run=client -o yaml | kubectl apply -f -
helm upgrade --install radar skyhook/radar \
  --namespace radar \
  --set cloud.enabled=true \
  --set cloud.url="wss://radar.acme.example/agent" \
  --set cloud.clusterName=<cluster-id> \
  --set cloud.existingSecret=radar-cloud-config \
  --set auth.mode=proxy \
  --set rbac.helm=true \
  --set rbac.secrets=true \
  --set rbac.podExec=true \
  --set rbac.portForward=true \
  --set rbac.metrics=true \
  --set rbac.selfUpgrade=true
```

The wizard generates this command with the real token and cluster ID. For an existing release it also offers a `--reuse-values` variant that preserves your settings. Alternatively, translate the generated values and Secret into your GitOps source. Do not put the raw token in Helm values checked into Git.

## 6. (Optional) Add OIDC

Once break-glass is verified working, layer OIDC on top:

```bash theme={null}
helm upgrade radar-hub skyhook/radar-hub \
  --namespace radar-hub \
  --reset-then-reuse-values \
  --set auth.oidc.issuer=https://acme.okta.com \
  --set auth.oidc.clientID=$OIDC_CLIENT_ID \
  --set auth.oidc.clientSecret=$OIDC_CLIENT_SECRET \
  --set auth.oidc.callbackURL=https://radar.acme.example/api/auth/oidc/callback \
  --set auth.oidc.groupClaim=groups \
  --set "auth.oidc.adminGroups={radar-admins}" \
  --set auth.oidc.defaultRole=member
```

Break-glass remains available regardless - the route stays mounted as long as `auth.breakGlass.email` is set.

See [Authentication](/docs/cloud/self-hosted/auth) for IdP-specific recipes.

## 7. (Optional) Pin to a specific release

The chart and images are one release unit. Pin the chart version and both image tags when you need to stage an exact release:

```bash theme={null}
RADAR_HUB_CHART_VERSION="CHART_VERSION"
RADAR_HUB_RELEASE="MATCHING_RELEASE_TAG"
helm show chart skyhook/radar-hub --version "$RADAR_HUB_CHART_VERSION"
helm upgrade radar-hub skyhook/radar-hub \
  --namespace radar-hub \
  --reset-then-reuse-values \
  --version "$RADAR_HUB_CHART_VERSION" \
  --set-string image.hub.tag="$RADAR_HUB_RELEASE" \
  --set-string image.web.tag="$RADAR_HUB_RELEASE"
```

Replace `CHART_VERSION` with the selected `radar-hub` chart version and `MATCHING_RELEASE_TAG` with the `appVersion` shown by `helm show chart`, after confirming that tag is published for both Hub and Web. Never mix a chart with images from another release, or mix Hub and Web release tags.

## Common gotchas

* **`HUB_COOKIE_PASSWORD must be at least 32 bytes`** - the value passed to `hub.cookiePassword` is shorter than 32 ASCII characters. Use `openssl rand -base64 48`.
* **OIDC redirect URI mismatch** - your IdP must have `https://<hub.publicURL>/api/auth/oidc/callback` registered as an exact match. No path normalization.
* **`postgres: set one of postgres.dsn, postgres.existingSecret, or postgres.cnpg.cluster`** - the chart fails fast at templating time when zero Postgres options are set. Pick one.
* **Migrate initContainer stuck pulling** - the hub Pod stays in `Init:` status. Check `kubectl describe pod` for image-pull errors against your registry mirror.
* **Control plane logs `license verification failed`** - confirm the JWT was pasted whole (no whitespace) and the build was cut against the matching public key. See [Licensing](/docs/cloud/self-hosted/licensing).
