# kubespawner turns a JupyterHub login into a Kubernetes pod, one user at a time

> kubespawner is the JupyterHub spawner that starts a single user notebook server as a pod on a Kubernetes cluster. It gives per user CPU and memory limits, persistent volumes and namespace isolation, at the cost of putting a cluster in front of what used to be a single machine.

**jupyterhub/kubespawner** — Kubernetes spawner for JupyterHub

- Repository: https://github.com/jupyterhub/kubespawner
- Website: https://jupyterhub-kubespawner.readthedocs.io
- Stars: 613 · Forks: 308
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/jupyterhub-kubespawner

## A spawner is a strategy for turning one user's request into a pod

kubespawner, also known as the JupyterHub Kubernetes Spawner, enables JupyterHub to spawn single user notebook servers on a Kubernetes cluster. That is the whole job description, and it is worth being precise about what it does and does not do.

JupyterHub itself does not run notebooks. It authenticates users, and then asks a spawner to start a server for each one and to stop it later. kubespawner is that strategy implemented against Kubernetes: it creates a pod for the user, waits for it to be ready, wires the proxy to reach it, and deletes it when the session ends.

The README frames the motivation in terms of scale. Kubernetes is described as an open source system for automating deployment, scaling and management of containerized applications, and the guidance is that if you want a JupyterHub setup that scales across multiple nodes, anything with over roughly 50 simultaneous users, Kubernetes is a wonderful way to do it. That number is the project's own rule of thumb for when the complexity starts to pay off, and it is the first thing to weigh before adopting.

The package is on PyPI as jupyterhub-kubespawner and on conda-forge, the project is primarily Python, and the licence is BSD-3-Clause.

## Resource limits per notebook are the feature teams cite first

The feature list leads with elasticity and runs through the operational benefits.

Easily and elastically running anywhere between 2 and thousands of nodes with the same set of abstractions, scaling up and down by adding or removing nodes. Running JupyterHub itself inside Kubernetes, which the README says allows you to manage many JupyterHub deployments with only Kubernetes and without an extra layer of Ansible, Puppet or Bash scripts, and which also gives integrated monitoring and failover for the hub process itself.

But the item that changes day to day for users is the resource control. The README lists the ability to provide guarantees and limits on the amount of CPU and RAM that single user notebooks can use, and points at Kubernetes resource control as the mechanism. On a shared machine, one user running a runaway job takes the box down for everyone. On Kubernetes, that notebook is capped, and the cost of the mistake is bounded.

The other two that matter operationally are the volume and the security parameters. Various types of persistent volumes can be mounted onto the single user notebook container, and security parameters such as userid and groupid and SELinux can be controlled through Pod Security Policy, which the README links.

So the value of this spawner is less about raw scale and more about being able to promise a colleague that their notebook will not eat the machine, and that their data will still be there tomorrow.

## Namespaces are how one cluster serves several JupyterHubs

The README describes spawning multiple hubs in the same Kubernetes cluster, with support for namespaces, and the point it makes is resource isolation between those hubs.

By limiting the amount of resources each namespace can use, you effectively limit the resources a single JupyterHub and its users can use. The README says this allows organizations to maintain multiple JupyterHubs with just one Kubernetes cluster, with easy maintenance and high resource utilization.

That is a different arrangement from the one most people assume. Rather than one hub per cluster, you get one cluster, several namespaces, and a hub in each. The operational benefit is that capacity planning happens once at the cluster level, and the cost of one team outgrowing its share becomes a namespace quota question rather than a cluster migration.

The README is candid that the cluster needs to be configured to support the types of volumes you want to use, and that configuration is the part of this work that is genuinely hard. Storage classes, access modes and quota enforcement are cluster level decisions, and a spawner configuration that asks for a volume type the cluster cannot provide will fail at spawn time rather than at deploy time.

## What kubespawner needs from the cluster, and what it does not

The requirements section is short, which is good, and one line in it is worth reading twice.

JupyterHub 4.0 or newer is required. For Kubernetes, everything should work from v1.24 or newer. And then the note that surprises people: the Kube DNS addon is not strictly required, because the spawner uses environment variable based discovery instead.

That is a design choice with practical consequences. A spawner that relied on cluster DNS would need the cluster's DNS service configured and healthy, and would break in a way that is hard to diagnose from the notebook side. Using environment variables for discovery removes that dependency, at the cost of not being able to resolve names the normal way.

The other requirement is on your side rather than the software's: your cluster must be configured to support the types of volumes you want to use. Kubernetes gives you a large set of volume types and they are not interchangeable, and the spawner only asks the cluster for what you configure it to ask for.

For getting a cluster to play with, the README suggests Google Container Engine as probably the nicest option, and for AWS or Azure it points at kops as probably the way to go.

## Installing the package and what it pulls in

The README does not give an install command, but the packaging metadata in pyproject.toml describes exactly what the package needs.

```toml
[project]
name = "jupyterhub-kubespawner"
description = "JupyterHub Spawner for Kubernetes"
readme = "README.md"
requires-python = ">=3.7"
license = {file = "LICENSE"}
```

The build backend is hatchling, and the runtime dependencies are the list that tells you what this thing talks to.

```toml
dependencies = [
    "escapism",
    "jinja2",
    "jupyterhub>=4.0.0",
    "kubernetes_asyncio>=24.2.3",
    "python-slugify",
    "pyYAML",
    "traitlets",
    "urllib3",
]
```

jupyterhub at 4.0.0 or newer is the hard floor, kubernetes_asyncio at 24.2.3 or newer is the Kubernetes client, and traitlets is how the spawner options are configured. jinja2 is there because the spawner renders templates for the pods it creates, and the repository has a kubespawner/templates directory that the build configuration explicitly includes as an artifact, with a comment explaining that hatch does not respect MANIFEST.in for this.

The test extra pulls in the synchronous kubernetes client, pytest, pytest-cov and pytest-asyncio, with a FIXME comment noting that pytest-asyncio is not pinned. The classifiers mark the project as Development Status 5, Production or Stable, and as typed.

For reference, the repository also ships a jupyterhub_config.py at the top level, which is the file you would edit to point a hub at the spawner.

## Where the real configuration surface is

The README does not enumerate the configuration options, and that is deliberate rather than an omission. It points at the KubeSpawner documentation for more information about features and usage, and in particular links a list of all the spawner options, described as the module kubespawner.spawner in the API reference.

That link is the configuration reference, and it is where a real deployment is won or lost. The options cover things the README only gestures at: the image, the resource requests and limits, the volumes and their mount paths, the service type, the labels and annotations applied to the pod, the namespace, and the storage configuration.

Two structural details in the repository layout explain how the pieces fit. The package directory is kubespawner/, and the templates subdirectory holds the pod manifests that the Jinja2 templates render. The pyproject.toml includes that directory as a build artifact explicitly, which means the rendered manifests are part of the distributed package rather than something you have to copy into your own configuration.

The site documentation is hosted on readthedocs at jupyterhub-kubespawner.readthedocs.io, and the repository has a docs/ directory as its source, so the documentation is versioned with the code.

## What you give up, and when this is the wrong spawner

There is a real cost to this approach and the README's own threshold hints at it.

You now need a Kubernetes cluster to be running, configured and kept running, before a student can open a notebook. The README suggests Google Container Engine for a cluster to play with, which is a hint that this is not a lightweight dependency. You also need storage configured, service networking in place, and enough familiarity with Kubernetes to debug a pod that fails to become ready, which is a different skill set from running JupyterHub on a virtual machine.

A second cost is the layer between you and the failure. When a notebook does not start, the question is whether the hub authenticated the user, whether the spawner created the pod, whether the pod became ready, or whether the proxy could route to it, and those are four different systems to look at. On a single machine, the equivalent debugging is a much shorter path.

The third is the scale threshold itself. Under roughly 50 simultaneous users, the README implies Kubernetes buys you more than you need.

So the wrong cases are specific: a single machine with a class of twenty students, an environment where nobody administers Kubernetes, or a deployment where a persistent volume per user is not something the storage layer can deliver. In all three, a simpler spawner on a single host serves the same users with less to operate.

## Maintenance, licensing and where the project is going

The repository is not archived and the last push was on 2026-10-05, so work is landing on the main branch. There are no GitHub releases, so the version to track is the one on PyPI, and the README carries badges for the latest PyPI version and the latest conda-forge version. There is a RELEASE.md in the repository for the release process.

The licence is BSD-3-Clause, described in the README as the revised BSD license. The README also explains the copyright model: a shared copyright model that enables all contributors to maintain the copyright on their contributions, with the Jupyter Contributors listed as the authors in pyproject.toml. For a project that ships inside an institutional platform, the BSD terms are among the least encumbering you can get.

The project also documents how to get help in two places, the Jupyter mailing list and Gitter for the JupyterHub channel, and there is a CONTRIBUTING.md in the repository.

One note for anyone watching for deprecations. The README describes security control through Pod Security Policy, and Pod Security Policy was removed from Kubernetes in version 1.25, while the README states that everything should work from v1.24 or newer. The README does not mention Pod Security Admission, the replacement mechanism, and it does not say which mechanism the current spawner options use. That is a real gap to close by reading the spawner options documentation, and it is the first thing to check before you write any security policy configuration.

## Conclusion

Adopt kubespawner if you already run Kubernetes and need JupyterHub to scale past a single node, or if you need per user CPU and memory guarantees, isolated namespaces for separate teams, and persistent volumes mounted into each notebook. Do not adopt it for a small teaching setup on one machine, where the cluster becomes the problem rather than the solution. Before you deploy, confirm you are on JupyterHub 4.0 or newer and Kubernetes v1.24 or newer, check that the volume types you intend to mount are supported by your cluster, and read the spawner options list in the documentation for the settings that match your namespace and storage layout.

## FAQ

### What does kubespawner do?

kubespawner, also known as the JupyterHub Kubernetes Spawner, enables JupyterHub to spawn single user notebook servers on a Kubernetes cluster, one pod per user, with the pod stopped when the session ends.

### What are the requirements for kubespawner?

It requires JupyterHub 4.0 or newer and Kubernetes v1.24 or newer. The README says the Kube DNS addon is not strictly required because the spawner uses environment variable based discovery instead, and that your cluster must be configured to support the volume types you want to use.

### How do I limit how much CPU and RAM a notebook uses?

Kubernetes resource control is exposed through the spawner, so you can set guarantees and limits on the CPU and RAM that single user notebooks consume. The README lists this as a key feature, alongside mounting persistent volumes and controlling security parameters.

### Can several JupyterHubs share one Kubernetes cluster?

Yes. The README describes spawning multiple hubs in the same cluster with support for namespaces, so limiting the resources each namespace can use effectively limits what a single JupyterHub and its users can consume.

### What licence does kubespawner use?

The revised BSD license, BSD-3-Clause. The README says it uses a shared copyright model that enables all contributors to maintain the copyright on their contributions, and the authors are listed as the Jupyter Contributors.

## Sources

- [Issues](https://github.com/jupyterhub/kubespawner/issues)
- [jupyterhub/kubespawner on GitHub](https://github.com/jupyterhub/kubespawner)
- [License: BSD-3-Clause](https://github.com/jupyterhub/kubespawner/blob/main/LICENSE)
- [Project website](https://jupyterhub-kubespawner.readthedocs.io)
- [README](https://github.com/jupyterhub/kubespawner/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jupyterhub-kubespawner
