The Launchpad for Nebari — service discovery and access portal.
A real-time, SSO-aware landing
page that gives users one place to find and launch every service on the platform.
Architecture · Quick Start · Helm Install · Development · API Reference · Local Dev Guide · Contributing
Status: Under active development as part of Nebari Infrastructure Core (NIC). APIs and behavior may change without notice.
Nebari Landing is the Launchpad — the entry point for every NIC-managed cluster. It surfaces all deployed
NebariApp services in a single, authenticated UI so users can discover and launch platform tools without knowing
individual URLs.
It is deployed automatically by the Nebari Infrastructure Core
as part of NIC's foundational software layer, and reconciled by the
Nebari Operator — the controller that orchestrates routing, TLS, and
OIDC client provisioning for every NebariApp on the cluster. Two components work together:
- webapi — Go REST API + WebSocket hub that watches
NebariAppCRs via the Kubernetes API, validates JWTs from Keycloak, manages service pins, access requests, and real-time notifications over WebSocket. - frontend — Vite/React SPA (shadcn/ui + Tailwind) served by nginx. The browser authenticates directly with Keycloak via keycloak-js using PKCE; there is no server-side session and no proxy in front of the SPA.
flowchart TD
Browser["Browser (SPA + keycloak-js PKCE)"]
Keycloak[("Keycloak")]
Nginx["nginx (serves SPA, proxies /api/*)"]
Webapi[("webapi")]
Watcher["NebariApp CR watcher (Kubernetes API)"]
Redis[("Redis<br/>pins, access requests,<br/>WS tickets (30 s TTL),<br/>service cache")]
Hub["WebSocket hub (real-time notifications)"]
Browser -->|PKCE: discovery + tokens| Keycloak
Browser -->|"REST: Authorization: Bearer JWT<br/>WS: POST /ws-ticket → wss://.../ws?ticket=<id>"| Nginx
Nginx --> Webapi
Webapi --> Watcher
Webapi --> Redis
Webapi --> Hub
Browsers cannot set an Authorization header on a WebSocket upgrade request, so the SPA exchanges its Bearer JWT for
a short-lived single-use ticket and passes it as ?ticket=<id> on the upgrade. Non-browser clients can skip the
exchange and send the Bearer token directly. See docs/api.md for the full
flow.
Both pods are deployed via the charts/nebari-landing Helm chart, typically managed by ArgoCD through NIC.
Release artifacts — the Go webapi binary (linux/darwin, amd64/arm64) and the packaged Helm chart — are attached to every GitHub release via GoReleaser.
| Feature | Description |
|---|---|
| Service Discovery | Automatically surfaces every NebariApp on the cluster — no manual registration required |
| Real-time Updates | WebSocket hub pushes service changes and notifications to the browser instantly |
| SSO-Aware | keycloak-js PKCE in the browser, JWT validation by the webapi — users land authenticated, admins see admin controls |
| Pins & Access Requests | Users can pin favourite services and request access to restricted ones |
| shadcn/ui + Tailwind | Accessible UI primitives with theme tokens; no design-system runtime to ship |
| Runtime Branding | Custom title, logo, favicon, and CSS theme tokens — no image rebuild required |
The frontend reads /config.json at startup (rendered from values.yaml by the Helm chart) and applies branding
settings to the document before React mounts. No image rebuild is needed — change values.yaml and redeploy.
| Field | Type | Description |
|---|---|---|
frontend.branding.title |
string | Browser tab title (default: "Nebari | Launchpad") |
frontend.branding.logoUrl |
string | URL to a custom header logo image |
frontend.branding.logoUrlDark |
string | URL to a custom dark-mode header logo. Falls back to logoUrl, then the built-in logo, when empty |
frontend.branding.faviconUrl |
string | URL to a custom favicon |
frontend.branding.theme.light |
map | CSS variable overrides for light mode |
frontend.branding.theme.dark |
map | CSS variable overrides for dark mode |
The theme.light and theme.dark maps accept any combination of these token names (camelCase):
primary · primaryForeground · background · foreground · secondary · secondaryForeground · muted · mutedForeground · accent · accentForeground · border · ring · radius
Each token maps to a CSS custom property on :root (light) or .dark (dark). For example, primary: "#0066cc"
becomes --primary: #0066cc;.
frontend:
branding:
title: "My Platform"
logoUrl: "https://example.com/logo.png"
logoUrlDark: "https://example.com/logo-dark.png"
faviconUrl: "https://example.com/favicon.ico"
theme:
light:
primary: "#0066cc"
primaryForeground: "#ffffff"
radius: "0.25rem"
dark:
primary: "#4da6ff"
primaryForeground: "#000000"Security note: Token values are validated at runtime. Values containing CSS injection vectors (
;,{,},url(),expression(),javascript:, etc.) are silently dropped.
Note: In a full Nebari / NIC deployment the chart is managed by the Nebari Operator and ArgoCD — you do not need to install it manually.
helm repo add nebari https://nebari-dev.github.io/helm-repository
helm repo updatehelm upgrade --install nebari-landing nebari/nebari-landing \
--namespace nebari-system --create-namespace \
--set frontend.keycloak.url=https://<keycloak-host> \
--set frontend.keycloak.realm=<realm> \
--set webapi.keycloak.url=http://keycloak-keycloakx-http.keycloak.svc.cluster.local:8080 \
--set webapi.keycloak.realm=<realm> \
--set webapi.keycloak.issuerUrl=https://<keycloak-host>frontend.keycloak.url is the public Keycloak base URL the browser uses for PKCE redirects (Keycloak 17+ does not
have an /auth context root). webapi.keycloak.url is the in-cluster URL the webapi uses to fetch JWKS; the
issuerUrl is what the webapi validates the iss claim against and must match what the browser sees.
See charts/nebari-landing/values.yaml for the full set of configurable values.
| Tool | Minimum version | Notes |
|---|---|---|
docker |
24+ | Used as the minikube driver |
kubectl |
1.28+ | Cluster interaction |
helm |
3.14+ | Installs Keycloak and PostgreSQL |
minikube |
any | Auto-downloaded to .bin/ if absent |
node |
22+ | Frontend development only (see frontend/.node-version) |
python3 |
3.10+ | Integration test script only |
See dev/QUICKSTART.md for the full local dev walkthrough.
# Setup the project
make -f dev/Makefile setup# Restart an existing cluster (after minikube was stopped or the host rebooted)
make -f dev/Makefile cluster-start
make -f dev/Makefile port-forward
# Tear down the deployed app (keeps the cluster running)
make -f dev/Makefile uninstall
# Or delete the cluster entirely
make -f dev/Makefile cluster-delete# Start dev-watch: Vite serves the SPA with hot-reload, proxied through the
# in-cluster nginx so /api/* calls reach the real webapi.
make -f dev/Makefile dev-watch
# Stop dev-watch. Run this before deploying a manually-built frontend image,
# otherwise the watcher will keep overwriting the deployed bundle.
make -f dev/Makefile stop-dev-watch# Build the binary
make build
# Run unit tests
make test
# Port-forward a running webapi deployment
make pf# Package the chart into dist/ (useful for local linting; not the release path)
make helm-packageReleases are produced by the Release prep workflow — see docs/maintainers/release-checklist.md. There are no local
maketargets for cutting a release.
cd frontend
npm ci
npm run devNote:
npm run devserves the SPA athttp://localhost:5173but does not proxy/api/*calls — those require a running webapi. For a fully connected local dev loop (Keycloak + webapi + frontend with hot-reload) use the dev cluster described in dev/QUICKSTART.md.
Frontend-only path: if you're iterating on the SPA and don't need the operator or webapi, use the lighter docker-compose + MSW workflow in docs/dev-quickstart.md — real Keycloak login, mocked
/api/v1/*driven by the OpenAPI spec, ~10-second boot.
cmd/— webapi entry point (main.go).internal/— webapi packages:accessrequests/— Access request store.api/— HTTP handlers and routes.app/— Application wiring.auth/— JWT validation.cache/— Service cache (backed by Redis).health/— Health check endpoint.keycloak/— Keycloak client.notifications/— Notification store.pins/— Pin store.watcher/— NebariApp CR watcher.websocket/— WebSocket hub.wsticket/— Single-use WebSocket ticket store (Redis-backed).
frontend/src/— Vite/React SPA:api/— Typed API clients.app/— App shell and global CSS (Tailwind + theme tokens).auth/— Keycloak integration (keycloak-js PKCE).components/— UI components.
charts/nebari-landing/— Helm chart.dev/— Local dev environment (minikube + Keycloak).docs/:api.md— HTTP API reference (auto-generated).design/— Architecture and design documents.maintainers/— Release checklist and maintainer guides.static/imgs/— Screenshots and static assets.
test/e2e/— End-to-end tests (Ginkgo).cmd/gendocs/— Injects auto-generated sections intodocs/api.md.cmd/genapicard/— Rendersdocs/assets/api-{dark,light}.svgendpoint cards.
# Unit tests with coverage
make test
# HTML coverage report
make test-html
# End-to-end tests (requires a live cluster with CRDs installed)
make test-e2eSee docs/api.md for the full HTTP API reference (auto-generated endpoint table, per-route detail, and a dark/light SVG card preview at the top).
The webapi can also serve its OpenAPI 3.1 spec and a server-rendered HTML
reference at runtime when started with --enable-docs (env:
ENABLE_DOCS=true, chart value: webapi.docsEnabled=true). The viewer is a
single self-contained page (no JavaScript, no CDN) rendered from the embedded
spec at startup. Both routes are absent without the flag — never enable in
production:
./bin/webapi --enable-docs ...
# Spec: curl http://localhost:8080/api/v1/docs/openapi.json
# Viewer: open http://localhost:8080/api/v1/docsTo regenerate the spec, the SVG cards, and docs/api.md after editing
handler annotations:
make docsmake fmt # go fmt
make vet # go vetTo test the full binary build locally before tagging:
goreleaser release --snapshot --clean
# Artifacts land in dist/Contributions are welcome! To get started:
git clone https://github.com/nebari-dev/nebari-landing.git
cd nebari-landing
# Build the webapi binary
make build
# Run unit tests
make test
# Start the local dev environment (full minikube + Keycloak + webapi + frontend bootstrap)
make -f dev/Makefile setupDocumentation:
- Contributing Guide - Complete development workflow
- Release Checklist - For maintainers creating releases
- API Reference - WebAPI endpoint documentation
See our issue tracker for open issues.
Apache License 2.0 — see LICENSE for details.