Note: This is the research document of jupyterhub setup with helm.
purpose:
This
project provides a complete setup of JupyterHub on Kubernetes using
Helm, tailored for multi-user environments with resource customization
and persistent storage. It includes user management with support for
local or external authentication and a dynamic spawner form integrated
with KubeSpawner. The spawner form enables configuration of Docker
image, GPU device(s), CPU and RAM allocation, and Time-To-Live (TTL) for
automatic notebook cleanup. However, resource selection is restricted
to admins only—regular users cannot choose or modify resource
configurations themselves. Each user's working directory is persistently
stored using PVCs (Persistent Volume Claims), ensuring data retention
across sessions. This setup is ideal for shared GPU servers or research
environments where users need isolated, configurable Jupyter notebook
instances on-demand under admin-controlled resource policies.
Project workflow
Helm installation
Jupyterhub installation with helm chart.
Assign GPU to users.
Image selection for users at the time of profile creation.
This will create namespace: jupyter
check this with,
kubectl get ns
Switch to default jupyter namespace so you dont have to write namespace every time:
kubectl config set-context --current --namespace=jupyter
Revert back to default namespace:
kubectl config set-context --current --namespace=default
Below are some post installation checks:
Verify that created Pods enter a Running state:
kubectl --namespace=jupyter get pod
If a pod is stuck with a Pending or ContainerCreating status, diagnose with:
`kubectl --namespace=jupyter describe pod <name of pod>`
If a pod keeps restarting, diagnose with:
`kubectl --namespace=jupyter logs --previous <name of pod>`
Verify an external IP is provided for the k8s Service proxy-public.
kubectl --namespace=jupyter get service proxy-public
If the external ip remains , diagnose with:
kubectl --namespace=jupyter describe service proxy-public
Verify web based access:
You have not configured a k8s Ingress resource so you need to access the k8s
Service proxy-public directly.
If your computer is outside the k8s cluster, you can port-forward traffic to
the k8s Service proxy-public with kubectl to access it from your
computer.
When trying to create user for the first time then it will go to pending state.
if face error,
Type Reason Age From Message
Warning FailedScheduling 2m31s jhub-user-scheduler 0/1
nodes are available: pod has unbound immediate PersistentVolumeClaims.
preemption: 0/1 nodes are available: 1 Preemption is not helpful for
scheduling.
Then the reason behind this is Dynamic storage volume is not found.
Solution:
Create Dynamic PVC:
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
Option 1: (not recommended )
First do add user from admin panel.
Then do signup with that user and change password.
Option 2:
1.Do signup from the login page. And the request of authorization is go to administrator.
Admin can authorize user via - http://127.0.0.1:30235/hub/authorize
- display_name: "GPU 6"description: "Access to GPU ID 6"slug: "gpu6"kubespawner_override:
image: quay.io/jupyter/r-notebook:2025-07-28# image: quay.io/jupyterhub/k8s-singleuser-sample:4.2.0extra_resource_limits:
nvidia.com/gpu: "6"environment:
NVIDIA_VISIBLE_DEVICES: "6"
Complete Project with resource selection:
This
project provides a complete setup of JupyterHub on Kubernetes using
Helm, tailored for multi-user environments with resource customization
and persistent storage. It includes user management with support for
local or external authentication and a dynamic spawner form integrated
with KubeSpawner. The spawner form enables configuration of Docker
image, GPU device(s), CPU and RAM allocation, and Time-To-Live (TTL) for
automatic notebook cleanup. However, resource selection is restricted
to admins only—regular users cannot choose or modify resource
configurations themselves. Each user's working directory is persistently
stored using PVCs (Persistent Volume Claims), ensuring data retention
across sessions. This setup is ideal for shared GPU servers or research
environments where users need isolated, configurable Jupyter notebook
instances on-demand under admin-controlled resource policies.
If you not specify the image it will pull the default image alpine.
Inside the container the folders and file will be created.
Container will be destroyed after one job is completed. That's why we have to use artifacts. So the build folder will be uploaded in artifacts of gitlab.
Then you use in another job test. You can check the logs and you will find the log “ Downloading artifacts”.
If branch is protected then only all the variables will be supported in gitlab CI.
Gitlab runner will use docker image to create build. Gitlab requires some workspace where it can execute build steps and from where it can deploy the application.
Multiple jobs are there in gitlab-ci.yml
Once one job is completed. The pulled image will be cleared.
Build stage example with docker push.
Now if you want to use variables in .gitlab-ci.yml then,
Project -> Settings -> CICD -> Variables
Sample build code for using variables inside gitlab ci and push image on docker registry