NebariApp CRD Reference
Where to find the NebariApp field reference, and how to declare a NebariApp with plain YAML, Kustomize, or Helm.
API Version: reconcilers.nebari.dev/v1
Kind: NebariApp
Operator version this page tracks: v0.1.1
This page used to hold a hand-maintained copy of the NebariApp field reference. It went stale: it tracked an operator release from several versions back, and nothing detected the drift. Two copies of it lived in this repository, and the operator generates a third from the Go types. The field reference now has one home, next to the code that enforces it.
Where to look instead
Section titled “Where to look instead”| For | Go to |
|---|---|
| Every field, type, and default | NebariApp API reference, generated from the Go types |
| How the operator reconciles a NebariApp | operator reconciler docs |
Step-by-step onboarding with the nebari-app chart | Onboarding an app |
The API reference is generated by make docs in the operator repository, so it cannot fall
behind the types the way this page did.
What the generated reference does not tell you
Section titled “What the generated reference does not tell you”auth.groupsis not enforced in v0.1.1. The generated reference says only members of the listed groups are authorized. They are not. The operator creates the groups in Keycloak and publishes them asstatus.serviceDiscovery.requiredGroupsfor the landing page, but the SecurityPolicy it generates contains no authorization rule, so any user who can log in to the realm reaches the app (nebari-operator#153). To restrict access by group today, verify the token in your app and check itsgroupsclaim (see Authentication Flow).auth.clientSecretRefis ignored in v0.1.1. The operator always reads and writes the Secret named<nebariapp-name>-oidc-client, with keysclient-id,client-secret, andissuer-url. If you manage credentials yourself (provisionClient: false), create the Secret under that name, and name the client in your identity provider<namespace>-<nebariapp-name>: the gateway’s SecurityPolicy always uses that client ID, not the Secret’sclient-id.status.clientSecretRefis not written either (nebari-operator#193).Ready=Truedoes not mean the app is reachable.Readyreflects the core checks (namespace label, Service, validation) and hard reconcile failures. It does not wait forRoutingReady,TLSReady, orAuthReady: a NebariApp withTLSReady=Falsestill reportsReady=True. Check the conditions you depend on (nebari-operator#195).landingPage.displayNameis not validated. Set it wheneverlandingPage.enabledistrue. The operator does not reject a missing value (nebari-operator#196).- NIC v0.14.0 deploys operator
v0.1.0-alpha.20, notv0.1.1.landingPage.iconLightandlandingPage.iconDarkwere added after alpha.20 and are not available there.
What this repository gives you
Section titled “What this repository gives you”Worked examples. Each examples/ directory onboards the same app a different way:
| Example | Shows |
|---|---|
examples/vanilla-yaml | The plainest possible NebariApp, no templating |
examples/basic-nginx | A minimal Helm chart that renders one |
examples/kustomize-nginx | A Kustomize base with dev and production overlays |
examples/wrap-existing-chart | Wrapping an upstream chart you do not control |
examples/auth-fastapi | An app verifying the identity token the platform provides |
For the authentication sequence end to end, see Authentication flow. Requirements for promoting a pack are in the release readiness checklist.
Namespace Opt-In
Section titled “Namespace Opt-In”The namespace containing the NebariApp must be labeled for the operator to process it:
kubectl label namespace my-pack nebari.dev/managed=trueWithout this label, the NebariApp will show NamespaceNotOptedIn and no resources
will be created.
When ArgoCD creates the namespace (CreateNamespace=true), have it apply the label too,
so nobody has to run kubectl by hand:
spec: syncPolicy: syncOptions: - CreateNamespace=true managedNamespaceMetadata: labels: nebari.dev/managed: "true"ArgoCD only applies managedNamespaceMetadata to a namespace that the same Application
creates. If the namespace already exists, label it yourself.
Who can read the OIDC Secret
Section titled “Who can read the OIDC Secret”When provisionClient is true, the operator writes <name>-oidc-client and creates a Role
(<name>-oidc-secret-reader) that lets spec.serviceAccountName (default: the NebariApp name) get that Secret
through the Kubernetes API. That Role adds access for one ServiceAccount. It does not remove access from
anyone else. Kubernetes RBAC is additive, so these can also read the Secret:
- anyone who already has
geton Secrets in the namespace, such as namespace admins - any pod in the namespace, under any ServiceAccount, that mounts the Secret as an env var or volume (the kubelet fetches it, not the pod’s ServiceAccount), and therefore anyone who can create pods there
- the operator, which has cluster-wide Secret access, and Envoy Gateway, which reads the client secret to run the OIDC filter
Treat the namespace as the security boundary for these credentials.
Deployment Patterns
Section titled “Deployment Patterns”The NebariApp resource can be included in your pack using any deployment method.
Plain YAML
Section titled “Plain YAML”The NebariApp is just another manifest file alongside your Deployment and Service:
apiVersion: reconcilers.nebari.dev/v1kind: NebariAppmetadata: name: my-packspec: hostname: my-pack.nebari.example.com service: name: my-pack port: 80 routing: routes: - pathPrefix: / tls: enabled: trueWhen deploying standalone (without Nebari), skip this file in your kubectl apply.
Kustomize
Section titled “Kustomize”Include the NebariApp in your base kustomization.yaml and use overlays to patch
environment-specific values like hostname and auth:
apiVersion: reconcilers.nebari.dev/v1kind: NebariAppmetadata: name: my-packspec: hostname: my-pack.nebari.example.com auth: enabled: trueA strategic-merge patch only changes the fields it lists, so routing from the base is kept.
Charts render the NebariApp through the shared nebari-app.nebariApp template
provided by the nebari-app chart, instead of hand-writing the manifest.
Add the dependency in Chart.yaml, then run helm dependency build to fetch it:
dependencies: - name: nebari-app repository: oci://quay.io/nebari/charts version: ">=0.1.1"Set any NebariApp spec field under nebariapp: in values.yaml. Everything
under nebariapp: (except enabled) is passed through to the NebariApp spec,
so every field in the NebariApp API reference can be set here. Each {{ ... }} value is rendered
with the chart context and must produce valid JSON, so a template that renders a string
ends with | toJson. Numbers such as the port below don’t need it:
nebariapp: enabled: false hostname: '{{ fail "nebariapp.hostname is required when nebariapp.enabled is true" }}' service: name: '{{ include "my-pack.fullname" . | toJson }}' port: '{{ .Values.service.port }}' routing: routes: - pathPrefix: / pathType: PathPrefix tls: enabled: true auth: enabled: false provider: keycloak provisionClient: true scopes: - openid - profile - email gateway: publicRender the NebariApp in templates/nebariapp.yaml. The if makes it optional,
so the chart works both standalone and on Nebari:
{{- if .Values.nebariapp.enabled }}{{- include "nebari-app.nebariApp" (dict "metadata" (dict "name" (include "my-pack.fullname" .) "namespace" .Release.Namespace "labels" (include "my-pack.labels" . | fromYaml) ) "spec" (omit .Values.nebariapp "enabled") "tplCtx" .) -}}{{- end }}"tplCtx" . passes the chart context into the template, so the {{ ... }}
values in values.yaml are rendered with access to .Values, .Release, and
the chart’s named templates.