A Nebari Software Pack for launching, managing, and observing web
apps on a Nebari Kubernetes cluster, behind Keycloak SSO. Two kinds of app are
supported: static sites (HTML/CSS/JS, served by nginx) and Python apps launched
by a pixi task β upload a zip with a pixi.toml and your source, name
the task, and the pack runs it. No Dockerfile, no image build.
Apps can be launched four ways, all converging on a single declarative App resource:
- π₯οΈ A form-based UI β dashboard with analytics, app detail with logs/events, a launch form, and direct zip/.html upload.
- π A REST API for programmatic CRUD + observability.
- π€ A coding agent (Claude Code, Codex) β connect to the in-cluster MCP server at
https://apps.<cluster-domain>/mcpand launch with natural language. - π§ The MCP server directly (launch / list / status / logs / update / stop / remove as agent tools; Keycloak device-flow auth).
Status: In development. Implemented so far: the
AppCRD and apps-operator (static + Python/pixi apps frominline/git/pvcsources), the apps-api (CRUD, logs/events/status, analytics, zip/.html upload), the apps-ui (Nebari design system: dashboard + analytics, app detail with logs, launch form with upload), the apps-mcp server (agent tools + device-flow auth at/mcp), and thenew-nebari-appskill (scaffold apps with anebari-app.yamlmanifest, then "launch it"). Apps are served athttps://<subdomain>.apps.<cluster-domain>(TLS can be switched off viatls.enabled=false, as local dev does).
Every producer β agent/MCP, API, UI, or (optional) GitOps β writes the same App custom
resource (apps.nebari.dev/v1alpha1). The apps-operator reconciles it into a Deployment +
Service + a NebariApp; the existing nebari-operator turns that
NebariApp into routing (HTTPRoute), TLS (cert-manager), Keycloak SSO, and a landing-page
tile.
UI βββββββββ
REST API βββ€ ββ Deployment (app pod)
agent/MCP ββΌββΊ App CR ββΊ apps- ββΌβ Service
GitOps βββββ (one operator ββ NebariApp ββΊ nebari-operator ββΊ HTTPRoute + TLS
contract) + Keycloak + tile
The apps-api is the single authority β the UI and MCP are thin clients over it, so launch
semantics are identical whether a human uses the form or an agent uses natural language. The API
writes App CRs directly via a ServiceAccount, so GitHub/GitOps is optional, not required.
| Component | Stack | Responsibility |
|---|---|---|
| App CRD | apps.nebari.dev/v1alpha1 |
The declarative contract for an app. |
| apps-operator | Go + controller-runtime | Reconcile App β Deployment, Service, NebariApp, status. |
| apps-api | Python / FastAPI | CRUD + observability + zip/.html upload; writes App CRs. |
| apps-ui | React + TS + Vite + nebari-design | Dashboard + analytics, launch form, app detail with logs/events. |
| apps-mcp | Python / FastMCP | Agent-facing tools at /mcp; Keycloak device-flow auth. |
| skill | Claude Code skill | Scaffold static apps + nebari-app.yaml; "launch it" via the MCP. |
- apps-ui / apps-api β the UI logs in with keycloak-js (SPA PKCE flow against a public
client its
NebariAppprovisions) and calls the API with a JWT bearer token, which the API validates against the realm JWKS. - Launched apps β no auth code at all: private apps are enforced at the gateway by the
SecurityPolicytheirNebariAppcreates;access.public: trueskips it.
nebari-apps-pack/
pack-metadata.yaml # dashboard registration
charts/nebari-apps/ # Helm chart (App CRD + operator + api + ui + mcp)
operator/ # Go operator (controller-runtime)
api/ # FastAPI backend (CRUD + observability + upload)
ui/ # React frontend (Nebari design system)
mcp/ # FastMCP server (agent tools at /mcp)
skill/ # new-nebari-app scaffolding skill (Claude Code)
dev/ # local dev loop (kind + full Nebari stack)
examples/ # sample App CRs
docs/ # user guide (Astro Starlight β packs.nebari.dev)
src/content/docs/ # guide pages (getting started, MCP, skill, references)
DESIGN.md # comprehensive design document
PLAN.md # phased implementation plan
Zip your site (or take a single .html file) and upload it from the UI's launch form, or via
the API:
curl -X POST https://apps.<cluster-domain>/api/v1/apps/upload \
-H "Authorization: Bearer $TOKEN" \
-F 'manifest={"name":"docs-site","namespace":"apps","displayName":"Docs Site",
"access":{"public":true,"subdomain":"docs-site"}}' \
-F "file=@site.zip"The uploaded files become the
App's inline source, materialized on the cluster as a ConfigMap-backed volume (text assets, ~900KB cap β use agitorpvcsource for larger sites). An archive needs anindex.htmlat its root.
Or write the App CR yourself β static apps can be sourced from inline files, a git
repository (cloned by an init container), or an existing pvc:
apiVersion: apps.nebari.dev/v1alpha1
kind: App
metadata:
name: team-site
spec:
displayName: "Team Site"
source:
type: git
git: { url: "https://github.com/org/site", ref: "main", subdir: "public" }
access:
public: false
groups: ["analysts"]
subdomain: team-sitekubectl apply it (or POST /api/v1/apps, or use the UI's launch form) β reachable at
https://team-site.apps.<cluster-domain> behind Keycloak SSO.
Install the scaffolding skill and connect the MCP server:
cp -r skill/new-nebari-app .claude/skills/ # or ~/.claude/skills/
claude mcp add --transport http nebari-apps https://apps.<cluster-domain>/mcpThen, in Claude Code:
You: create a public static site called release-notes (the skill scaffolds
release-notes/with real files +nebari-app.yaml) You: launch it (the agent reads the manifest, callslaunch_app, polls status, and replies withhttps://release-notes.apps.<cluster-domain>)
The manifest maps 1:1 onto App.spec, so the same directory works with the UI's upload
form, the API, or GitOps.
- A Nebari cluster with nebari-operator (provides the
NebariAppCRD), Envoy Gateway, cert-manager issuer (only when TLS is enabled), and a Keycloak realm.
On a Nebari cluster (nebari-operator installed):
helm install nebari-apps charts/nebari-apps \
--namespace nebari-apps --create-namespace \
--set clusterDomain=<your-cluster-domain> \
--set keycloak.url=https://keycloak.<your-cluster-domain>/auth
kubectl label namespace nebari-apps nebari.dev/managed=trueThe UI lands at https://apps.<your-cluster-domain> (keycloak-js SSO) with the
MCP endpoint at https://apps.<your-cluster-domain>/mcp; every launched app
gets https://<name>.apps.<your-cluster-domain> with auth enforced at the
gateway. Connect a coding agent:
claude mcp add --transport http nebari-apps https://apps.<your-cluster-domain>/mcpApps can also be applied directly:
kubectl label namespace <your-namespace> nebari.dev/managed=true
kubectl apply -n <your-namespace> -f examples/static-inline-app.yaml
kubectl get apps -n <your-namespace> -w # wait for Phase=Running, then open the URLLocally (kind + the full Nebari stack, ~5β10 min first run):
cd dev
make up # cluster + operator + api + ui + mcp + example app (plain HTTP, no auth)
open http://apps.nebari.test # the UI
open http://docs-site.apps.nebari.test # the example appNo port-forwarding or /etc/hosts edits: a local wildcard-DNS container resolves
*.nebari.test to 127.0.0.1 (one-time /etc/resolver file on macOS, offered by make up),
and the kind cluster maps host port 80 straight to the gateway, so every launched app is
reachable as soon as its route reconciles. Type the http:// scheme explicitly: browsers
auto-upgrade bare hostnames to https, which is disabled locally. See the
local development guide for the full dev loop.
The user guide lives at packs.nebari.dev/nebari-apps-pack
(an Astro Starlight site in docs/; run it locally
with cd docs && bun install && bun run dev):
- Getting started, launching apps (UI / upload / API / kubectl), the MCP server (connecting agents, device-flow auth, tool list), the scaffolding skill, and local development
- Reference: the App CRD, the REST API, and architecture & auth
Design documents stay in the repository:
docs/DESIGN.mdβ the comprehensive design document.docs/PLAN.mdβ phased implementation roadmap and risks.- software-pack-template β pack
conventions and the
NebariAppCRD reference this pack builds on.