Notebooks Stack (JupyterHub)
Reference for SeaGit’s Notebooks template: a multi-user JupyterHub in a Kubernetes cluster in your own AWS account. Each developer signs in and gets a private JupyterLab with a persistent home and keyless access to an S3 bucket.
What the Notebooks Stack is
The Notebooks template deploys JupyterHub 5 into a cluster you choose, from a single form. A developer opens the hub’s address, signs in, picks a server size and an image, and works in JupyterLab in the browser or from VS Code. Their home folder is kept between sessions. A server that sits idle stops itself, so you pay for compute only while someone is working. Notebooks read and write one S3 bucket through an IAM role, with no access keys to hand out.
It is built for a team of roughly 10 to 50 people sharing one hub. With a dedicated entrance the hub was ready about seven minutes after Deploy in a live run, most of it the load balancer warming up; the shared entrance skips that wait. A developer’s first server took about two and a half minutes to start.
The template is available on paid plans. It needs a native (sgt-k8s) cluster with two add-ons: EBS CSI (home volumes) and cert-manager (the HTTPS certificate), plus Load Balancer when you choose a dedicated entrance, and an environment with a domain. The wizard tells you which add-on is missing on the cluster you pick.
What gets deployed
One application with one component, hub: a release of the sg-notebooks Helm chart, which wraps the upstream Zero to JupyterHub chart, pinned to one cluster. Nothing cluster-wide is installed.
| Part | What it is |
|---|---|
| Hub | One JupyterHub pod. It signs people in, starts and stops their servers, and keeps its own small database on an EBS volume. |
| Proxy and entrance | By default the hub is served through the cluster’s shared entrance (its ingress: nginx, or the Gateway API), like your other apps: no load balancer of its own. Choose a dedicated entrance to give the hub its own internet-facing Network Load Balancer instead. Either way only the Allowed IPs can connect. |
| Name and certificate | <dep>.<environment domain>, with a Let’s Encrypt certificate from cert-manager. A renewed certificate is loaded automatically. |
| A server per developer | A private JupyterLab pod, started on sign-in and stopped when idle. Each has its own home volume. |
| Data bucket | An S3 bucket (or a folder of yours) in the cluster’s AWS account. Servers reach it through an IAM role; there are no access keys. |
Deploy it
- Open Applications → Templates and choose Notebooks. Clicking the card opens its details; Deploy starts the wizard.
- First step: name, environment, cluster and space, then the sign-in method and whether the data bucket is new or one you already own.
- Second step: who may sign in, the sizes and images developers can pick, home storage, the idle timeout, the Allowed IPs and the bucket details. With an existing bucket, its name or ARN is required.
- Review and deploy. When the stack is ready, the Stack tab shows the hub address, the sign-in details and code to copy.
Sign-in
| Method | What you provide | Good for |
|---|---|---|
| Shared passwords | One admin password and one team password, generated per stack, plus a list of usernames that may sign in. | Trials and small teams. Nothing to set up outside SeaGit. |
| GitHub | A GitHub OAuth app. Access by organization (acme) or team (acme:data-science). | Teams that already manage membership in GitHub. |
| Single sign-on (OpenID Connect) | Okta, Entra ID, Google, Keycloak, Cognito or any OIDC provider, by issuer URL. Optional allowed groups and admin groups. | Companies with an identity provider. Offboarding happens there. |
| New sign-in for this stack | A sign-in service the stack runs for itself: GitHub (organization, optional team), Google (allowed email domains) or any OIDC provider. Optional allowed groups and admin groups. | One hub with sign-in in front of every page, including the hub’s own. |
| An existing Auth Proxy stack | An Auth Proxy stack in any of your environments whose cookie domain covers the hub’s address. Optional allowed groups and admin groups. | One sign-in for MLflow, Notebooks and your other tools. |
With the first three, sign-in is handled by the hub itself. With the last two, an Auth Proxy (oauth2-proxy) signs people in and the hub accepts its session.
Shared passwords
The Stack tab shows the admin and team passwords masked. Each reveal needs update access to the app and is written to the audit log. Admins sign in with the admin password; the usernames you listed sign in with the team password. Anyone else is refused, with either password.
GitHub and single sign-on
Create the OAuth app or client with your provider, paste its client ID and secret into the form, and deploy. The secret is stored encrypted and is not shown again. Register the OAuth callback URL with the provider. Set Hub address to the application name and the hub lives at https://<application name>.<domain>: the wizard shows the callback URL under that choice, so you can register it before you deploy. This needs your own domain on the environment (not Seagit’s shared domain), and an address already used on that domain is refused. With a generated name, copy the callback URL from the Stack tab after the first deploy. For single sign-on, the endpoints are read from the issuer’s discovery document. To restrict access by group, set Allowed groups; Okta sends the groups claim only when groups is added under Extra scopes.
Through an Auth Proxy
New sign-in for this stack runs a sign-in service beside the hub, on the hub’s own address. Create the OAuth app or client with your provider, choose the provider, and paste its client ID and secret. Register the Redirect URL to register (https://<hub address>/services/auth/callback) with the provider: shown in the wizard when Hub address is the application name, otherwise on the Stack tab after the first deploy. GitHub needs an organization and can be limited to one team; Google needs the email domains allowed to sign in.
An existing Auth Proxy stack reuses the sign-in you already run for MLflow and other tools: people who are signed in there are signed in to the hub, with nothing to register. The Auth Proxy can be in any of your environments, on any cluster, as long as its cookie domain covers the hub’s address: an Auth Proxy in an environment on ns.example.com serves hubs on ns.example.com and its sub-domains. On the same cluster the hub checks sessions at the Auth Proxy’s in-cluster address, otherwise at its public one. If the picker is empty, the note under it names your Auth Proxy stacks and the domains they are on. Allowed groups narrow who may use this hub.
In both cases the hub checks the session with the sign-in service over the cluster network. It does not trust identity headers, so a notebook cannot pretend to be someone else. Usernames are email addresses.
Log out ends the sign-in service’s session as well as the hub’s, then shows a signed-out page. With an existing Auth Proxy stack this signs you out of every app behind it, MLflow included, because they share one session. Your notebook server keeps running until it is idle. You stay signed in to your provider (GitHub, Google or your identity provider), so signing in again does not ask for a password.
Admins
Admins see every server in the hub’s admin page and can stop them. With GitHub they are GitHub usernames; with single sign-on, the username claim (usually the email) or members of the Admin groups. Through an Auth Proxy, email addresses or members of the Admin groups.
Servers
Sizes
Tick each size developers may pick, in any combination. The limit is what a server may use; the guaranteed memory is what must be free on one node for the server to start.
| Size | Limit | Guaranteed memory |
|---|---|---|
| X-Small | 0.5 vCPU, 2 GB | 512 MB |
| Small | 1 vCPU, 4 GB | 1 GB |
| Medium | 2 vCPU, 8 GB | 2 GB |
| Large | 4 vCPU, 16 GB | 4 GB |
| X-Large | 8 vCPU, 32 GB | 8 GB |
Check the guaranteed memory against your nodes. On a pool of 2 GB nodes a Small server may not fit beside the system pods; offer X-Small there or add a larger pool.
Images
| Image | What it is |
|---|---|
| Python data science | quay.io/jupyter/scipy-notebook: pandas, NumPy, SciPy, scikit-learn, Matplotlib. |
| PyTorch | quay.io/jupyter/pytorch-notebook: the above plus PyTorch. |
| Python, R and Julia | quay.io/jupyter/datascience-notebook: three kernels in one image. |
| Your team image | Any image you build FROM a quay.io/jupyter image, given by full reference (for example an ECR image in the cluster’s account). |
The built-in images are pinned to a dated tag, so every developer gets the same versions. Packages installed with pip into the home folder survive a restart; packages installed system-wide do not. For a stable team environment, build an image.
Home storage
Each developer gets an EBS volume (10 GiB by default) mounted as the home folder, created on first start. It is kept when the server stops and deleted when the stack is deleted. Put data you want to outlive the stack in the bucket.
Idle servers
A server with no activity for the chosen time (30 minutes to 24 hours, 1 hour by default) is stopped. The home is kept, and the server starts again at the next sign-in. A kernel that is busy counts as activity.
Spot nodes
The hub and the servers stay off spot nodes by default. Allow spot for experiments where downtime is fine: a reclaimed node stops the hub or the servers on it. The hub database and the homes are disks in one availability zone, so they start again only once a spot node is free in that same zone, which can take a while. Homes are kept; unsaved kernel state is lost. The environment chooses the node pool; this option chooses spot or on-demand within it.
Data bucket
| Choice | What happens |
|---|---|
| Create new bucket | SeaGit creates a bucket in the cluster’s AWS account. Leave the name empty for a generated one. Encryption: S3-managed keys, a new KMS key SeaGit creates (you choose its alias), or a KMS key you already have. |
| Use a folder of an existing bucket | A bucket you already own, in the same account, by name or ARN. The bucket is required; the folder defaults to seagit-notebooks/<app name>/. Servers can read and write that folder only. Optionally a KMS key ARN; empty uses the bucket’s default encryption. |
Every server runs under one Kubernetes service account tied to an IAM role for that bucket or folder, so it reaches the bucket without keys. Each server installs s3fs and boto3 when it starts (about 15 seconds; the images do not include them). These variables are set in every server:
| Variable | Value |
|---|---|
| SEAGIT_BUCKET | The bucket name |
| SEAGIT_PREFIX | The folder inside it (empty for a new bucket) |
| SEAGIT_BUCKET_URI | s3://<bucket>/<folder>, ready to prefix a path |
| AWS_REGION | The cluster’s region |
| MLFLOW_TRACKING_URI | Only with Track experiments in MLflow: the MLflow stack’s in-cluster address |
import os
import pandas as pd
base = os.environ["SEAGIT_BUCKET_URI"]
pd.DataFrame({"x": [1, 2, 3]}).to_csv(base + "examples/hello.csv", index=False)
print(pd.read_csv(base + "examples/hello.csv"))pandas reads and writes s3:// addresses through s3fs, so code that builds paths from SEAGIT_BUCKET_URI needs no AWS-specific calls. Use boto3 when you want the S3 API itself, for example to list objects or set metadata.
Access is shared: every developer on the hub can read and write the whole bucket or folder. Use separate stacks for teams that must not see each other’s data.
Network and security
- Allowed IPs limit who can reach the sign-in page, at the shared entrance or at the dedicated load balancer. They are optional: left empty (or 0.0.0.0/0), anyone can reach the page (sign-in is still required) and the Stack tab says so. Set your office or VPN addresses. They apply with every sign-in method.
- Servers cannot reach the cloud metadata address, so a notebook cannot pick up the node’s own AWS permissions.
- Servers get no Kubernetes API token and cannot reach other buckets in the account.
- By default servers reach the internet only. Reach private addresses lets them connect to databases and services in the VPC and the cluster.
Track experiments in MLflow
Deploy an MLflow stack on the same cluster first. In the Notebooks wizard, set Track experiments in MLflow to Use an MLflow stack in this environment and pick the stack. The list shows only MLflow stacks on the cluster you chose, and one on another cluster is refused at create, because notebooks reach it at its in-cluster address. Every server then gets MLFLOW_TRACKING_URI set to MLflow’s in-cluster address, and a network rule that lets servers reach MLflow on port 5000 in its namespace, whatever Reach private addresses is set to. Servers started before the change pick it up at their next start.
The Connect panel shows an MLflow snippet:
%pip install mlflow==3.17.1
import mlflow
mlflow.set_experiment("my-experiment")
with mlflow.start_run():
mlflow.log_param("model", "baseline")
mlflow.log_metric("accuracy", 0.91)Notebooks reach MLflow inside the cluster, so they do not sign in. With per-experiment access control on the MLflow stack, runs from notebooks act as its in-cluster service identity.
Use it from VS Code
- Open the hub, sign in and start your server.
- In the hub: File → Hub Control Panel → Token → Request new API token.
- In VS Code with the Jupyter extension: Select Kernel → Existing JupyterHub Server, then enter the hub address, your username and the token.
Changing and deleting
There is no Stop action for this stack: servers already stop when idle, and the hub itself is small.
Deleting the stack stops every server and deletes the hub, the load balancer and all home volumes. The delete dialog offers to also delete a bucket SeaGit created; left unticked, the bucket and its data stay in your account. A bucket you brought is never deleted.
Limits in this version
- No GPU sizes.
- No shared team folder between homes; share through the bucket.
- The hub is internet-facing with an IP allowlist; there is no private-only option yet.
- One hub per stack, on one cluster.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| A server never starts and times out after 10 minutes | No node has room for it. A server needs its guaranteed memory free on one node (see Sizes). Add a larger node pool or offer smaller sizes (X-Small fits small nodes). |
| Deploy waits on “its TLS certificate” | cert-manager is still issuing the certificate (DNS validation takes a minute or two). If Let’s Encrypt fails an order, cert-manager 1.20 (the add-on version on Kubernetes 1.32 and later) retries after 5 minutes. |
| The sign-in page does not load | Your address is not in Allowed IPs, or (with a dedicated entrance) its new load balancer is still warming up: SeaGit reports the stack ready only after that load balancer has been healthy for three minutes. |
| Sign-in with GitHub or SSO fails with a redirect error | Register the OAuth callback URL (or, with New sign-in for this stack, the Redirect URL to register) with your provider. With Hub address set to the application name the wizard shows it before you create the stack; with a generated name it is on the Stack tab after the first deploy. |
| Signed in to the Auth Proxy, but the hub says access denied | You are not in the Allowed groups set on the Notebooks stack. With an existing Auth Proxy, also check that its cookie domain covers the hub’s address. |
| A notebook gets AccessDenied on S3 | Servers can use the stack’s bucket or folder only. Other buckets need their own access, which this template does not grant. |