Admin setup
This is the administrator’s entry point: what the chart needs from the cluster, what it derives on its own, and where each knob lives. For a five-minute install, start with Quick Start instead.
What the cluster must provide
Section titled “What the cluster must provide”| Requirement | Why | Optional? |
|---|---|---|
| nebari-operator | Reconciles the NebariApp into routing, TLS, and a Keycloak OIDC client | Yes — set nebariapp.enabled: false |
| Envoy Gateway | The NebariApp’s HTTPRoute attaches to it | With the operator |
| cert-manager | Issues the TLS certificate for the hub hostname | With the operator |
Keycloak (bitnami/keycloakx) | Identity provider; the operator provisions the hub client in it | With the operator |
| A ReadWriteMany StorageClass | Per-group shared directories | Yes — see Shared Storage |
| A default (RWO) StorageClass | Per-user home PVCs and Nebi workspace PVCs | No |
Namespace label nebari.dev/managed=true | The operator ignores NebariApps in unlabeled namespaces | No, when the operator is used |
| Helm 3.17 or newer | The nebari-app library subchart uses toYamlPretty, added in Helm 3.17.0. Older Helm cannot parse the chart at all. See Helm version | No |
Without the operator the chart still installs — dummy authenticator, no routing, no shared Keycloak. That is the local-development path, not a deployment mode.
Helm version
Section titled “Helm version”The chart depends on the nebari-app library chart, whose templates call
toYamlPretty. That function was added in Helm 3.17.0 (released 2025-01-15). On
any older Helm the chart fails while templates are being parsed, before values
are evaluated:
Error: parse error at (nebari-data-science-pack/charts/nebari-app/templates/_nebari-app.tpl:21): function "toYamlPretty" not definedBecause this is a parse-time failure it cannot be worked around with values.
Setting nebariapp.enabled: false does not avoid it, and neither does
--show-only on an unrelated template: helm lint, helm template and
helm install all fail identically.
The library chart is bundled into the released .tgz, so the requirement travels
with the published chart. You do not need registry access to quay.io at install
time, but you do need a new enough Helm.
On an ArgoCD-managed cluster the Helm that matters is the one bundled in the ArgoCD repo-server image, not the one on your workstation:
| ArgoCD | Bundled Helm | Renders this chart? |
|---|---|---|
| 2.13.x | 3.15.4 | No |
| 2.14.x | 3.16.3 | No |
| 3.0.0 and newer | 3.17.1 and newer | Yes |
On a NIC-managed cluster this is already satisfied and needs no action. NIC
installs the argo-cd chart pinned in nebari-infrastructure-core
(pkg/argocd/config.go), currently 9.7.1, which installs ArgoCD v3.4.4 with
Helm 3.19.4. That pin is also held at v3.4 or later for an unrelated reason (the
helm.valueFiles glob expansion the overlay seam depends on), so it will not
drift below the floor this chart needs.
The table matters when you run ArgoCD yourself, or install the chart with your
own helm binary. If ArgoCD reports function "toYamlPretty" not defined on
sync, upgrade ArgoCD to 3.0 or newer rather than changing chart values.
One required field
Section titled “One required field”The chart is built around a single input. Everything else is derived by subdomain convention and can be overridden individually:
keycloak: hostname: keycloak.example.comFrom that one value:
| Derived | Rule | Example |
|---|---|---|
| Base domain | keycloak.hostname minus its first label | example.com |
| Hub hostname | <subdomains.hub>.<base> | hub.example.com |
| Nebi external URL | https://<subdomains.nebi>.<base> | https://nebi.example.com |
| Keycloak token URL | https://<keycloak.hostname>/realms/<realm>/…/token | — |
| Hub OIDC client ID | jupyterhub-<release>-<chart> | jupyterhub-data-science-pack-nebari-data-science-pack |
| Nebi OIDC client ID | nebi-<nebi.releaseName>-nebari-nebi-pack | nebi-nebi-pack-nebari-nebi-pack |
Install
Section titled “Install”helm repo add nebari https://raw.githubusercontent.com/nebari-dev/helm-repository/gh-pages/helm repo update
kubectl create namespace data-sciencekubectl label namespace data-science nebari.dev/managed=true
helm install data-science-pack nebari/nebari-data-science-pack \ --namespace data-science \ --set keycloak.hostname=keycloak.example.comAlso available as an OCI artifact:
helm install data-science-pack \ oci://quay.io/nebari/charts/nebari-data-science-pack --version <version>Where each knob lives
Section titled “Where each knob lives”Configuration splits across three layers. Knowing which one you are in explains most “my value did nothing” reports.
| Layer | Path | What it is |
|---|---|---|
| Chart values | keycloak, subdomains, nebariapp, singleuser, singleuserCuller, sharedStorage, nebi, rbac | This chart’s own values |
| Chart-derived hub config | jupyterhub.custom.* | Read by the Python files in jupyterhub_config.d/ via get_chart_config() |
| Upstream passthrough | everything else under jupyterhub.* | Handed verbatim to Zero to JupyterHub |
Field-by-field detail for all three is in the Values reference.
What the chart creates
Section titled “What the chart creates”Beyond the z2jh subchart’s own objects:
| Object | Template | Purpose |
|---|---|---|
NebariApp | nebariapp.yaml | Routing, TLS, Keycloak client, landing-page card |
| Hub config ConfigMap | hub-config.yaml | The four jupyterhub_config.d/ Python files |
| Singleuser config ConfigMap | singleuser-config.yaml | Per-pod config mounted by the spawner |
| Nebi config ConfigMap | singleuser-nebi-config.yaml | Admin-provisioned Nebi registries — only when customized |
| Shared PVC (+ NFS server) | shared-pvc.yaml, nfs-server.yaml | Per-group shared storage |
| NFS client installer | nfs-client-installer.yaml | DaemonSet installing nfs-common, opt-in |
| Keycloak RBAC bootstrap Job | keycloak-rbac-bootstrap-job.yaml | post-install/upgrade hook; groups mapper + shared-mount role |
| Two NetworkPolicies | singleuser-gateway-egress.yaml, hub-nebi-networkpolicy.yaml | Egress the subchart’s policy does not cover |
The Keycloak bootstrap job
Section titled “The Keycloak bootstrap job”rbac.bootstrap.enabled defaults to true. It runs as a post-install/post-upgrade hook in
the keycloak namespace, authenticates with the admin credentials Secret, and is
idempotent — it skips cleanly when kcAdminCredentialSecret is unset, so the chart still
installs on clusters that have not surfaced one.
It does four things:
- Adds the
oidc-group-membership-mapperto thegroupsclient scope. Without it thegroupsclaim is empty, and both shared storage andaccess: yamlprofile gating silently fall back to “no groups”. - Creates the
allow-group-directory-creation-roleclient role on the hub client. - Enables
serviceAccountsEnabledon the hub client and bindsrealm-management.{view-clients,view-groups,view-realm}to its service account. - Assigns the shared-mount role to the groups listed in
rbac.bootstrap.sharedMountGroups.
Set enabled: false for BYO-Keycloak or local development. Override namespace,
kcAdminCredentialSecret, and kcHost for non-bitnami Keycloak layouts.
Integrations
Section titled “Integrations”- Nebi — the environment manager. Ships into user pods via an init container and needs a matching OIDC client for token exchange.
- MLflow — experiment tracking. Two values, one of which is a NetworkPolicy that has to name the pod port rather than the service port.
- NebariApp — the CRD fields this chart sets and why.
User-facing configuration
Section titled “User-facing configuration”- Server profiles — sizes, images, and per-group gating.
- Shared Storage — per-group directories and RWX requirements.
Verify a deployment
Section titled “Verify a deployment”kubectl -n data-science get podskubectl -n data-science get nebariapp,httproute,certificate
# The operator only acts on labeled namespaceskubectl get namespace data-science -o jsonpath='{.metadata.labels}'
# The hub reads its OAuth client from this Secret; absent means dummy authkubectl -n data-science get secret data-science-pack-nebari-data-science-pack-oidc-client
# Did the Keycloak bootstrap hook succeed?kubectl -n keycloak get jobs -l app.kubernetes.io/instance=data-science-packThen log in through Keycloak and check that the profile selector appears with the sizes you
expect. An empty or unexpectedly short list usually means the groups claim is missing —
see Server profiles.