Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions docs/toolhive/guides-cloud-ui/configuration.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
title: Cloud UI configuration
sidebar_label: Configuration
description:
Configure environment variables and OIDC authentication for the ToolHive Cloud
UI.
---

This guide covers the environment variables and OIDC settings that the ToolHive
Cloud UI reads at startup. For end-to-end deployment steps on Kubernetes, see
[Deploy the Cloud UI](./deployment.mdx).

## Environment variables

The Cloud UI is configured entirely through environment variables. In a
Kubernetes deployment, supply them through a Kubernetes Secret referenced from
`envFrom` (recommended) or through the chart's `env` values.

### Required variables

| Variable | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------- |
| `OIDC_ISSUER_URL` | Issuer URL of your OIDC provider (for example, `https://your-org.okta.com`) |
| `OIDC_CLIENT_ID` | OAuth2 client ID registered with your OIDC provider |
| `OIDC_CLIENT_SECRET` | OAuth2 client secret for the registered client |
| `BETTER_AUTH_SECRET` | Secret used to encrypt session tokens. Generate one with `openssl rand -base64 32` |
| `BETTER_AUTH_URL` | Public URL where the Cloud UI is reachable (for example, `https://cloud-ui.example.com`) |
| `API_BASE_URL` | URL of the Registry Server API (for example, `http://my-registry-api.toolhive-system.svc.cluster.local:8080`) |

### Optional variables

| Variable | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| `DATABASE_URL` | PostgreSQL connection string for the auth database. When omitted, the Cloud UI uses an in-memory SQLite database |
| `TRUSTED_ORIGINS` | Comma-separated list of allowed CORS origins |

:::warning

The default in-memory SQLite database is not suitable for production. Sessions
are lost on pod restart and can't be shared across replicas. For multi-replica
deployments or any non-evaluation environment, set `DATABASE_URL` to point at a
managed PostgreSQL instance.

:::

## Configure OIDC authentication

The Cloud UI delegates authentication to an external OIDC provider using
[Better Auth](https://www.better-auth.com/). It works with any
standards-compliant provider, including Okta, Microsoft Entra ID, Auth0, and
Keycloak.

To configure your provider:

1. Register a new OAuth2 / OIDC application in your identity provider.
2. Set the redirect URI to `<BETTER_AUTH_URL>/api/auth/callback/oidc` (for
example, `https://cloud-ui.example.com/api/auth/callback/oidc`).
3. Request the `openid`, `profile`, and `email` scopes.
4. Copy the issuer URL, client ID, and client secret into a Kubernetes Secret as
described in [Deploy the Cloud UI](./deployment.mdx).

## Helm chart values

The chart in
[`toolhive-cloud-ui/helm`](https://github.com/stacklok/toolhive-cloud-ui/tree/main/helm)
supports the following customizations beyond environment variables:

- Replica count and horizontal pod autoscaling (HPA)
- Resource requests and limits
- Liveness, readiness, and startup probes
- Pod and container security contexts
- Image pull secrets for private registries
- Custom Service types (ClusterIP, NodePort)
- Additional volumes and volume mounts

Refer to the chart's
[`values.yaml`](https://github.com/stacklok/toolhive-cloud-ui/blob/main/helm/values.yaml)
for the full set of configurable parameters and their defaults.

:::info

The chart does not ship an Ingress template. To expose the Cloud UI outside the
cluster, create an Ingress resource separately or use a Service of type
`NodePort` or `LoadBalancer`. See
[Step 5 of the deployment guide](./deployment.mdx#step-5-expose-the-cloud-ui)
for an Ingress example.

:::

## Next steps

- [Deploy the Cloud UI](./deployment.mdx) end-to-end on Kubernetes if you
haven't already.
- [Publish servers](../guides-registry/publish-servers.mdx) to populate your
catalog with MCP server entries.
- Set up [Registry Server authentication](../guides-registry/authentication.mdx)
to control access to the catalog API the Cloud UI reads from.
286 changes: 286 additions & 0 deletions docs/toolhive/guides-cloud-ui/deployment.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
---
title: Deploy the Cloud UI
sidebar_label: Deploy on Kubernetes
description:
Deploy the ToolHive Cloud UI on Kubernetes alongside a Registry Server, with
OIDC authentication.
---

This guide walks you through deploying the ToolHive Cloud UI on a Kubernetes
cluster using the Helm chart shipped in the
[`toolhive-cloud-ui`](https://github.com/stacklok/toolhive-cloud-ui) repository.
Because the Cloud UI requires both a running Registry Server and an OIDC
identity provider, it isn't a "few-minute" setup, so plan to work through this
guide end-to-end.

## What you'll learn

- How to install the Cloud UI Helm chart in an existing Kubernetes cluster
- How to wire the Cloud UI to a running Registry Server
- How to configure OIDC authentication using a Kubernetes Secret
- How to expose the Cloud UI for browser access

## Prerequisites

Before starting, make sure you have:

- A running Kubernetes cluster (v1.24+). The
[Registry Server quickstart](../guides-registry/quickstart.mdx) walks through
a local kind cluster you can reuse.
- A **Registry Server** reachable from the cluster. If you don't have one yet,
complete the [Registry Server quickstart](../guides-registry/quickstart.mdx)
first. Take note of the in-cluster Service name and port (the quickstart
exposes `my-registry-api:8080` in the `toolhive-system` namespace).
- An **OIDC application** registered with your identity provider (Okta,
Microsoft Entra ID, Auth0, Keycloak, or any standards-compliant provider).
You'll need the issuer URL, client ID, and client secret.
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) and
[Helm](https://helm.sh/docs/intro/install/) v3.10+ installed locally.
- [Git](https://git-scm.com/downloads) to clone the chart repository.

:::tip[Don't have an OIDC provider yet?]

If you want to evaluate the Cloud UI without setting up a real IdP, the
`toolhive-cloud-ui` repository includes a mock OIDC provider for local
development. See the
[repository README](https://github.com/stacklok/toolhive-cloud-ui#development-modes)
for details. Use the Kubernetes path below for any environment beyond local
evaluation.

:::

## Step 1: Register the OIDC application

In your identity provider, create a new OAuth2 / OIDC application with the
following settings:

- **Application type**: Web application
- **Redirect URI**: `<CLOUD_UI_URL>/api/auth/callback/oidc`, where
`<CLOUD_UI_URL>` is the public URL where you plan to expose the Cloud UI (for
example, `https://cloud-ui.example.com/api/auth/callback/oidc`).
- **Scopes**: `openid`, `profile`, `email`

Copy the **issuer URL**, **client ID**, and **client secret** that your provider
returns. You'll reference them in the next step.

## Step 2: Clone the chart repository

The Helm chart lives inside the `toolhive-cloud-ui` repository under `helm/`.
Clone it to your workstation:

```bash
git clone https://github.com/stacklok/toolhive-cloud-ui.git
cd toolhive-cloud-ui
```

## Step 3: Create a namespace and OIDC Secret

Create a namespace for the Cloud UI and a Kubernetes Secret that holds the
sensitive configuration. Storing credentials in a Secret keeps them out of your
Helm values file and chart history.

```bash
kubectl create namespace cloud-ui
```

```bash
kubectl create secret generic cloud-ui-config \
-n cloud-ui \
--from-literal=OIDC_ISSUER_URL=https://your-org.okta.com \
--from-literal=OIDC_CLIENT_ID=<CLIENT_ID> \
--from-literal=OIDC_CLIENT_SECRET=<CLIENT_SECRET> \
--from-literal=BETTER_AUTH_SECRET=$(openssl rand -base64 32) \
--from-literal=BETTER_AUTH_URL=https://cloud-ui.example.com \
--from-literal=API_BASE_URL=http://my-registry-api.toolhive-system.svc.cluster.local:8080
```

Replace the placeholder values:

- `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`: values from
[Step 1](#step-1-register-the-oidc-application).
- `BETTER_AUTH_URL`: the public URL where users will reach the Cloud UI.
- `API_BASE_URL`: the in-cluster URL of the Registry Server Service. If you
followed the [Registry Server quickstart](../guides-registry/quickstart.mdx),
it's `http://my-registry-api.toolhive-system.svc.cluster.local:8080`.

See [Configuration](./configuration.mdx) for the full list of supported
environment variables.

## Step 4: Install the Helm chart

Install the chart from the cloned repository and reference the Secret you just
created with `envFrom`:

```bash title="Install the Cloud UI"
helm install cloud-ui ./helm \
--namespace cloud-ui \
--set envFrom[0].secretRef.name=cloud-ui-config
```

Wait for the pod to become ready:

```bash
kubectl rollout status deployment/cloud-ui -n cloud-ui --timeout=120s
```

:::info[What's happening?]

The chart deploys the Cloud UI as a single-replica Deployment with a ClusterIP
Service on port 80 that targets the container on port 3000. The container reads
all OIDC and Registry Server settings from the Secret you mounted with
`envFrom`. Check `helm/values.yaml` in the repository for the full set of
configurable parameters (replicas, resources, probes, HPA, security context).

:::

## Step 5: Expose the Cloud UI

The chart creates a ClusterIP Service by default. To make the Cloud UI reachable
from a browser, choose one of the following options.

### Option A: Port-forward (testing only)

For testing, forward the Service port to your local machine:

```bash
kubectl port-forward svc/cloud-ui 3000:80 -n cloud-ui
```

Open `http://localhost:3000` in your browser. For this to work end-to-end, set
`BETTER_AUTH_URL=http://localhost:3000` and register the matching redirect URI
(`http://localhost:3000/api/auth/callback/oidc`) in your OIDC application.

### Option B: Ingress (recommended for shared environments)

For shared or production deployments, expose the Cloud UI through an Ingress
controller. The chart does not ship an Ingress template, so create one yourself.
The example below assumes you have an Ingress controller (such as NGINX) and a
TLS certificate already configured:

```yaml title="cloud-ui-ingress.yaml"
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cloud-ui
namespace: cloud-ui
spec:
ingressClassName: nginx
tls:
- hosts:
- cloud-ui.example.com
secretName: cloud-ui-tls
rules:
- host: cloud-ui.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: cloud-ui
port:
number: 80
```

Apply the manifest:

```bash
kubectl apply -f cloud-ui-ingress.yaml
```

Make sure the host matches `BETTER_AUTH_URL` and the registered redirect URI in
your IdP.

## Step 6: Sign in

Open the Cloud UI in your browser. You should be redirected to your OIDC
provider's sign-in page. After signing in successfully, your provider redirects
you back to the Cloud UI's `/catalog` page where you can:

- Browse MCP servers registered in your Registry Server
- View server details and connection information
- Copy server URLs into your AI agents or MCP clients

If sign-in fails, see the [Troubleshooting](#troubleshooting) section below.

## Clean up

To remove the Cloud UI deployment:

```bash
helm uninstall cloud-ui -n cloud-ui
kubectl delete namespace cloud-ui
```

This leaves the Registry Server and your IdP application untouched.

## Next steps

- [Cloud UI configuration](./configuration.mdx) covers every supported
environment variable, including optional persistence with PostgreSQL and CORS
origins.
- [Publish servers](../guides-registry/publish-servers.mdx) to populate the
catalog that the Cloud UI displays.
- [Set up Registry Server authentication](../guides-registry/authentication.mdx)
to protect the catalog API the Cloud UI reads from.

## Related information

- [Registry Server quickstart](../guides-registry/quickstart.mdx) - deploy the
backend the Cloud UI depends on
- [Deploy the Registry Server](../guides-registry/deployment.mdx) - production
deployment patterns for the Registry Server

## Troubleshooting

<details>
<summary>Sign-in redirects to the IdP but never returns to the Cloud UI</summary>

The most common cause is a mismatch between the redirect URI registered in your
IdP and the value the Cloud UI sends. Confirm that:

- `BETTER_AUTH_URL` in the Secret matches the public URL the browser uses to
reach the Cloud UI (scheme, host, and port).
- The redirect URI registered in your IdP is exactly
`<BETTER_AUTH_URL>/api/auth/callback/oidc`.

Restart the pod after changing the Secret so the new values take effect:

```bash
kubectl rollout restart deployment/cloud-ui -n cloud-ui
```

</details>

<details>
<summary>Catalog page shows no MCP servers</summary>

The Cloud UI talks to the Registry Server through `API_BASE_URL`. If the catalog
is empty:

1. Confirm the Registry Server is reachable from inside the cluster:

```bash
kubectl run curl --rm -it --image=curlimages/curl --restart=Never -- \
curl -s http://my-registry-api.toolhive-system.svc.cluster.local:8080/registry/default/v0.1/servers
```

2. Confirm the registry has at least one server published. See
[Publish servers](../guides-registry/publish-servers.mdx).

</details>

<details>
<summary>Pod fails to start with missing environment variable errors</summary>

The Cloud UI requires all six environment variables listed in
[Step 3](#step-3-create-a-namespace-and-oidc-secret). Confirm the Secret
contains every key:

```bash
kubectl describe secret cloud-ui-config -n cloud-ui
```

Recreate the Secret with the missing keys, then restart the deployment.

</details>
Loading