DockerSpawner: giving every JupyterHub user their own container
Spawns JupyterHub single user servers in Docker containers
At a glance
- What is it?
- DockerSpawner is a JupyterHub spawner that starts each user's notebook server inside a Docker container, with variants for plain containers, Docker Swarm services and host system users. It suits institutions that want per-user isolation and reproducible images, and it adds a container runtime to the critical path of every session.
- Who is it for?
- Adopt DockerSpawner when you run JupyterHub for many users and need each session isolated in its own container, when your users' environments differ enough that a shared environment would be unworkable, and when you already operate a Docker daemon on the same host as JupyterHub.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 17 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the spawner actually replaces in JupyterHub
JupyterHub's job is to authenticate a user and then hand that user a running notebook server. The part that actually starts the server is called a spawner, and the default one starts a process on the Hub host itself. DockerSpawner replaces that step.
The description is precise: the dockerspawner, also known as JupyterHub Docker Spawner, enables JupyterHub to spawn single user notebook servers in Docker containers. That is the whole contract. The Hub still does authentication, still manages the proxy routes to each server, and still tracks server state. Only the launch mechanism changes.
Why anyone would want that is the isolation. Every user gets a fresh container, so one user's installed package cannot break another's session, and a user who fills their disk or crashes a kernel does not affect the Hub or the other users. It also means the environment is defined by an image rather than by whatever happens to be installed on the shared host, which is the difference between a course that works for the first cohort and a course that works for all of them.
The target audience is stated in the package classifiers as Developers, System Administrators and Science/Research, and the project is licensed under the revised BSD license with a shared copyright model, meaning contributors keep copyright on their own contributions while the code is BSD licensed as a whole.
The project is part of the JupyterHub organisation, with the authorship credited to the Jupyter Development Team, and its support channel is the Jupyter Discourse forum rather than a bespoke tracker.
Three spawner classes, registered as separate entry points
The README describes three types of spawner, and the packaging metadata shows how each one is exposed to JupyterHub.
`DockerSpawner` takes an authenticated user and spawns a notebook server in a Docker container for that user. `SwarmSpawner` launches single user notebook servers as Docker Swarm mode services. `SystemUserSpawner` spawns single user notebook servers that correspond to system users on the host.
The entry point table in `pyproject.toml` makes the mechanism explicit, with three keys under `jupyterhub.spawners`:
[project.entry-points."jupyterhub.spawners"]
docker = "dockerspawner:DockerSpawner"
docker-system-user = "dockerspawner:SystemUserSpawner"
docker-swarm = "dockerspawner:SwarmSpawner"So these are not three modes of one class. They are three distinct spawner classes, each selectable in the JupyterHub configuration by the name you write there, and each is a separate piece of behaviour with a separate operational profile.
The differences matter more than the names suggest. The plain DockerSpawner asks the Docker daemon to run a container, and the container's lifetime is bound to the user's session. The SwarmSpawner asks a Swarm manager to create a service, which is a different scheduling and failover model, and it is the one that scales across a cluster. The SystemUserSpawner maps each Hub user to a real account on the host, which is the closest of the three to the default spawner and the one to pick when you need the notebook server to see host-level identity rather than a container-local one.
The examples directory backs this up with one directory per scenario: `examples/simple/`, `examples/swarm/`, `examples/oauth/`, `examples/internal-ssl/` and `examples/image_form/`. That last one is a useful signal, because a Docker image form is a common way to let a user choose their environment at spawn time rather than fixing one image for everyone.
Installing it and pointing JupyterHub at the spawner
The prerequisites are stated plainly: Python 3.9 or above and JupyterHub 4 or above is required. There is a small inconsistency to be aware of, since the README says Python 3.9 or above while the packaging metadata sets `requires-python = ">=3.10"`, so the effective floor for an installation from the package index is 3.10.
Installation is a single command:
pip install dockerspawnerThe package is on PyPI and on conda-forge, and the README shows badges for both.
After installing, the spawner is selected by naming it in the JupyterHub configuration. The key that JupyterHub looks for is `docker`, or `docker-swarm`, or `docker-system-user` depending on which class you want, and the `c.JupyterHub.spawner_class` configuration value is where the choice goes. The exact configuration file is not shown in the README, which points to the DockerSpawner documentation on Read the Docs for features and usage, and the `examples/` directory holds working configuration for each of the scenarios above.
The runtime dependencies are minimal and telling. The `docker` package is the Python client for the Docker Engine API, `escapism` handles quoting and escaping of command arguments so that a username with a space in it does not break a command line, and `jupyterhub>=4` pins the Hub API contract the spawner is written against. There is no ORM, no web framework and no template engine, which is what a spawner should look like: it starts processes, it does not serve pages.
One operational point the documentation has to cover and the README does not: the Hub needs permission to talk to the Docker daemon. Since the Hub is the process that asks for containers, that access is a real deployment requirement and the place where most first-time setups get stuck.
The three runtime dependencies and what they imply
Three direct dependencies is a small surface, and each one tells you something about the design.
`docker` is the official Python client for the Docker Engine API, which means DockerSpawner drives the daemon over its socket rather than shelling out to a command line client. That is a better foundation for a spawner, because it gives structured errors and a documented API rather than parsing terminal output, and it is why the `escapism` package is needed at all, since anything that does construct command strings still has to quote them correctly.
`escapism` is there for the parts that do build command lines. A Hub user name is attacker-controlled input in a real deployment, and a name containing shell metacharacters would be a problem if it were interpolated into a command unquoted. Handling that in a small dedicated library is a better outcome than each call site improvising.
`jupyterhub>=4` is the third, and it is a floor rather than a loose constraint. The spawner is a plugin loaded through the `jupyterhub.spawners` entry point group, so it is coupled to the Hub's spawner API. When the Hub changes that API in a major release, a spawner that has not been updated stops working, and the version constraint makes that failure explicit at install time rather than at session start.
The optional test extras tell you what the project considers its test surface: `psutil` for inspecting process state, `pytest`, `pytest-asyncio>=0.25`, and `pytest-cov` for coverage. The `asyncio` requirement is the interesting one, since it tells you the spawner is written as asynchronous code, which is consistent with a Hub that has been asynchronous for several major versions.
The build system is `hatchling`, the version is read dynamically from `dockerspawner/_version.py`, and the project classifies itself as `Development Status :: 5 - Production/Stable`.
Where DockerSpawner is the wrong tool
The first wrong-tool case is a small deployment. If you are running JupyterHub for a handful of users on one machine, the container runtime buys you very little and costs you a lot: the Hub needs daemon access, every session start includes a container create, and debugging a session now means debugging inside a container. The default spawner, which starts a process on the host, is simpler and adequate at that size.
The second is an environment that already has a cluster. If your organisation runs Kubernetes and the operational model is that workloads live in the cluster, the right answer is a KubernetesSpawner rather than a Docker daemon that happens to run on the Hub host. DockerSpawner's Swarm variant covers the Docker-native cluster case, but it covers only that case, and the difference in approach between a Swarm service and a Kubernetes pod is larger than the shared word container suggests.
The third is a requirement for per-user machine-level identity. The SystemUserSpawner exists precisely to bridge this, and it reintroduces most of the problems of the default spawner while adding container overhead. If your authorisation model depends on Unix user identity or on host file permissions, decide deliberately which of the two worlds you need.
The fourth is a network environment without a usable container host. Because the Hub must reach the Docker daemon, a setup where the Hub sits on a locked-down control node and the containers run elsewhere needs either a remote daemon configuration or a different spawner entirely.
The honest comparison with a plain notebook server on a shared host is straightforward. One server per user process on one host is faster to start, easier to inspect and cheaper, and it gives you isolation only at the process level. DockerSpawner gives you a clean image per session, disposable state and the ability to rebuild the environment from a Dockerfile, and it charges you for that with the runtime dependency, the image build time and the debugging overhead.
Maintenance state, releases and where the project is headed
The repository is not archived and the last push was on 2026-10-05, so the code is being worked on. There are, however, no GitHub releases, which is normal for a Python package that is published to PyPI and conda-forge rather than shipped as binaries.
The versioning scheme is the thing to notice. The package declares a dynamic version read from `dockerspawner/_version.py`, and the tree contains a `RELEASE.md` file. For a package that must stay compatible with a moving Hub API, a calendar-based version is a reasonable choice, since the Hub's own major releases are what break things and a date tells you which Hub era a given release was built against.
The documentation infrastructure is in place, with a `.readthedocs.yml` in the tree and the project documentation hosted at jupyterhub-dockerspawner.readthedocs.io. The `.pre-commit-config.yaml` and `.flake8` files show the formatting and linting gates, and the `tools/` directory holds repository maintenance scripts.
The support model is worth reading as an adopter. Questions go to the Discourse community forum at discourse.jupyter.org, issues are for reporting problems, and the README links the JupyterHub tutorial, the JupyterHub documentation, the REST API documentation and the Project Jupyter documentation. That spread of links tells you the project expects you to be running a JupyterHub first and to treat this as one component in a documented stack rather than a self-contained product.
The licence is the revised BSD license, and the README is explicit about the shared copyright model, which enables all contributors to maintain the copyright on their contributions while the code as a whole is BSD licensed. For an institution embedding this in its own deployment, that is the arrangement you want: no copyleft obligations on your surrounding platform.
Editorial conclusion
Adopt DockerSpawner when you run JupyterHub for many users and need each session isolated in its own container, when your users' environments differ enough that a shared environment would be unworkable, and when you already operate a Docker daemon on the same host as JupyterHub. Do not adopt it for a single-user or small teaching setup where a LocalProcessSpawner or a KubernetesSpawner is simpler, and do not adopt it without reading the Swarm examples, because the plain DockerSpawner and the SwarmSpawner have clearly different operational characteristics. Before you install, check the Docker socket access requirements and the `docker` and `escapism` dependencies, verify that the JupyterHub version is 4 or above, and note the small inconsistency between the README prerequisite of Python 3.9 and the packaging requirement of 3.10 or later.
Frequently asked questions
What does DockerSpawner do in JupyterHub?
It is a JupyterHub spawner that starts each user's single user notebook server inside a Docker container instead of on the Hub host. JupyterHub still handles authentication and routing, and only the launch mechanism changes.
How do I install DockerSpawner and which spawner do I select?
The package is installed with `pip install dockerspawner`, and JupyterHub 4 or above is required. The spawner class is chosen in the Hub configuration by name, using `docker` for DockerSpawner, `docker-swarm` for SwarmSpawner or `docker-system-user` for SystemUserSpawner, each of which is registered as an entry point in the `jupyterhub.spawners` group.
What are the differences between DockerSpawner, SwarmSpawner and SystemUserSpawner?
DockerSpawner spawns a notebook server in a Docker container for the authenticated user, SwarmSpawner launches them as Docker Swarm mode services, and SystemUserSpawner spawns servers that correspond to system users on the host. The three are separate spawner classes rather than modes of one class, and the examples directory holds configuration for each.
What does DockerSpawner depend on at runtime?
The direct dependencies are the `docker` Python client for the Docker Engine API, `escapism` for quoting and escaping of command arguments, and `jupyterhub>=4` for the Hub API it is written against. The test extras add `psutil`, `pytest`, `pytest-asyncio>=0.25` and `pytest-cov`.
What licence is DockerSpawner released under?
All code is licensed under the terms of the revised BSD license, and the package metadata records `BSD-3-Clause`. The README states the project uses a shared copyright model, so contributors keep copyright on their own contributions.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jupyterhub-dockerspawner)Was this useful?