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 |
Without the operator the chart still installs — dummy authenticator, no routing, no shared Keycloak. That is the local-development path, not a deployment mode.
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.