Skip to content

NebariApp CRD Reference

Updated 4 min read

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.

ForGo to
Every field, type, and defaultNebariApp API reference, generated from the Go types
How the operator reconciles a NebariAppoperator reconciler docs
Step-by-step onboarding with the nebari-app chartOnboarding 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.groups is 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 as status.serviceDiscovery.requiredGroups for 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 its groups claim (see Authentication Flow).
  • auth.clientSecretRef is ignored in v0.1.1. The operator always reads and writes the Secret named <nebariapp-name>-oidc-client, with keys client-id, client-secret, and issuer-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’s client-id. status.clientSecretRef is not written either (nebari-operator#193).
  • Ready=True does not mean the app is reachable. Ready reflects the core checks (namespace label, Service, validation) and hard reconcile failures. It does not wait for RoutingReady, TLSReady, or AuthReady: a NebariApp with TLSReady=False still reports Ready=True. Check the conditions you depend on (nebari-operator#195).
  • landingPage.displayName is not validated. Set it whenever landingPage.enabled is true. The operator does not reject a missing value (nebari-operator#196).
  • NIC v0.14.0 deploys operator v0.1.0-alpha.20, not v0.1.1. landingPage.iconLight and landingPage.iconDark were added after alpha.20 and are not available there.

Worked examples. Each examples/ directory onboards the same app a different way:

ExampleShows
examples/vanilla-yamlThe plainest possible NebariApp, no templating
examples/basic-nginxA minimal Helm chart that renders one
examples/kustomize-nginxA Kustomize base with dev and production overlays
examples/wrap-existing-chartWrapping an upstream chart you do not control
examples/auth-fastapiAn 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.

The namespace containing the NebariApp must be labeled for the operator to process it:

Terminal window
kubectl label namespace my-pack nebari.dev/managed=true

Without 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.

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 get on 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.

The NebariApp resource can be included in your pack using any deployment method.

The NebariApp is just another manifest file alongside your Deployment and Service:

nebariapp.yaml
apiVersion: reconcilers.nebari.dev/v1
kind: NebariApp
metadata:
name: my-pack
spec:
hostname: my-pack.nebari.example.com
service:
name: my-pack
port: 80
routing:
routes:
- pathPrefix: /
tls:
enabled: true

When deploying standalone (without Nebari), skip this file in your kubectl apply.

Include the NebariApp in your base kustomization.yaml and use overlays to patch environment-specific values like hostname and auth:

overlays/production/nebariapp-patch.yaml
apiVersion: reconcilers.nebari.dev/v1
kind: NebariApp
metadata:
name: my-pack
spec:
hostname: my-pack.nebari.example.com
auth:
enabled: true

A 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: public

Render 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.