JupyterLab Configuration
What is JupyterLab?
JupyterLab is the interactive notebook interface for TDP Kubernetes. It is the environment where data analysts and engineers write and run code, organize files, and explore data directly in the browser.
In TDP Kubernetes, JupyterLab is served by JupyterHub, which handles authentication and the lifecycle of each user's environment in the cluster.
See JupyterLab — Concepts for a complete overview of the tool, its architecture, and operation.
Helm values structure
The tdp-jupyter chart organizes its configuration into four main blocks:
| Block | Purpose |
|---|---|
tdp-jupyter: | JupyterHub configuration: Hub, Proxy, single-user pods, authentication, storage, and security |
tdpSparkIntegration: | Spark integration: local or external cluster mode, ConfigMap, and optional subchart |
TDP-Settings: | TDP platform settings: external database and exposure control (Ingress/Gateway API) |
openshift: | Force OpenShift mode when auto-detection is not possible |
Use helm show values oci://registry.tecnisys.com.br/tdp/charts/tdp-jupyter > values-default.yaml to inspect all available parameters.
Overview
| Property | Value |
|---|---|
| Chart | tdp-jupyter |
| JupyterHub version | 5.3.0 |
| Chart version | 3.0.1 |
| User interface | JupyterLab served by JupyterHub |
| Execution model | One single-user pod per authenticated user |
| Metadata database | SQLite by default, with an external PostgreSQL option |
| HTTP exposure | JupyterHub proxy through Service, Ingress, or Gateway API |
Main components
| Component | Description |
|---|---|
| Hub | Central JupyterHub service — handles authentication and environment lifecycle |
| Proxy | Reverse proxy that routes each user to their environment |
| Single-user pods | One Kubernetes pod per authenticated user |
| PVCs | Persistent storage for notebooks, when configured |
Single-user model
Each time a user logs in, the Hub creates a dedicated pod for them in Kubernetes. When the user stops the server, the pod is terminated, but storage persists if it is backed by a PVC.
Cluster sizing must account for the expected peak of concurrent users.
Default kernel (Python 3.12)
On startup, the single-user pod runs a postStart hook that makes the py312 kernel (Python 3.12, with Spark/Delta/Iceberg libraries pre-installed) the default kernel shown as "Python 3" in the notebook — the original python3 kernel is replaced by a copy of py312. The KernelSpecManager.allowed_kernelspecs setting restricts the list of kernels available to the user to this kernel.
Prerequisites
- Kubernetes 1.32+, Red Hat OpenShift 4.19+, or Rancher Manager 2.10.x+
- Helm 3.2.0+
- PV provisioner available in the cluster
- Spark cluster via
tdp-sparkor compatible installation, when using Spark integration
Related pages
- Installation
- Main parameters
- Configuration details
- Access & Security
- Troubleshooting
- Uninstallation
Installation (OCI)
Kubernetes
helm upgrade --install <RELEASE_NAME> \
oci://registry.tecnisys.com.br/tdp/charts/tdp-jupyter \
-n <NAMESPACE> --create-namespace --timeout 10m
Without a -f file, the chart defaults are used. In production environments, version a values file with resources, storage, and service type aligned to the cluster's policies.
OpenShift
The chart automatically detects OpenShift via the Kubernetes discovery API. If auto-detection cannot reach the API (restricted networks, CI pipelines), force OpenShift mode explicitly:
helm upgrade --install <RELEASE_NAME> \
oci://registry.tecnisys.com.br/tdp/charts/tdp-jupyter \
-n <NAMESPACE> --create-namespace --timeout 10m \
--set openshift.enabled=true \
--set tdp-jupyter.scheduling.userScheduler.containerSecurityContext.runAsNonRoot=true
Or use a dedicated overlay file values-openshift.yaml:
openshift:
enabled: true
tdp-jupyter:
scheduling:
userScheduler:
containerSecurityContext:
runAsNonRoot: true
When OpenShift mode is active, the chart automatically applies:
- Overlay volumes for
/etc/passwdand/etc/groupon single-user pods openshift-passwd-bootstrapinit container to map UID/GID- Post-install hook that nullifies
runAsUser/runAsGroupon the Hub and forcesrunAsNonRoot: true
Main parameters
| Parameter | Description | Default |
|---|---|---|
tdp-jupyter.enabled | Enable JupyterHub deployment | true |
tdp-jupyter.hub.resources.requests.cpu | Hub minimum CPU | 500m |
tdp-jupyter.hub.resources.requests.memory | Hub minimum memory | 1Gi |
tdp-jupyter.hub.resources.limits.cpu | Hub maximum CPU | 1000m |
tdp-jupyter.hub.resources.limits.memory | Hub maximum memory | 2Gi |
tdp-jupyter.hub.config.JupyterHub.authenticator_class | Authentication class | dummy |
tdp-jupyter.hub.config.DummyAuthenticator.username | Default username | admin |
tdp-jupyter.hub.config.DummyAuthenticator.password | Default password (change in production) | tdp-password |
tdp-jupyter.singleuser.cpu.limit | Maximum CPU per notebook | 4 |
tdp-jupyter.singleuser.cpu.guarantee | Guaranteed CPU per notebook | 2 |
tdp-jupyter.singleuser.memory.limit | Maximum memory per notebook | 8G |
tdp-jupyter.singleuser.memory.guarantee | Guaranteed memory per notebook | 4G |
tdp-jupyter.singleuser.storage.capacity | PVC size per user | 5Gi |
tdp-jupyter.singleuser.storage.dynamic.storageClass | User PVC storage class | null (cluster default) |
tdp-jupyter.proxy.service.type | Proxy service type | NodePort |
tdp-jupyter.proxy.service.nodePorts.http | HTTP NodePort | 30080 |
tdp-jupyter.proxy.service.nodePorts.https | HTTPS NodePort | 30443 |
TDP-Settings.externalDatabase.enabled | Enable external database | false |
TDP-Settings.externalDatabase.recreate | Recreate database on upgrade | false |
TDP-Settings.gateway.ingress.enabled | Enable Ingress | false |
TDP-Settings.gateway.gatewayApi.enabled | Enable Gateway API | false |
tdpSparkIntegration.enabled | Enable Spark integration | false |
tdpSparkIntegration.deploySparkCluster | Install tdp-spark subchart | false |
openshift.enabled | Force OpenShift mode | false |
External database (PostgreSQL)
By default, the Hub uses SQLite with PVC storage. For production environments with multiple replicas, or when Hub persistence must be managed outside the Helm release lifecycle, use external PostgreSQL.
TDP-Settings:
externalDatabase:
enabled: true
recreate: false
externalSecret:
releaseName: "<POSTGRESQL_RELEASE>"
tdp-jupyter:
hub:
db:
type: postgres
url: "postgresql://<DATABASE_USER>@<POSTGRESQL_SERVICE>.<NAMESPACE>.svc.cluster.local:5432/<DATABASE_NAME>"
password: null
upgrade: false
extraEnv:
PGPASSWORD:
valueFrom:
secretKeyRef:
name: "<SECRET_NAME>"
key: password
Storage
Each authenticated user can receive a persistent volume for their working directory. The StorageClass defines how the disk is provisioned.
singleuser:
storage:
dynamic:
storageClass: <STORAGE_CLASS>
capacity: 5Gi
Choose a storageClass compatible with your cluster's provisioner and set the capacity according to the expected local data usage in notebooks.
Host aliases
When notebook pods need to resolve hostnames that are not in the cluster DNS — for example, to access an internal S3 storage endpoint — use singleuser.extraPodConfig.hostAliases:
tdp-jupyter:
singleuser:
extraPodConfig:
hostAliases:
- ip: "<CONTROLLER_IP>"
hostnames:
- "<COMPONENT_HOSTNAME>"
This parameter adds entries to the /etc/hosts file of each notebook pod, allowing Spark and Python libraries to resolve the hostname to the given IP.
Spark integration
JupyterLab can run without Spark integration (local local[*] mode) or connect to an external Spark cluster. Full details, operating modes, and troubleshooting are in Integrations — JupyterLab.
Service exposure
The way you expose the Proxy defines how users reach the interface.
NodePort (default)
tdp-jupyter:
proxy:
service:
type: NodePort
nodePorts:
http: 30080
https: 30443
kubectl get nodes -o wide
# Access via http://<NODE_IP>:30080
Port-forward
kubectl -n <NAMESPACE> port-forward service/<RELEASE_NAME>-proxy 8080:80
# Access via http://localhost:8080
LoadBalancer
tdp-jupyter:
proxy:
service:
type: LoadBalancer
kubectl get svc <RELEASE_NAME>-proxy -n <NAMESPACE>
Use the external IP provided by your LoadBalancer provisioner to access the interface.
ClusterIP
tdp-jupyter:
proxy:
service:
type: ClusterIP
Restricts access to the cluster interior (internal only).
External exposure
To expose JupyterHub through Ingress or Gateway API with a stable hostname, see the external exposure page.
If the environment requires direct Service exposure, tdp-jupyter.proxy.service.type can be set to LoadBalancer in the values file. This is a Kubernetes Service networking decision, not an HTTP/HTTPS exposure mode equivalent to Ingress or Gateway API.
Authentication
Detailed authentication, LDAP configuration, and access best practices are in Security — JupyterLab.
Troubleshooting
# Pod status
kubectl -n <NAMESPACE> get pods -l app.kubernetes.io/instance=<RELEASE_NAME>
# Hub logs
kubectl -n <NAMESPACE> logs -l app.kubernetes.io/component=hub
# Proxy logs
kubectl -n <NAMESPACE> logs -l app.kubernetes.io/component=proxy
# Pod events
kubectl -n <NAMESPACE> describe pod <POD_NAME>
# PVCs
kubectl -n <NAMESPACE> get pvc -l app.kubernetes.io/instance=<RELEASE_NAME>
# External database (if configured)
kubectl -n <NAMESPACE> exec -it <POD_NAME> -- \
psql "postgresql://<DATABASE_USER>@<POSTGRESQL_SERVICE>.<NAMESPACE>.svc.cluster.local:5432/<DATABASE_NAME>"
| Symptom | Likely cause | Suggested action |
|---|---|---|
Pods in Pending | StorageClass missing or out of capacity | Check PV provisioner and quotas |
| Spark connection fails | NetworkPolicy or Spark service unreachable | Verify tdpSparkIntegration.enabled and connectivity |
| External database connection fails | PostgreSQL unreachable or wrong credentials | Check URL, Secret, and PostgreSQL accessibility |
| Pods OOMKilled | CPU/memory limits too low | Adjust hub.resources and singleuser.cpu/singleuser.memory |
Uninstallation
helm uninstall <RELEASE_NAME> -n <NAMESPACE>
kubectl delete configmap <RELEASE_NAME>-spark-integration -n <NAMESPACE>
User PVCs are not removed automatically. To remove them:
kubectl delete pvc -l app.kubernetes.io/instance=<RELEASE_NAME> -n <NAMESPACE>
This operation is destructive and deletes all user notebook data.