docker-stacks Documentation

Ready-to-run Docker images containing Jupyter applications

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy docker-stacks on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name docker-stacks --restart always jupyter/docker-stacks:latest

Technical Documentation: jupyter/docker-stacks

ℹ️ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference

Overview (`README.md`)

Jupyter Docker Stacks








Jupyter Docker Stacks are a set of ready-to-run Docker images containing Jupyter applications and interactive computing tools.
You can use a stack image to do any of the following (and more):

  • Start a personal Jupyter Server with the JupyterLab frontend (default)
  • Run JupyterLab for a team using JupyterHub
  • Start a personal Jupyter Server with the Jupyter Notebook frontend in a local Docker container
  • Write your own project Dockerfile

Quick Start

You can try the quay.io/jupyter/base-notebook image on https://mybinder.org.
Otherwise, the examples below may help you get started if you have Docker installed,
know which Docker image you want to use, and want to launch a single Jupyter Application in a container.

The User Guide on ReadTheDocs describes additional uses and features in detail.

{note}
Since `2023-10-20` our images are only pushed to `Quay.io` registry.
Older images are available on Docker Hub, but they will no longer be updated.

Example 1

This command pulls the jupyter/scipy-notebook image tagged 2026-07-28 from Quay.io if it is not already present on the local host.
It then starts a container running a Jupyter Server with the JupyterLab frontend and exposes the container's internal port 8888 to port 10000 of the host machine:

bash
docker run -p 10000:8888 quay.io/jupyter/scipy-notebook:2026-07-28

You can modify the host port to which the container's port is mapped by changing the value of the -p option to -p 8888:8888.

Visiting http://<hostname>:10000/?token=<token> in a browser loads JupyterLab,
where:

  • The hostname is the name of the computer running Docker
  • The token is the secret token printed in the console.

The container remains intact for restart after the Server exits.

Example 2

This command pulls the jupyter/datascience-notebook image tagged 2026-07-28 from Quay.io if it is not already present on the local host.
It then starts an ephemeral container running a Jupyter Server with the JupyterLab frontend and exposes the server on host port 10000.

bash
docker run -it --rm -p 10000:8888 -v "${PWD}":/home/jovyan/work quay.io/jupyter/datascience-notebook:2026-07-28

The use of the -v flag in the command mounts the current working directory on the host (${PWD} in the example command) as /home/jovyan/work in the container.
The server logs appear in the terminal.

Visiting http://<hostname>:10000/?token=<token> in a browser loads JupyterLab.

Due to the usage of the --rm flag
Docker automatically cleans up the container and removes the file system when the container exits,
but any changes made to the ~/work directory and its files in the container will remain intact on the host.
The -i flag keeps the container's STDIN open, and lets you send input to the container through standard input.
The -t flag attaches a pseudo-TTY to the container.

{note}
By default, [jupyter's root_dir](https://jupyter-server.readthedocs.io/en/latest/other/full-config.html) is `/home/jovyan`.
So, new notebooks will be saved there, unless you change the directory in the file browser.

To change the default directory, you must specify `ServerApp.root_dir` by adding this line to the previous command: `start-notebook.py --ServerApp.root_dir=/home/jovyan/work`.

Choosing Jupyter frontend

JupyterLab is the default for all the Jupyter Docker Stacks images.
It is still possible to switch back to Jupyter Notebook (or to launch a different startup command).
You can achieve this by passing the environment variable DOCKER_STACKS_JUPYTER_CMD=notebook (or any other valid jupyter subcommand) at container startup;
more information is available in the documentation.

Resources

Acknowledgments

  • Starting from 2022-07-05, aarch64 self-hosted runners were sponsored by @mathbunnyru.
    Please, consider sponsoring his work on GitHub
  • Starting from 2023-10-31, aarch64 self-hosted runners are sponsored by an amazing 2i2c non-profit organization
  • Starting from 2025-02-11, we use GitHub-hosted aarch64 runners

CPU Architectures

  • We publish containers for both x86_64 and aarch64 platforms
  • Single-platform images have either aarch64- or x86_64- tag prefixes, for example, quay.io/jupyter/base-notebook:aarch64-python-3.13.14
  • Starting from 2022-09-21, we create multi-platform images (except tensorflow-notebook)
  • Starting from 2023-06-01, we create a multi-platform tensorflow-notebook image as well
  • Starting from 2024-02-24, we create CUDA enabled variants of pytorch-notebook image for x86_64 platform
  • Starting from 2024-03-26, we create CUDA enabled variant of tensorflow-notebook image for x86_64 platform
  • Starting from 2025-12-02, we create CUDA enabled variants of pytorch-notebook and tensorflow-notebook images for aarch64 platform as well

Using old images

This project only builds one set of images at a time.
The weekly build (latest tag) is based on Ubuntu 26.04 and Python 3.13.
If you want to use the older Ubuntu and/or Python version, you can use the following images:

Ubuntu 24.04

Build Date Python Tag
2024-10-22 3.11 b74418220768
2025-08-11 3.12 82d322f00937
2026-09-28 3.13 ac62373f9f60

Ubuntu 22.04

Build Date Python Tag
2022-10-09 3.7 b86753318aa1
2022-10-09 3.8 7285848c0a11
2022-10-09 3.9 ed2908bbb62e
2023-05-30 3.10 4d70cf8da953
2024-08-26 3.11 00987883e58d

Ubuntu 20.04

Build Date Python Tag
2022-10-09 3.7 1aac87eb7fa5
2022-10-09 3.8 a374cab4fcb6
2022-10-09 3.9 5ae537728c69
2022-10-09 3.10 f3079808ca8c

Contributing

Please see the the documentation
for information about how to contribute
issues,
features,
recipes,
tests,
and community-maintained stacks.

LICENSE

This project is licensed under the terms of the Modified BSD License (also known as New or Revised or 3-Clause BSD).

About the Jupyter Development Team

The Jupyter Development Team is the set of all contributors to the Jupyter project.
This includes all of the Jupyter subprojects.

The core team that coordinates development on GitHub can be found here:
https://github.com/jupyter/.

Jupyter uses a shared copyright model. Each contributor maintains copyright
over their contributions to Jupyter. But, it is important to note that these
contributions are typically only changes to the repositories. Thus, the Jupyter
source code, in its entirety is not the copyright of any single person or
institution. Instead, it is the collective copyright of the entire Jupyter
Development Team. If individual contributors want to maintain a record of what
changes/contributions they have specific copyright on, they should indicate
their copyright in the commit message of the change, when they commit the
change to one of the Jupyter repositories.

With this in mind, the following banner should be used in any source code file
to indicate the copyright and license terms:

text
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

Alternatives


Overview (`examples/README.md`)

Examples

These examples are not tested and might not work.
Please, send PRs if you start using these examples and see some issues.


Overview (`tagging/README.md`)

Docker stacks tagging and manifest creation

Please, refer to the tagging section of documentation to see how tags and manifests are created.


Overview (`tests/README.md`)

Docker stacks testing

Please, refer to the testing section of documentation to see how the tests are run.


Overview (`examples/docker-compose/README.md`)

Docker Compose example

This example demonstrate how to deploy docker-stack notebook containers to any Docker Machine-controlled host using Docker Compose.

Prerequisites

See the installation instructions for your environment.

Quickstart

Build and run a jupyter/minimal-notebook image on a VirtualBox VM on local desktop.

bash
# create a Docker Machine-controlled VirtualBox VM
bin/vbox.sh mymachine

# activate the docker machine
eval "$(docker-machine env mymachine)"

# build the notebook image on the machine
notebook/build.sh

# bring up the notebook container
notebook/up.sh

To stop and remove the container:

bash
notebook/down.sh

FAQ

How do I specify which docker-stack notebook image to deploy?

You can customize the docker-stack notebook image to deploy by modifying the notebook/Dockerfile.
For example, you can build and deploy a jupyter/all-spark-notebook by modifying the Dockerfile like so:

dockerfile
FROM quay.io/jupyter/all-spark-notebook
# Your RUN commands and so on

Once you modify the Dockerfile, don't forget to rebuild the image.

bash
# activate the docker machine
eval "$(docker-machine env mymachine)"

notebook/build.sh

Can I run multiple notebook containers on the same VM?

Yes. Set environment variables to specify unique names and ports when running the up.sh command.

bash
NAME=my-notebook PORT=9000 notebook/up.sh
NAME=your-notebook PORT=9001 notebook/up.sh

To stop and remove the containers:

bash
NAME=my-notebook notebook/down.sh
NAME=your-notebook notebook/down.sh

Where are my notebooks stored?

The up.sh creates a Docker volume named after the notebook container with a -work suffix, e.g., my-notebook-work.

Can multiple notebook containers share the same notebook volume?

Yes. Set the WORK_VOLUME environment variable to the same value for each notebook.

bash
NAME=my-notebook PORT=9000 WORK_VOLUME=our-work notebook/up.sh
NAME=your-notebook PORT=9001 WORK_VOLUME=our-work notebook/up.sh

How do I run over HTTPS?

To run the Jupyter Server with a self-signed certificate, pass the --secure option to the up.sh script.
You must also provide a password, which will be used to secure the Jupyter Server.
You can specify the password by setting the PASSWORD environment variable, or by passing it to the up.sh script.

bash
PASSWORD=a_secret notebook/up.sh --secure

# or
notebook/up.sh --secure --password a_secret

Can I use Let's Encrypt certificate chains?

Sure. If you want to secure access to publicly addressable notebook containers, you can generate a free certificate using the Let's Encrypt service.

This example includes the bin/letsencrypt.sh script, which runs the letsencrypt client to create a full-chain certificate and private key, and stores them in a Docker volume.

{note}
The script hard codes several `letsencrypt` options, one of which automatically agrees to the Let's Encrypt Terms of Service.

The following command will create a certificate chain and store it in a Docker volume named mydomain-secrets.

bash
FQDN=host.mydomain.com [email protected] \
    SECRETS_VOLUME=mydomain-secrets \
    bin/letsencrypt.sh

Now run up.sh with the --letsencrypt option.
You must also provide the name of the secrets volume and a password.

bash
PASSWORD=a_secret SECRETS_VOLUME=mydomain-secrets notebook/up.sh --letsencrypt

# or
notebook/up.sh --letsencrypt --password a_secret --secrets mydomain-secrets

Be aware that Let's Encrypt has a pretty low rate limit per domain at the moment.
You can avoid exhausting your limit by testing against the Let's Encrypt staging servers.
To hit their staging servers, set the environment variable CERT_SERVER=--staging.

bash
FQDN=host.mydomain.com [email protected] \
    CERT_SERVER=--staging \
    bin/letsencrypt.sh

Also, be aware that Let's Encrypt certificates are short-lived (90 days).
If you need them for a longer period of time, you'll need to manually set up a cron job to run the renewal steps.
(You can reuse the command above.)

Can I deploy to any Docker Machine host?

Yes, you should be able to deploy to any Docker Machine-controlled host.
To make it easier to get up and running, this example includes scripts to provision Docker Machines to VirtualBox and IBM SoftLayer, but more scripts are welcome!

To create a Docker machine using a VirtualBox VM on local desktop:

bash
bin/vbox.sh mymachine

To create a Docker machine using a virtual device on IBM SoftLayer:

bash
export SOFTLAYER_USER=my_softlayer_username
export SOFTLAYER_API_KEY=my_softlayer_api_key
export SOFTLAYER_DOMAIN=my.domain

# Create virtual device
bin/softlayer.sh myhost

# Add DNS entry (SoftLayer DNS zone must exist for SOFTLAYER_DOMAIN)
bin/sl-dns.sh myhost

Troubleshooting

Unable to connect to VirtualBox VM on Mac OS X when using Cisco VPN client

The Cisco VPN client blocks access to IP addresses that it does not know about, and may block access to a new VM if it is created while the Cisco VPN client is running.

  1. Stop Cisco VPN client. (It does not allow modifications to route table).
  2. Run ifconfig to list vboxnet virtual network devices.
  3. Run sudo route -nv add -net 192.168.99 -interface vboxnetX, where X is the number of the virtual device assigned to the VirtualBox VM.
  4. Start Cisco VPN client.

Overview (`examples/make-deploy/README.md`)

Make deploy example

This folder contains a Makefile and a set of supporting files demonstrating how to run a docker-stack notebook container on a docker-machine controlled host.

Prerequisites

  • make 3.81+
  • docker-machine 0.5.0+
  • docker 1.9.0+

Quickstart

To show what's possible, here's how to run the jupyter/minimal-notebook on a brand-new local virtualbox.

bash
# create a new VM
make virtualbox-vm NAME=dev
# make the new VM the active docker machine
eval $(docker-machine env dev)
# pull a docker stack and build a local image from it
make image
# start a Server in a container
make notebook

The last command will log the IP address and port to visit in your browser.

FAQ

Can I run multiple notebook containers on the same VM?

Yes. Specify a unique name and port on the make notebook command.

bash
make notebook NAME=my-notebook PORT=9000
make notebook NAME=your-notebook PORT=9001

Can multiple notebook containers share their notebook directory?

Yes.

bash
make notebook NAME=my-notebook PORT=9000 WORK_VOLUME=our-work
make notebook NAME=your-notebook PORT=9001 WORK_VOLUME=our-work

How do I run over HTTPS?

Instead of make notebook, run make self-signed-notebook PASSWORD=your_desired_password.
This target gives you a notebook with a self-signed certificate.

That self-signed certificate is a pain. Let's Encrypt?

Yes. Please.

bash
make letsencrypt FQDN=host.mydomain.com [email protected]
make letsencrypt-notebook

The first command creates a Docker volume named after the notebook container with a -secrets suffix.
It then runs the letsencrypt client with a slew of options (one of which has you automatically agreeing to the Let's Encrypt Terms of Service, see the Makefile).
The second command mounts the secrets volume and configures Jupyter to use the full-chain certificate and private key.

Be aware: Let's Encrypt has a pretty low rate limit per domain at the moment.
You can avoid exhausting your limit by testing against the Let's Encrypt staging servers.
To hit their staging servers, set the environment variable CERT_SERVER=--staging.

bash
make letsencrypt FQDN=host.mydomain.com [email protected] CERT_SERVER=--staging

Also, keep in mind Let's Encrypt certificates are short-lived: 90 days at the moment.
You'll need to manually set up a cron job to run the renewal steps at the moment.
(You can reuse the first command above.)

My pip/conda/apt-get installs disappear every time I restart the container. Can I make them permanent?

bash
# add your pip, conda, apt-get, etc. permanent features to the Dockerfile where
# indicated by the comments in the Dockerfile
vi Dockerfile
make image
make notebook

How do I upgrade my Docker container?

bash
make image DOCKER_ARGS=--pull
make notebook

The first line pulls the latest version of the Docker image used in the local Dockerfile.
Then it rebuilds the local Docker image containing any customizations you may have added to it.
The second line kills your currently running notebook container, and starts a fresh one using the new image.

Can I run on another VM provider other than VirtualBox?

Yes. As an example, there's a softlayer.makefile included in this repo as an example.
You would use it like so:

bash
make softlayer-vm NAME=myhost \
    SOFTLAYER_DOMAIN=your_desired_domain \
    SOFTLAYER_USER=your_user_id \
    SOFTLAYER_API_KEY=your_api_key
eval $(docker-machine env myhost)
# optional, creates a real DNS entry for the VM using the machine name as the hostname
make softlayer-dns SOFTLAYER_DOMAIN=your_desired_domain
make image
make notebook

If you'd like to add support for another docker-machine driver, use the softlayer.makefile as a template.

Where are my notebooks stored?

make notebook creates a Docker volume named after the notebook container with a -data suffix.

Uh ... make?

Yes, sorry Windows users. It got the job done for a simple example.
We can certainly accept other deployment mechanism examples in the parent folder or in other repos.

Are there any other options?

Yes indeed. cat the Makefiles and look at the target parameters.


Overview (`examples/openshift/README.md`)

OpenShift example

This example provides templates for deploying the Jupyter Project docker-stacks images to OpenShift.

Prerequisites

Any OpenShift 3 environment.
The templates were tested with OpenShift 3.7.
It is believed they should work with at least OpenShift 3.6 or later.

Do be aware that the Jupyter Project docker-stacks images are very large.
The OpenShift environment you are using must provide sufficient quota on the per-user space for images and the file system for running containers.
If the quota is too small, the pulling of the images to a node in the OpenShift cluster when deploying them, will fail due to lack of space.
Even if the image is able to run, if the quota is only just larger than the space required for the image, you will not be able to install many packages into the container before running out of space.

OpenShift Online, the public hosted version of OpenShift from Red Hat has a quota of only 3GB for the image and container file system.
As a result, only the minimal-notebook can be started and there is little space remaining to install additional packages.
Although OpenShift Online is suitable for demonstrating these templates work, what you can do in that environment will be limited due to the size of the images.

If you want to experiment with using Jupyter Notebooks in an OpenShift environment, you should instead use Minishift.
Minishift provides you the ability to run OpenShift in a virtual machine on your own local computer.

Loading the Templates

To load the templates, login to OpenShift from the command line and run:

bash
oc create -f https://raw.githubusercontent.com/jupyter/docker-stacks/main/examples/openshift/templates.json

This should create the jupyter-notebook template

The template can be used from the command line using the oc new-app command, or from the OpenShift web console by selecting Add to Project.
This README is only going to explain deploying from the command line.

Deploying a Notebook

To deploy a notebook from the command line using the template, run:

bash
oc new-app --template jupyter-notebook

The output will be similar to:

lang-none
--> Deploying template "jupyter/jupyter-notebook" to project jupyter

     Jupyter Notebook
     ---------
     Template for deploying Jupyter Notebook images.

     * With parameters:
        * APPLICATION_NAME=notebook
        * NOTEBOOK_IMAGE=quay.io/jupyter/minimal-notebook:latest
        * NOTEBOOK_PASSWORD=ded4d7cada554aa48e0db612e1ed1080 # generated

--> Creating resources ...
    configmap "notebook-cfg" created
    deploymentconfig "notebook" created
    route "notebook" created
    service "notebook" created
--> Success
    Access your application via route 'notebook-jupyter.b9ad.pro-us-east-1.openshiftapps.com'
    Run 'oc status' to view your app.

When no template parameters are provided, the name of the deployed notebook will be notebook.
The image used will be:

lang-none
quay.io/jupyter/minimal-notebook:latest

A password you can use when accessing the notebook will be auto generated and is displayed in the output from running oc new-app.

To see the hostname for accessing the notebook run:

bash
oc get routes

The output will be similar to:

lang-none
NAME       HOST/PORT                                               PATH      SERVICES   PORT       TERMINATION     WILDCARD
notebook   notebook-jupyter.abcd.pro-us-east-1.openshiftapps.com             notebook   8888-tcp   edge/Redirect   None

A secure route will be used to expose the notebook outside the OpenShift cluster, so in this case the URL would be:

lang-none
https://notebook-jupyter.abcd.pro-us-east-1.openshiftapps.com/

When prompted, enter the password for the notebook.

Passing Template Parameters

To override the name for the notebook, the image used, and the password, you can pass template parameters using the --param option.

bash
oc new-app --template jupyter-notebook \
    --param APPLICATION_NAME=mynotebook \
    --param NOTEBOOK_IMAGE=quay.io/jupyter/scipy-notebook:latest \
    --param NOTEBOOK_PASSWORD=mypassword

You can deploy any of the Jupyter Project docker-stacks images.

If you don't care what version of the image is used, add the :latest tag at the end of the image name, otherwise use the hash corresponding to the image version you want to use.

Deleting the Notebook Instance

To delete the notebook instance, run oc delete using a label selector for the application name.

bash
oc delete all,configmap --selector app=mynotebook

Adding Persistent Storage

You can upload notebooks and other files using the web interface of the notebook.
Any uploaded files or changes you make to them will be lost when the notebook instance is restarted.
If you want to save your work, you need to add persistent storage to the notebook.
To add persistent storage run:

bash
oc set volume dc/mynotebook --add \
    --type=pvc --claim-size=1Gi --claim-mode=ReadWriteOnce \
    --claim-name mynotebook-data --name data \
    --mount-path /home/jovyan

When you have deleted the notebook instance, if using a persistent volume, you will need to delete it in a separate step.

bash
oc delete pvc/mynotebook-data

Customizing the Configuration

If you want to set any custom configuration for the notebook, you can edit the config map created by the template.

bash
oc edit configmap/mynotebook-cfg

The data field of the config map contains Python code used as the jupyter_server_config.py file.

If you are using a persistent volume, you can also create a configuration file at:

lang-none
/home/jovyan/.jupyter/jupyter_server_config.py

This will be merged at the end of the configuration from the config map.

Because the configuration is Python code, ensure any indenting is correct.
Any errors in the configuration file will cause the notebook to fail when starting.

If the error is in the config map, edit it again to fix it and trigger a new deployment if necessary by running:

bash
oc rollout latest dc/mynotebook

If you make an error in the configuration file stored in the persistent volume, you will need to scale down the notebook, so it isn't running.

bash
oc scale dc/mynotebook --replicas 0

Then run:

bash
oc debug dc/mynotebook

to run the notebook in debug mode.
This will provide you with an interactive terminal session inside a running container, but the notebook will not have been started.
Edit the configuration file in the volume to fix any errors and exit the terminal session.

Start up the notebook again.

bash
oc scale dc/mynotebook --replicas 1

Changing the Notebook Password

The password for the notebook is supplied as a template parameter, or if not supplied will be automatically generated by the template.
It will be passed into the container through an environment variable.

If you want to change the password, you can do so by editing the environment variable on the deployment configuration.

bash
oc set env dc/mynotebook JUPYTER_NOTEBOOK_PASSWORD=mypassword

This will trigger a new deployment so ensure you have downloaded any work if not using a persistent volume.

If using a persistent volume, you could instead set up a password in the file /home/jovyan/.jupyter/jupyter_server_config.py as per guidelines in https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html.

Deploying from a Custom Image

If you want to deploy a custom variant of the Jupyter Project docker-stacks images, you can replace the image name with that of your own.
If the image is not stored on Quay.io, but some other public image registry, prefix the name of the image with the image registry host details.

If the image is in your OpenShift project, because you imported the image into OpenShift, or used the docker build strategy of OpenShift to build a derived custom image,
you can use the name of the image stream for the image name, including any image tag if necessary.

This can be illustrated by first importing an image into the OpenShift project.

bash
oc import-image quay.io/jupyter/datascience-notebook:latest --confirm

Then deploy it using the name of the image stream created.

bash
oc new-app --template jupyter-notebook \
    --param APPLICATION_NAME=mynotebook \
    --param NOTEBOOK_IMAGE=datascience-notebook \
    --param NOTEBOOK_PASSWORD=mypassword

Importing an image into OpenShift before deploying it means that when a notebook is started, the image need only be pulled from the internal OpenShift image registry rather than Quay.io for each deployment.
Because the images are so large, this can speed up deployments when the image hasn't previously been deployed to a node in the OpenShift cluster.


Overview (`examples/source-to-image/README.md`)

Custom Jupyter Notebook images

This example provides scripts for building custom Jupyter Notebook images containing notebooks, data files, and with Python packages required by the notebooks already installed.
The scripts provided work with the Source-to-Image tool, and you can create the images from the command line on your own computer.
Templates are also provided to enable running builds in OpenShift, as well as deploying the resulting image to OpenShift to make it available.

The build scripts, when used with the Source-to-Image tool, provide similar capabilities to repo2docker.
When builds are run under OpenShift with the supplied templates, it provides similar capabilities to mybinder.org,
but where notebook instances are deployed in your existing OpenShift project and JupyterHub is not required.

For separate examples of using JupyterHub with OpenShift, see the project:

Source-to-Image Project

Source-to-Image (S2I) is an open source project which provides a tool for creating container images.
It works by taking a base image, injecting additional source code or files into a running container created from the base image,
and running a builder script in the container to process the source code or files to prepare the new image.

Details on the S2I tool, and executable binaries for Linux, macOS and Windows, can be found on GitHub at:

The tool is standalone, and can be used on any system which provides a docker daemon for running containers.
To provide an end-to-end capability to build and deploy applications in containers, support for S2I is also integrated into container platforms such as OpenShift.

Getting Started with S2I

As an example of how S2I can be used to create a custom image with a bundled set of notebooks, run:

bash
s2i build \
    --scripts-url https://raw.githubusercontent.com/jupyter/docker-stacks/main/examples/source-to-image \
    --context-dir docs/source/examples/Notebook \
    https://github.com/jupyter/notebook \
    quay.io/jupyter/minimal-notebook:latest \
    notebook-examples

This example command will pull down the Git repository https://github.com/jupyter/notebook
and build the image notebook-examples using the files contained in the docs/source/examples/Notebook directory of that Git repository.
The base image which the files will be combined with is quay.io/jupyter/minimal-notebook:latest, but you can specify any of the Jupyter Project docker-stacks images as the base image.

The resulting image from running the command can be seen by running docker images command:

bash
docker images
# REPOSITORY         TAG     IMAGE ID      CREATED        SIZE
# notebook-examples  latest  f5899ed1241d  2 minutes ago  2.59GB

You can now run the image.

bash
docker run --rm -p 8888:8888 notebook-examples

Open your browser on the URL displayed, and you will find the notebooks from the Git repository and can work with them.

The S2I Builder Scripts

Normally when using S2I, the base image would be S2I enabled and contain the builder scripts needed to prepare the image and define how the application in the image should be run.
As the Jupyter Project docker-stacks images are not S2I enabled (although they could be),
in the above example the --scripts-url option has been used to specify that the example builder scripts contained in this directory of this Git repository should be used.

Using the --scripts-url option, the builder scripts can be hosted on any HTTP server,
or you could also use builder scripts local to your computer file using an appropriate file:// format URI argument to --scripts-url.

The builder scripts in this directory of this repository are assemble and run and are provided as examples of what can be done.
You can use the scripts as is, or create your own.

The supplied assemble script performs a few key steps.

The first steps copy files into the location they need to be when the image is run, from the directory where they are initially placed by the s2i command.

bash
cp -Rf /tmp/src/. "/home/${NB_USER}"

rm -rf /tmp/src

The next steps are:

bash
if [ -f "/home/${NB_USER}/environment.yml" ]; then
    mamba env update --name root --file "/home/${NB_USER}/environment.yml"
    mamba clean --all -f -y
else
    if [ -f "/home/${NB_USER}/requirements.txt" ]; then
        pip --no-cache-dir install -r "/home/${NB_USER}/requirements.txt"
    fi
fi

This determines whether a environment.yml or requirements.txt file exists with the files and if so, runs the appropriate package management tool to install any Python packages listed in those files.

This means that so long as a set of notebook files provides one of these files listing what Python packages they need,
those packages will be automatically installed into the image, so they are available when the image is run.

A final step is:

bash
fix-permissions "${CONDA_DIR}"
fix-permissions "/home/${NB_USER}"

This fixes up permissions on any new files created by the build.
This is necessary to ensure that when the image is run, you can still install additional files.
This is important for when an image is run in sudo mode, or it is hosted in a more secure container platform such as Kubernetes/OpenShift where it will be run as a set user ID that isn't known in advance.

As long as you preserve the first and last set of steps, you can do whatever you want in the assemble script to install packages, create files etc.
Do be aware though that S2I builds do not run as root and so you cannot install additional system packages.
If you need to install additional system packages, use a Dockerfile and normal docker build to first create a new custom base image from the Jupyter Project docker-stacks images,
with the extra system packages, and then use that image with the S2I build to combine your notebooks and have Python packages installed.

The run script in this directory is very simple and just runs the notebook application.

bash
exec start-notebook.py "$@"

Integration with OpenShift

The OpenShift platform provides integrated support for S2I type builds.
Templates are provided for using the S2I build mechanism with the scripts in this directory.
To load the templates run:

bash
oc create -f https://raw.githubusercontent.com/jupyter/docker-stacks/main/examples/source-to-image/templates.json

This will create the templates:

bash
jupyter-notebook-builder
jupyter-notebook-quickstart

The templates can be used from the OpenShift web console or command line.
This README is only going to explain deploying from the command line.

To use the OpenShift command line to build into an image, and deploy, the set of notebooks used above, run:

bash
oc new-app --template jupyter-notebook-quickstart \
    --param APPLICATION_NAME=notebook-examples \
    --param GIT_REPOSITORY_URL=https://github.com/jupyter/notebook \
    --param CONTEXT_DIR=docs/source/examples/Notebook \
    --param BUILDER_IMAGE=quay.io/jupyter/minimal-notebook:latest \
    --param NOTEBOOK_PASSWORD=mypassword

You can provide a password using the NOTEBOOK_PASSWORD parameter.
If you don't set that parameter, a password will be generated, with it being displayed by the oc new-app command.

Once the image has been built, it will be deployed.
To see the hostname for accessing the notebook, run oc get routes.

lang-none
NAME                HOST/PORT                                                       PATH SERVICES           PORT      TERMINATION    WILDCARD
notebook-examples   notebook-examples-jupyter.abcd.pro-us-east-1.openshiftapps.com       notebook-examples  8888-tcp  edge/Redirect  None

As the deployment will use a secure connection, the URL for accessing the notebook in this case would be https://notebook-examples-jupyter.abcd.pro-us-east-1.openshiftapps.com.

If you only want to build an image but not deploy it, you can use the jupyter-notebook-builder template.
You can then deploy it using the jupyter-notebook template provided with the openshift examples directory.

See the openshift examples directory for further information on customizing configuration for a Jupyter Notebook deployment and deleting a deployment.


Overview (`images/all-spark-notebook/README.md`)

Jupyter Notebook Python, R, Spark Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/base-notebook/README.md`)

Base Jupyter Notebook Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/datascience-notebook/README.md`)

Jupyter Notebook Data Science Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/docker-stacks-foundation/README.md`)

Foundation Jupyter Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/julia-notebook/README.md`)

Jupyter Notebook Julia Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/minimal-notebook/README.md`)

Minimal Jupyter Notebook Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/pyspark-notebook/README.md`)

Jupyter Notebook Python, Spark Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/pytorch-notebook/README.md`)

Jupyter Notebook PyTorch Deep Learning Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/r-notebook/README.md`)

Jupyter Notebook R Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/scipy-notebook/README.md`)

Jupyter Notebook Scientific Python Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Overview (`images/tensorflow-notebook/README.md`)

Jupyter Notebook TensorFlow Deep Learning Stack

GitHub Actions in the https://github.com/jupyter/docker-stacks project builds and pushes this image to the Registry.

Please visit the project documentation site for help to use and contribute to this image and others.


Index (`docs/index.rst`)

.. include:: ../README.md
:parser: myst_parser.sphinx_

Table of Contents

.. toctree::
:maxdepth: 2
:caption: User Guide

using/selecting
using/running
using/common
using/specifics
using/recipes
using/custom-images
using/troubleshooting
using/faq

.. toctree::
:maxdepth: 2
:caption: Contributor Guide

contributing/dev-setup
contributing/issues
contributing/features
contributing/tests
contributing/lint
contributing/recipes
contributing/stacks
contributing/packages

.. toctree::
:maxdepth: 2
:caption: Maintainer Guide

maintaining/new-images-and-packages-policy
maintaining/tagging
maintaining/tasks

.. toctree::
:maxdepth: 1
:caption: Changelog

changelog

.. toctree::
:maxdepth: 2
:caption: Getting Help

Issue Tracker on GitHub https://github.com/jupyter/docker-stacks/issues
Jupyter Discourse Forum https://discourse.jupyter.org
Jupyter Website https://jupyter.org


Requirements (`docs/requirements.txt`)

docutils==0.22.4
myst-parser==5.1.0
sphinx==9.1.0
sphinx-book-theme==1.4.0
sphinx-copybutton==0.5.2
sphinx-last-updated-by-git==0.3.8
sphinxcontrib-mermaid==2.1.1


Home (`wiki/Home.md`)

Jupyter Docker Stacks build manifests

Welcome!
Please see the documentation for help with
using, contributing to, and maintaining the Jupyter Docker stacks images.

Build History

This is an auto-generated index of information from the build system.
In this index, you can find image tags, links to commits, and build manifests that describe the image.
All the builds are grouped by year and then month.

Note: we only store the last 4500 manifest files because of GitHub limits.
That's why old manifest files might not be available.
If you want to clone this repo and access the Git history, use the following command: git clone [email protected]:{REPOSITORY}.wiki.git

In the tables below, each line represents:

  • YYYY-MM: link to a page with a list of images built
  • Builds: # of times build workflow finished
  • Images: # of single platform images pushed
  • Commits: # of commits made and a GitHub link

Contributing Dev Setup (`docs/contributing/dev-setup.md`)

Development Setup

This page covers everything you need to run the full CI-equivalent checks locally before opening a pull request.

Prerequisites

  • Docker
  • Python 3.12+
  • GNU Make
  • Git

One-time setup

bash
# Clone the repository (SSH, recommended)
git clone [email protected]:jupyter/docker-stacks.git
# or via HTTPS
git clone https://github.com/jupyter/docker-stacks.git

cd docker-stacks

# Install Python development dependencies
pip install -r requirements-dev.txt

# Install pre-commit hooks (runs linters automatically on git commit)
pre-commit install --install-hooks

Pre-PR checklist

Run these steps locally before pushing.

bash
# 1. Run all linters including mypy
pre-commit run --all-files --hook-stage manual

# 2. Build the image you changed
make build/<image-name>

# 3. Run tests for that image
make test/<image-name>
{note}
If the parent image isn't built locally, Docker pulls it from the registry.
This means changes to a parent image won't be reflected unless you also rebuild it locally.

Replace `` with the image you modified (e.g., docker-stacks-foundation, base-notebook, scipy-notebook).

{note}
`make test/<image-name>` runs the tests for that image plus every parent image's
tests against it (e.g., testing `scipy-notebook` also runs `base-notebook` and
`docker-stacks-foundation` test files against the `scipy-notebook` image). CI
additionally runs the same test set against every downstream image, so a change
in `docker-stacks-foundation` is verified across all images in CI even though
locally you'd only run `make test/docker-stacks-foundation`.

Common examples

bash
# Working on the foundation image (start scripts, logging, etc.)
pre-commit run --all-files --hook-stage manual
make build/docker-stacks-foundation
make test/docker-stacks-foundation

# Working on the base notebook image
pre-commit run --all-files --hook-stage manual
make build/docker-stacks-foundation
make build/base-notebook
make test/base-notebook

# Build and test every image (slow; mainly useful before opening a PR
# that changes the foundation or base image)
make build-all
make test-all

Contributing Features (`docs/contributing/features.md`)

New Features

Thank you for contributing to the Jupyter Docker Stacks!
We review pull requests for new features (e.g., new packages, new scripts, new flags)
to balance the value of the images to the Jupyter community with the cost of maintaining the images over time.

Suggesting a New Feature

Please follow the process below to suggest a new feature for inclusion in one of the core stacks:

  1. Open a GitHub feature request issue
    describing the feature you'd like to contribute.
  2. Discuss with the maintainers whether the addition makes sense
    in one of the core stacks,
    as a way to build a custom set of images,
    as a recipe in the documentation,
    as a community stack,
    or as something else entirely.

Selection Criteria

Roughly speaking, we evaluate new features based on the following criteria:

  • Usefulness to Jupyter users:
    Is the feature generally applicable across domains?
    Does it work with JupyterLab, Jupyter Notebook, JupyterHub, etc.?
  • Fit with the image purpose:
    Does the feature match the theme of the stack to which it will be added?
    Would it fit better in a new community stack?
  • The complexity of build/runtime configuration:
    How many lines of code does the feature require in one of the Dockerfiles or startup scripts?
    Does it require new scripts entirely?
    Do users need to adjust how they use the images?
  • Impact on image metrics:
    How many bytes does the feature and its dependencies add to the image(s)?
    How many minutes do they add to the build time?
  • Ability to support the addition:
    Can existing maintainers answer user questions and address future build issues?
    Are the contributors interested in helping with long-term maintenance?
    Can we write tests to ensure the feature continues to work over the years?

Submitting a Pull Request

If there's agreement that the feature belongs in one or more of the core stacks:

  1. Implement the feature in a local clone of the jupyter/docker-stacks project.

  2. Please, build the image locally before submitting a pull request.
    It shortens the debugging cycle by taking some load off GitHub Actions,
    which graciously provides free build services for open-source projects like this one.
    If you use make, call:

    bash
    make build/<somestack>
  3. Submit a pull request (PR) with your changes.

  4. Watch for GitHub to report a build success or failure for your PR on GitHub.

  5. Discuss changes with the maintainers and address any build issues.

{note}
If your changes cannot affect any image beyond `docker-stacks-foundation` and `base-notebook`,
you can add `[FAST_BUILD]` to the PR title to skip building the rest of the images and speed up CI.

Contributing Issues (`docs/contributing/issues.md`)

Project Issues

We appreciate you taking the time to report an issue you encountered while using the Jupyter Docker Stacks.
Please review the following guidelines when reporting your problem.

  • Please use GitHub's "Report a Vulnerability" button under Security > Advisories on the appropriate repo,
    e.g. report here for Jupyter Docker Stacks.
    You may also send an email to mailto:[email protected], but the GitHub reporting system is preferred.
  • If you think your problem is unique to the Jupyter Docker Stacks images,
    please search the jupyter/docker-stacks issue tracker
    to see if someone else has already reported the same problem.
    If not, please open a GitHub bug report issue
    and provide all the information requested in the issue template.
    Additionally, check the Troubleshooting Common Problems page in the documentation before submitting an issue.
  • If the issue you're seeing is with one of the open-source libraries included in the Docker images and is reproducible outside the images,
    please file a bug with the appropriate open-source project.
  • If you have a general question about how to use the Jupyter Docker Stacks in your environment,
    in conjunction with other tools, customizations, and so on,
    please post your question on the Jupyter Discourse site.

Contributing Lint (`docs/contributing/lint.md`)

Lint

To enforce some rules, linters are used in this project.
Linters can be run either during the development phase (by the developer) or the integration phase (by GitHub Actions).
To integrate and enforce this process in the project lifecycle, we are using git hooks through pre-commit.

Using pre-commit hooks

Pre-commit hook installation

pre-commit is a Python package that needs to be installed.
To achieve this, use the generic task to install all Python development dependencies.

sh
# Install all development dependencies for the project
pip install -r requirements-dev.txt
# It can also be installed directly
pip install "$(grep '^pre-commit==' requirements-dev.txt)"

Then the git hooks scripts configured for the project in .pre-commit-config.yaml need to be installed in the local git repository.

sh
pre-commit install

Run

Now, pre-commit (and so configured hooks) will run automatically on git commit on each changed file.
However, you can also run it against all files manually.

{note}
Hadolint pre-commit uses Docker to run, so `docker` should be running while executing this command.
sh
pre-commit run --all-files --hook-stage manual
{note}
We're running `pre-commit` with `--hook-stage manual`, because `pre-commit` is run on modified files only, which doesn't work well with `mypy --follow-imports error`.
More information can be found in [`.pre-commit-config.yaml` file](https://github.com/jupyter/docker-stacks/blob/main/.pre-commit-config.yaml)

Image Lint

To comply with Docker best practices,
we are using the Hadolint tool to analyze each Dockerfile.

Ignoring Rules

Sometimes it is necessary to ignore some rules.
The following rules are ignored by default for all images in the .hadolint.yaml file.

  • DL3006: We use a specific policy to manage image tags.
    • The docker-stacks-foundation FROM clause is fixed but based on an argument (ARG).
    • Building downstream images from (FROM) the latest is done on purpose.
  • DL3008: System packages are always updated (apt-get) to the latest version.
  • DL3013: We always install the latest packages using pip

The preferred way to ignore other rules is to flag them in the Dockerfile.
You can use a special comment directly above the Dockerfile instruction you want to make an exception for.
Ignore rule comments look like # hadolint ignore=DL3001,SC1081.
For example:

dockerfile
FROM ubuntu

# hadolint ignore=DL3003,SC1035
RUN cd /tmp && echo "hello!"

Contributing Packages (`docs/contributing/packages.md`)

Package Updates

Generally, we do not pin package versions in our Dockerfiles.
Dependency resolution is a difficult thing to do.
All this means that packages might have old versions.
Images are rebuilt weekly, so usually, packages receive updates quite frequently.

{note}
We pin major.minor version of Python, so this will stay the same even after invoking the `mamba update` command.

Outdated packages

To help identify packages that can be updated, you can use the following helper tool.
It will list all the outdated packages installed in the Dockerfile.
Dependencies are filtered to display only the requested packages.

bash
make check-outdated/base-notebook

# INFO     test_outdated:test_outdated.py:80 3/8 (38%) packages could be updated
# INFO     test_outdated:test_outdated.py:82
# Package     Current    Newest
# ----------  ---------  --------
# conda       4.7.12     4.8.2
# jupyterlab  1.2.5      2.0.0
# python      3.7.4      3.8.2

Contributing Recipes (`docs/contributing/recipes.md`)

New Recipes

We welcome contributions of recipes, which are short examples of using, configuring, or extending the Docker Stacks for inclusion in the documentation site.
Follow the process below to add a new recipe:

  1. Open the docs/using/recipes.md source file.
  2. Add a second-level Markdown heading naming your recipe at the bottom of the file (e.g., ## Slideshows with JupyterLab and RISE)
  3. Write the body of your recipe under the heading, including whatever command line, links, etc. you need.
  4. If you have a Dockerfile, please put it in a recipe_code subdirectory.
    This file will be built automatically by contributed-recipes workflow.
  5. Submit a pull request (PR) with your changes.
    Maintainers will respond and work with you to address any formatting or content issues.

Contributing Stacks (`docs/contributing/stacks.md`)

Community Stacks

We love to see the community create and share new Jupyter Docker images.
We've put together a cookiecutter project
and the documentation below to help you get started defining, building, and sharing your Jupyter environments in Docker.

Following these steps will:

  1. Set up a project on GitHub containing a Dockerfile based on any image we provide.
  2. Configure GitHub Actions to build and test your image when users submit pull requests to your repository.
  3. Configure a container registry (Docker Hub in this example) to host your images for others to use.
  4. Update the list of community stacks in this documentation to include your image.

This approach mirrors how we build and share the core stack images.
Feel free to follow it or pave your path using alternative services and build tools.

Creating a Project

First, install cookiecutter using pip or mamba:

bash
pip install cookiecutter  # or mamba install cookiecutter

Run the cookiecutter command pointing to the jupyter/cookiecutter-docker-stacks project on GitHub.

bash
cookiecutter https://github.com/jupyter/cookiecutter-docker-stacks.git

Enter a name for your new stack image.
This will serve as both the git repository name and the part of the Docker image name after the slash.

text
stack_name [my-jupyter-stack]:

Enter the user or organization name under which this stack will reside on Docker Hub.
You must have access to manage this Docker Hub organization to push images here.

text
stack_org [my-project]:

Select an image from the jupyter/docker-stacks project that will serve as the base for your new image.

text
stack_base_image [quay.io/jupyter/base-notebook]:

Enter a longer description of the stack for your README.

text
stack_description [my-jupyter-stack is a community-maintained Jupyter Docker Stack image]:

Create a GitHub repository to store your project.
Initialize your project as a Git repository and push it to GitHub.

bash
cd <stack_name you chose>

git init
git add .
git commit -m 'Seed repo'
git remote add origin <url from github>
git push -u origin main

Exploring GitHub Actions

  1. By default, the .github/workflows/docker.yaml file generated by the cookiecutter template will trigger the CI pipeline whenever you push to your main branch
    and when any Pull Requests are made to your repository.
    For more details on this configuration, visit the GitHub Actions documentation on triggers.

  2. Go to your repository and click on the Actions tab.
    From there, you can click on the workflows on the left-hand side of the screen.

    {note}
    The first run is expected to fail because we haven't yet added Docker credentials to push the image
  3. In the next screen, you will see information about the workflow run and duration.
    If you click the button with the workflow name again, you will see the logs for the workflow steps.

Configuring Docker Hub

{note}
Jupyter Docker Stacks are hosted on Quay.io, but in this example, we show you how to host your image on Docker Hub.

Now, configure Docker Hub so that your stack image can be pushed to the Docker Hub repository whenever
you merge a GitHub pull request to the main branch of your project.

  1. Visit https://hub.docker.com/ and log in.

  2. Create a new repository - make sure to use the correct namespace (account or organization).
    Enter the name of the image matching the one you entered when prompted with stack_name by the cookiecutter.

  3. Enter a description for your image.

  4. Click on your avatar in the top-right corner and select Account Settings.

  5. Click on Security and then click on the New Access Token button.

  6. Enter a meaningful name for your token and click on Generate

  7. Copy the personal access token displayed on the next screen.

    {note}
    **You will not be able to see it again after you close the pop-up window**.
  8. Head back to your GitHub repository and click on the Settings tab.

  9. Click on the Secrets and variables->Actions section and then on the New repository secret button in the top right corner.

  10. Create a DOCKERHUB_TOKEN secret and paste the Personal Access Token from Docker Hub in the value field.

  11. Now you're ready to go and you can restart a failed workflow.

Defining Your Image

Make edits to the Dockerfile in your project to add third-party libraries and configure Jupyter applications.
Refer to the Dockerfiles for the core stacks (e.g., jupyter/datascience-notebook)
to get a feel for what's possible and the best practices.

Submit pull requests
to your project repository on GitHub.
Ensure your image builds correctly on GitHub Actions before merging to the main branch.
After merging to the main branch, your image will be built and pushed to the Docker Hub automatically.

Sharing Your Image

Finally, if you'd like to add a link to your project to this documentation site, please do the following:

  1. Fork the jupyter/docker-stacks GitHub repository.
  2. Open the docs/using/selecting.md source file and locate the Community Stacks section in your fork.
  3. Add a table entry with a link to your project, a binder link, and a short description of what your Docker image contains.
  4. Submit a pull request (PR) with your changes.
    Maintainers will respond and work with you to address any formatting or content issues.

Contributing Tests (`docs/contributing/tests.md`)

Image Tests

We greatly appreciate Pull Requests that extend the automated tests that vet the basic functionality of the Docker images.

How the Tests Work

A GitHub Actions workflow
runs tests against pull requests submitted to the jupyter/docker-stacks repository.

We use the pytest module to run tests on the image.
conftest.py and pytest.ini in the tests folder define the environment in which tests are run.
Read pytest documentation.

The actual image-specific test files are located in folders like tests/by_image/<somestack>/ (e.g., tests/by_image/docker-stacks-foundation/, etc.).

{note}
If your test is located in `tests/by_image/<somestack>/`, it will be run against the `jupyter/<somestack>` image and against all the [images inherited from this image](../using/selecting.md#image-relationships).

Many tests make use of global pytest fixtures
defined in the conftest.py file.

Unit tests

You can add a unit test if you want to run a Python script in one of our images.
You should create a tests/by_image/<somestack>/units/ directory, if it doesn't already exist, and put your file there.
Files in this folder will be executed in the container when tests are run.
You can see a TensorFlow package example here.

Contributing New Tests

Please follow the process below to add new tests:

  1. Add your test code to one of the modules in the tests/by_image/<somestack>/ directory or create a new module.

  2. Build one or more images you intend to test and run the tests locally.
    If you use make, call:

    bash
    make build/<somestack>
    make test/<somestack>
  3. Submit a pull request
    (PR) with your changes.

  4. Watch for GitHub to report a build success or failure for your PR on GitHub.

  5. Discuss changes with the maintainers and address any issues running the tests on GitHub.


Maintaining New Images And Packages Policy (`docs/maintaining/new-images-and-packages-policy.md`)

New images / packages policy

There are many things we consider while adding new images and packages.

Here is a non-exhaustive list of things we do care about:

  1. Software health, details, and maintenance status
    • reasonable versioning is adopted, and the version is considered to be stable
    • has been around for several years
    • the package maintains documentation
    • a changelog is actively maintained
    • a release procedure with helpful automation is established
    • multiple people are involved in the maintenance of the project
    • provides a conda-forge package besides a pypi package, where both are kept up to date
    • supports both x86_64 and aarch64 architectures
  2. Installation consequences
    • GitHub Actions build time
    • Image sizes
    • All requirements should be installed as well
  3. Jupyter Docker Stacks image fit
    • new package or stack changes (or inherits from) the most suitable stack
  4. Software impact for users of docker-stacks images
    • How this image can help existing users, or maybe reduce the need to build new images
  5. Why it shouldn't just be a documented recipe
  6. Impact on security
    • Does the package open additional ports, or add new web endpoints, that could be exploited?

With all this in mind, we have a voting group, that consists of
@mathbunnyru,
@consideRatio,
@yuvipanda, and
@manics.

This voting group is responsible for accepting or declining new packages and stacks.
The change is accepted, if there are at least 2 positive votes.


Maintaining Tagging (`docs/maintaining/tagging.md`)

Tags and manifests

The main purpose of the source code in the tagging folder is to
properly write the tags file, the build history line, and the manifest for a single-platform image,
apply these tags, and then merge single-platform images into one multi-arch image.

What is a tag and a manifest

A tag is a label attached to a Docker image identifying specific attributes or versions.
For example, an image jupyter/base-notebook with Python 3.10.5 will have a full image name quay.io/jupyter/base-notebook:python-3.10.5.
These tags are pushed to our Quay.io registry.

A manifest is a description of important image attributes written in Markdown format.
For example, we dump all conda packages with their versions into the manifest.

Main principles

  • All images are organized in a hierarchical tree.
    More info on image relationships.
  • TaggerInterface and ManifestInterface are interfaces for functions to generate tags and manifest pieces by running commands in Docker containers.
  • Tags and manifests are reevaluated for each image in the hierarchy since values may change between parent and child images.
  • To tag an image and create its manifest and build history line, run make hook/<somestack> (e.g., make hook/base-notebook).

Utils

DockerRunner

DockerRunner is a helper class to easily run a docker container and execute commands inside this container:

{literalinclude} tagging_examples/docker_runner.py
:language: py
:lines: 3-

GitHelper

GitHelper methods are run in the current git repo and give the information about the last commit hash and commit message:

{literalinclude} tagging_examples/git_helper.py
:language: py
:lines: 3-

The prefix of the commit hash (namely, 12 characters) is used as an image tag to make it easy to inherit from a fixed version of a docker image.

Taggers and Manifests

Tagger

Tagger is a function that runs commands inside a docker container to calculate a tag for an image.

All the taggers follow TaggerInterface:

{literalinclude} ../../tagging/taggers/tagger_interface.py
:language: py
:start-at: TaggerInterface

So, the tagger(container) gets a docker container as an input and returns a tag.

For example:

{literalinclude} ../../tagging/taggers/sha.py
:language: py
:start-at: def
  • taggers/ subdirectory contains all taggers.
  • apps/write_tags_file.py, apps/apply_tags.py, and apps/merge_tags.py are Python executables used to write tags for an image, apply tags from a file, and create multi-arch images.

Manifest

All manifest functions except build_info_manifest follow ManifestInterface
and manifest(container) method returns a piece of the manifest.

{literalinclude} ../../tagging/manifests/manifest_interface.py
:language: py
:start-at: ManifestInterface

For example:

{literalinclude} ../../tagging/manifests/apt_packages.py
:language: py
:start-at: def

where:

  • quoted_output(container, cmd) simply runs the command inside a container using DockerRunner.exec_cmd and wraps it to triple quotes to create a valid markdown piece.
    It also adds the command which was run to the markdown piece.
  • manifests/ subdirectory contains all the manifests.
  • apps/write_manifest.py is a Python executable to create the build manifest and history line for an image.

Images Hierarchy

All images' dependencies on each other and what taggers and manifests are applicable to them are defined in hierarchy/images_hierarchy.py.

hierarchy/get_taggers.py and hierarchy/get_manifests.py define functions to get the taggers and manifests for a specific image.


Maintaining Tasks (`docs/maintaining/tasks.md`)

Maintainer Playbook

Merging Pull Requests

To build new images and publish them to the Registry, do the following:

  1. Make sure GitHub Actions status checks pass for the PR.

  2. Merge the PR.

  3. Monitor the merge commit GitHub Actions status.

    {note}
    GitHub Actions are pretty reliable, so please investigate if some error occurs.
    Building Docker images in PRs is the same as building them in the default branch.
    The only difference is that single-platform images are pushed to Registry and then tags are merged for `x86_64` and `aarch64`.
  4. Merging another PR while builds in the main branch are still running does not cancel them -
    runs in the main branch (and scheduled runs) are queued instead
    (GitHub keeps the latest pending run, replacing any previously queued one).
    Still, it is tidier to let the pending builds finish before merging another PR:
    this way, you will know which commit might have broken the build
    and also have the correct values for the moving tags (like the Python version).

`[FAST_BUILD]` pull requests

If a PR title contains [FAST_BUILD], the docker.yml workflow
only builds and tests the docker-stacks-foundation and base-notebook images and skips all the other image jobs.
This only affects pull_request events - pushes to the main branch and weekly scheduled builds always build all the images.

Keep in mind:

  • Skipped jobs still satisfy the required status checks.
    So, before merging such a PR, make sure the [FAST_BUILD] title is appropriate for the changes made
    (i.e. the changes cannot affect the skipped images).
  • Changing a PR title does not re-trigger the checks (the workflow does not run on the edited activity type).
    Push a new commit or re-run the workflow jobs to refresh the checks after retitling a PR.

Updating Python version

When a new Python version is released, we wait for:

  • all the dependencies to be available (as wheels or in conda-forge).
  • the first Python patch release for this version.
    This allows us to avoid many bugs that can happen in a major release.

Updating the Ubuntu Base Image

jupyter/docker-stacks-foundation is based on the LTS Ubuntu docker image.
We wait for the first point release of the new LTS Ubuntu before updating the version.
Other images are directly or indirectly inherited from jupyter/docker-stacks-foundation.
We rebuild our images automatically each week, which means they frequently receive updates.

When there's a security fix in the Ubuntu base image, it's a good idea to manually trigger the rebuild of images
from the GitHub Actions workflow UI.
Pushing the Run Workflow button will trigger this process.

Adding a New Core Image to the Registry

{note}
In general, we do not add new core images and ask contributors to either
create a [recipe](../using/recipes.md) or [community stack](../contributing/stacks.md).
We have a [policy](./new-images-and-packages-policy.md), which we consider when adding new images or new packages to existing images.

Take a look at an example of adding a new image.

When there's a new stack definition, check before merging the PR:

  1. PR includes an update to the stack overview diagram
    in the documentation.
  2. PR updates the Makefile.
  3. Necessary Tagger(s)/Manifest(s) are added for the new image
    in the tagging folder.
  4. A new repository is created in the jupyter organization in the Registry,
    and it's named after the stack folder in the git repo.
  5. Robot Write permission is added in the Repository Settings.

Adding a New Registry Owner Account

  1. Visit https://quay.io/organization/jupyter/teams/owners
  2. Add the maintainer's username.

Restarting a failed build

If an automated build in GitHub Actions fails, you can restart the failed jobs on GitHub.
You can also download the artifacts and investigate them for any issues.


Using Common (`docs/using/common.md`)

Common Features

Except for jupyter/docker-stacks-foundation, a container launched from any Jupyter Docker Stacks image runs a Jupyter Server with the JupyterLab frontend.
The container does so by executing a start-notebook.py script.
This script configures the internal container environment and then runs jupyter lab, passing any command-line arguments received.

This page describes the options supported by the startup script and how to bypass it to run alternative commands.

Jupyter Server Options

You can pass Jupyter Server options to the start-notebook.py script when launching the container.

  1. For example, to secure the Jupyter Server with a custom password
    hashed using jupyter_server.auth.passwd() instead of the default token,
    you can run the following (this hash was generated for the my-password password):

    bash
    docker run -it --rm -p 8888:8888 quay.io/jupyter/base-notebook \
        start-notebook.py --PasswordIdentityProvider.hashed_password='argon2:$argon2id$v=19$m=10240,t=10,p=8$JdAN3fe9J45NvK/EPuGCvA$O/tbxglbwRpOFuBNTYrymAEH6370Q2z+eS1eF4GM6Do'
  2. To set the base URL of the Jupyter Server, you can run the following:

    bash
    docker run -it --rm -p 8888:8888 quay.io/jupyter/base-notebook \
        start-notebook.py --ServerApp.base_url=/customized/url/prefix/

Docker Options

You may instruct the start-notebook.py script to customize the container environment before launching the Server.
You do so by passing arguments to the docker run command.

  • -e NB_USER= - The desired username and associated home folder.
    The default value is jovyan.
    Setting NB_USER redefines the jovyan default user and ensures that the desired user has the correct file permissions
    for the new home directory created at /home/.
    For this option to take effect, you must run the container with --user root.
    Additionally, set -e CHOWN_HOME=yes if you also change NB_UID/NB_GID or mount the user's home directory from the host.
    If the working directory was under /home/jovyan, the startup script automatically updates it to the new /home/ location.

    Example usage:

    bash
    docker run -it --rm \
        -p 8888:8888 \
        --user root \
        -e NB_USER="my-username" \
        -e CHOWN_HOME=yes \
        quay.io/jupyter/base-notebook
    {note}
    If you set `NB_USER` to `root`, you must also set `NB_UID=0` and `NB_GID=0`;
    the `root` home dir will then be set to `/home/root`.
    See discussion [here](https://github.com/jupyter/docker-stacks/issues/2042).
  • -e NB_UID=<numeric uid> - Instructs the startup script to switch the numeric user ID of ${NB_USER} to the given value.
    The default value is 1000.
    This feature is useful when mounting host volumes with specific owner permissions.
    You must run the container with --user root for this option to take effect.
    (The start.sh script will run the container command as ${NB_USER} via sudo after adjusting the user ID.)
    Instead, you might consider using the modern Docker-native options --user and
    --group-add - see the last bullet in this section for more details.

  • -e NB_GID=<numeric gid> - Instructs the startup script to change the primary group of ${NB_USER} to ${NB_GID}
    (the new group is added with a name of ${NB_GROUP} if it is defined. Otherwise, the group is named ${NB_USER}).
    This feature is useful when mounting host volumes with specific group permissions.
    You must run the container with --user root for this option to take effect.
    (The start.sh script will run the container command as ${NB_USER} via sudo after adjusting the group ID.)
    Instead, you might consider using modern Docker options --user and --group-add - see the last bullet in this section for more details.
    The user is added to the supplemental group users (gid 100) to grant write access to the home directory and /opt/conda.
    If you override the user/group logic, ensure the user stays in the group users if you want them to be able to modify files in the image.

  • -e NB_GROUP=<name> - The name used for ${NB_GID}, which defaults to ${NB_USER}.
    This group name is only used if ${NB_GID} is specified, and is completely optional: it only has a cosmetic effect.

  • --user 5000 --group-add users - Launches the container with a specific user ID and adds that user to the users group so that it can modify files in the default home directory and /opt/conda.
    You can use these arguments as alternatives to setting ${NB_UID} and ${NB_GID}.

Permission-specific configurations

  • -e NB_UMASK= - Configures Jupyter to use a umask value different from the default 022.
    For example, if setting umask to 002, new files will be readable and writable by group members instead of the owner only.
    Check this Wikipedia article for an in-depth description of umask and suitable values for multiple needs.
    While the default umask value should be sufficient for most use cases, you can set the NB_UMASK value to fit your requirements.

    {note}
    When `NB_UMASK` is set, it only applies to the Jupyter process itself -
    you cannot use it to set a `umask` for additional files created during `run-hooks.sh`.
    For example, via `pip` or `conda`.
    If you need to set a `umask` for these, you **must** set the `umask` value for each command.
  • -e CHOWN_HOME=yes - Instructs the startup script to change the ${NB_USER} home directory owner and group to the current value of ${NB_UID} and ${NB_GID}.
    This change will take effect even if the user home directory is mounted from the host using -v as described below.
    The change is not applied recursively by default.
    You can modify the chown behavior by setting CHOWN_HOME_OPTS (e.g., -e CHOWN_HOME_OPTS='-R').

  • -e CHOWN_EXTRA="<some dir>,<some other dir>" - Instructs the startup script to change the owner and group of each comma-separated container directory to the current value of ${NB_UID} and ${NB_GID}.
    The change is not applied recursively by default.
    You can modify the chown behavior by setting CHOWN_EXTRA_OPTS (e.g., -e CHOWN_EXTRA_OPTS='-R').

  • -e GRANT_SUDO=yes - Instructs the startup script to grant the NB_USER user passwordless sudo capability.
    You do not need this option to allow the user to conda or pip install additional packages.
    This option is helpful for cases when you wish to give ${NB_USER} the ability to install OS packages with apt or modify other root-owned files in the container.
    You must run the container with --user root for this option to take effect.
    (The start.sh script will run the container command as ${NB_USER} via sudo after adding ${NB_USER} to sudoers.)
    You should only enable sudo if you trust the user or if the container runs on an isolated host.

Additional runtime configurations

  • -e GEN_CERT=yes - Instructs the startup script to generate a self-signed SSL certificate.
    Configures Jupyter Server to use it to accept encrypted HTTPS connections.
  • -e DOCKER_STACKS_JUPYTER_CMD=<jupyter command> - Instructs the startup script to run jupyter ${DOCKER_STACKS_JUPYTER_CMD} instead of the default jupyter lab command.
    See Switching back to the classic notebook or using a different startup command for available options.
    This setting is helpful in container orchestration environments where setting environment variables is more straightforward than changing command line parameters.
  • -e RESTARTABLE=yes - Runs Jupyter in a loop so that quitting Jupyter does not cause the container to exit.
    This may be useful when installing extensions that require restarting Jupyter.
  • -v /some/host/folder/for/work:/home/jovyan/work - Mounts a host machine directory as a folder in the container.
    This configuration is useful for preserving notebooks and other work even after the container has been destroyed.
    You must grant the within-container notebook user or group (NB_UID or NB_GID) write access to the host directory (e.g., sudo chown 1000 /some/host/folder/for/work).
  • -e JUPYTER_ENV_VARS_TO_UNSET=ADMIN_SECRET_1,ADMIN_SECRET_2 - Unsets specified environment variables in the default startup script.
    The variables are unset after the hooks have been executed but before the command provided to the startup script runs.
  • -e NOTEBOOK_ARGS="--log-level='DEBUG' --dev-mode" - Adds custom options to the jupyter command.
    This way, the user could use any option supported by the jupyter subcommand.
  • -e JUPYTER_PORT=8117 - Changes the port in the container that Jupyter is using to the value of the ${JUPYTER_PORT} environment variable.
    This may be useful if you run multiple instances of Jupyter in swarm mode and want to use a different port for each instance.

Startup Hooks

You can further customize the container environment by adding shell scripts (*.sh) to be sourced
or executables (chmod +x) to be run to the paths below:

  • /usr/local/bin/start-notebook.d/ - handled before any of the standard options noted above is applied
  • /usr/local/bin/before-notebook.d/ - handled after all the standard options noted above are applied
    and ran right before the Server launches

Look at the run-hooks.sh script and how it's used in the start.sh
script for execution details.

SSL Certificates

You may mount an SSL key and certificate file into a container and configure the Jupyter Server to use them to accept HTTPS connections.
For example, to mount a host folder containing a notebook.key and notebook.crt and use them, you might run the following:

bash
docker run -it --rm -p 8888:8888 \
    -v /some/host/folder:/etc/ssl/notebook \
    quay.io/jupyter/base-notebook \
    start-notebook.py \
    --ServerApp.keyfile=/etc/ssl/notebook/notebook.key \
    --ServerApp.certfile=/etc/ssl/notebook/notebook.crt

Alternatively, you may mount a single PEM file containing both the key and certificate.
For example:

bash
docker run -it --rm -p 8888:8888 \
    -v /some/host/folder/notebook.pem:/etc/ssl/notebook.pem \
    quay.io/jupyter/base-notebook \
    start-notebook.py \
    --ServerApp.certfile=/etc/ssl/notebook.pem

In either case, Jupyter Server expects the key and certificate to be a base64 encoded text file.
The certificate file or PEM may contain one or more certificates (e.g., server, intermediate, and root).

For additional information about using SSL, see the following:

Alternative Commands

Switching back to the classic notebook or using a different startup command

JupyterLab, built on top of Jupyter Server, is now the default for all the images of the stack.
However, switching back to the classic notebook or using a different startup command is still possible.
You can achieve this by setting the environment variable DOCKER_STACKS_JUPYTER_CMD at container startup.
The table below shows some options.
Since Jupyter Notebook v7, jupyter-server is used as a backend.

DOCKER_STACKS_JUPYTER_CMD Frontend
lab (default) JupyterLab
notebook Jupyter Notebook
nbclassic NbClassic
server None
retro* RetroLab
{note}
- Changing frontend for **JupyterHub singleuser image** is described in [JupyterHub docs](https://jupyterhub.readthedocs.io/en/latest/howto/configuration/config-user-env.html#switching-back-to-the-classic-notebook).
- \* `retro` is not installed at this time, but it could be the case in the future or in a community stack.
- Any other valid `jupyter` subcommand that starts the Jupyter Application can be used.

Example:

bash
# Run Jupyter Server with the Jupyter Notebook frontend
docker run -it --rm \
    -p 8888:8888 \
    -e DOCKER_STACKS_JUPYTER_CMD=notebook \
    quay.io/jupyter/base-notebook

# Executing the command: start-notebook.py
# Executing: jupyter notebook
# ...

# Use Jupyter NBClassic frontend
docker run -it --rm \
    -p 8888:8888 \
    -e DOCKER_STACKS_JUPYTER_CMD=nbclassic \
    quay.io/jupyter/base-notebook

# Executing the command: start-notebook.py
# Executing: jupyter nbclassic
# ...

`start.sh`

Most of the configuration options in the start-notebook.py script are handled by an internal start.sh script that automatically runs before the command provided to the container
(it's set as the container entrypoint).
This allows you to specify an arbitrary command that takes advantage of all these features.
For example, to run the text-based ipython console in a container, do the following:

bash
docker run -it --rm quay.io/jupyter/base-notebook ipython

This script is handy when you derive a new Dockerfile from this image and install additional Jupyter applications with subcommands like jupyter console, jupyter kernelgateway, etc.

Conda Environments

The default Python 3.x Conda environment resides in /opt/conda.
The /opt/conda/bin directory is part of the default jovyan user's ${PATH}.
That directory is also searched for binaries when run using sudo (sudo my_binary will search for my_binary in /opt/conda/bin/).

The jovyan user has full read/write access to the /opt/conda directory.
You can use either mamba, pip, or conda (mamba is recommended) to install new packages without any additional permissions.

bash
# install a package into the default (python 3.x) environment and clean it up after
# the installation
mamba install --yes some-package && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

pip install --no-cache-dir some-package && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

conda install --yes some-package && \
    conda clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

Using Alternative Channels

Conda is configured by default to use only the conda-forge channel.
However, you can use alternative channels, either one-shot by overwriting the default channel in the installation command or by configuring mamba to use different channels.
The examples below show how to use the anaconda default channels instead of conda-forge to install packages.

bash
# using defaults channels to install a package
mamba install --channel defaults humanize

# configure conda to add default channels at the top of the list
conda config --system --prepend channels defaults

# install a package
mamba install --yes humanize && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

Using Custom Images (`docs/using/custom-images.md`)

Building a custom set of images

This section describes how to build a custom set of images.
It may be helpful if you need to change the Ubuntu or Python version, or to make a significant change to the build process itself.

This project only builds one set of images at a time.
If you want to use older images, take a look here.

Automating your build using template cookiecutter project

If you wish to build your own image on top of one of our images and automate your build process,
please, take a look at cookiecutter template.

Custom arguments

Our repository provides several customization points:

  • ROOT_IMAGE (docker argument) - the parent image for docker-stacks-foundation image
  • PYTHON_VERSION (docker argument) - the Python version to install in docker-stacks-foundation image
  • REGISTRY, OWNER, BASE_IMAGE (docker arguments) - they allow you to specify the parent image for all the other images
  • REGISTRY, OWNER (part of env in some GitHub workflows) - these allow you to properly tag and refer to images during the following steps:

These customization points can't be changed during runtime.
Read more about Docker build arguments and GitHub environment variables for a single workflow.

Building stack images with custom arguments

A selection of prebuilt images is available from Quay.io.
However, it's impossible to cater to everybody's needs.
For extensive customization with an automated build pipeline,
you may wish to create a community-maintained stack.
However, for minor customizations, this may be overkill.
For example, you may wish to use the same Jupyter stacks but built on a different base image,
or built with a different Python version.

To achieve this you can use Docker Bake
to build the stacks locally with custom arguments.

{note}
Custom arguments may result in build errors due to incompatibility.
If so your use-case may require a fully customized stack.

As a basic example, suppose you want to build a custom image based on the minimal-notebook image using Python 3.12,
and you have a Dockerfile like:

{code-block} dockerfile
:caption: Dockerfile

ARG BASE_IMAGE=minimal-notebook
FROM $BASE_IMAGE
...

Then include the file below in your project:

{literalinclude} recipe_code/docker-bake.custom-python.hcl
:force:
:language: hcl
:caption: docker-bake.hcl

To build this stack, in the same directory run:

bash
docker buildx bake

Docker Bake then determines the correct build order from the contexts parameters
and builds the stack as requested.

This image can then be run the same way as any other image provided by this project, for example:

bash
docker run -it --rm -p 8888:8888 custom-jupyter

or referenced in a Docker Compose file.

Forking our repository

If for some reason, you need to change more things in our images, feel free to fork it and change it any way you want.
If your customization is easy to backport to the main repo and might be helpful for other users, feel free to create a PR.

It is almost always a great idea to keep your diff as small as possible and to merge/rebase the latest version of our repo in your project.


Using Faq (`docs/using/faq.md`)

Frequently Asked Questions (FAQ)

How to persist user data

There are two types of data you might want to persist.

  1. If you want to persist your environment (i.e. packages installed by mamba, conda, pip, apt-get, and so on),
    then you should create an inherited image and install packages only once while building your Dockerfile.
    Take a look at an example of using mamba and pip in a child image.

    {note}
    If you install a package inside a running container (for example you run `pip install <package>` in a terminal),
    it won't be preserved when you next run your image.
    To make it work, install this package in your inherited image and rerun the `docker build` command.
  2. If you want to persist user data (files created by you, like Python scripts, notebooks, text files, and so on),
    then you should use a
    Docker bind mount or
    Docker Volume.
    You can find an example of using a bind mount here.
    There is also a mount troubleshooting section if you experience any issues.

Why we don't add your favorite package

We have lots of users with different packages they want to use.
Adding them all is impossible, so we have several images to choose from.
Choose the image that is closest to your needs, and feel free to add your package on top of our images.

Who is `jovyan`

As described in this issue comment:

text
JoΒ·vyΒ·an
/ˈjōvΔ“Ι™n/
noun – an inhabitant of Jupyter

Jovyan is often a special term used to describe members of the Jupyter community.
It is also used as the user ID in the Jupyter Docker stacks or referenced in conversations.
You can find more information on the Jupyter Community documentation.

How to give root permissions to the user

We have a recipe for enabling root permissions.


Using Recipes (`docs/using/recipes.md`)

Contributed Recipes

Users sometimes share interesting ways of using the Jupyter Docker Stacks.
We encourage users to contribute these recipes to the documentation in case they prove helpful to other community members by submitting a pull request to docs/using/recipes.md.
The sections below capture this knowledge.

All the recipes here assume you would like to use an image built by this project and install some things on top of it.
If you would like to build a custom set of images, take a look at the docs.

Using `sudo` within a container

Password authentication is disabled for the NB_USER (e.g., jovyan).
We made this choice to avoid distributing images with a weak default password that users might will forget to change before running a container on a publicly accessible host.

You can grant the within-container NB_USER passwordless sudo access by adding --user root and -e GRANT_SUDO=yes to your Docker command line or appropriate container orchestrator config.

For example:

bash
docker run -it --rm \
    --user root \
    -e GRANT_SUDO=yes \
    quay.io/jupyter/base-notebook

You should only enable sudo if you trust the user and/or if the container is running on an isolated host.
See Docker security documentation for more information about running containers as root.

Create a new Dockerfile like the one shown below.
To use a requirements.txt file, first, create your requirements.txt file with the listing of packages desired.

{literalinclude} recipe_code/mamba_install.dockerfile
:language: docker

pip usage is similar:

{literalinclude} recipe_code/pip_install.dockerfile
:language: docker

Then build a new image.

bash
docker build --rm --tag my-custom-image .

You can then run the image as follows:

bash
docker run -it --rm \
    -p 8888:8888 \
    my-custom-image

Add a custom conda environment and Jupyter kernel

The default version of Python that ships with the image may not be the version you want.
The instructions below permit adding a conda environment with a different Python version and making it accessible to Jupyter.
You may also use older images like quay.io/jupyter/base-notebook:python-3.11.
We also maintain a full build history.

{literalinclude} recipe_code/custom_environment.dockerfile
:language: docker

Dask JupyterLab Extension

Dask JupyterLab Extension provides a JupyterLab extension to manage Dask clusters, as well as embed Dask's dashboard plots directly into JupyterLab panes.
Create the Dockerfile as:

{literalinclude} recipe_code/dask_jupyterlab.dockerfile
:language: docker

And build the image as:

bash
docker build --rm --tag my-custom-image .

Once built, run using the command:

bash
docker run -it --rm \
    -p 8888:8888 \
    -p 8787:8787 \
    my-custom-image

Let's Encrypt a Server

{warning}
This recipe is not tested and might be broken.

See the README for basic automation here
https://github.com/jupyter/docker-stacks/tree/main/examples/make-deploy
which includes steps for requesting and renewing a Let's Encrypt certificate.

Ref: https://github.com/jupyter/docker-stacks/issues/78

Slideshows with JupyterLab and RISE

RISE: "Live" Reveal.js JupyterLab Slideshow Extension.

{note}
We're providing the recipe to install the JupyterLab extension.
You can find the original Jupyter Notebook extension [here](https://github.com/damianavila/RISE)
{literalinclude} recipe_code/rise_jupyterlab.dockerfile
:language: docker

xgboost

{literalinclude} recipe_code/xgboost.dockerfile
:language: docker

JAX with CUDA

JAX GPU support via the bundled NVIDIA CUDA wheels.
Run with --gpus all and a host NVIDIA driver new enough for CUDA 13.
For CUDA 12 instead, swap jax[cuda13] for jax[cuda12].

{literalinclude} recipe_code/jax_cuda.dockerfile
:language: docker

Running behind an nginx proxy

{warning}
This recipe is not tested and might be broken.

Sometimes it is helpful to run the Jupyter instance behind an nginx proxy, for example:

  • you would prefer to access the notebook at a server URL with a path
    (https://example.com/jupyter) rather than a port (https://example.com:8888)
  • you may have many services in addition to Jupyter running on the same server
    and want nginx to help improve server performance in managing the connections

Here is a quick example of NGINX configuration to get started.
You'll need a server, a .crt, and a .key file for your server, and docker & docker-compose installed.
Then download the files at that gist and run docker-compose up to test it out.
Customize the nginx.conf file to set the desired paths and add other services.

Host volume mounts and notebook errors

If you are mounting a host directory as /home/jovyan/work in your container,
and you receive permission errors or connection errors when you create a notebook,
be sure that the jovyan user (UID=1000 by default) has read/write access to the directory on the host.
Alternatively, specify the UID of the jovyan user on container startup using the -e NB_UID option
described in the Common Features, Docker Options section

Ref: https://github.com/jupyter/docker-stacks/issues/199

Manpage installation

Most images, including our Ubuntu base image, ship without manpages installed to save space.
You can use the following Dockerfile to inherit from one of our images to enable manpages:

{literalinclude} recipe_code/manpage_install.dockerfile
:language: docker

Adding the documentation on top of the existing image wastes a lot of space
and requires reinstalling every system package,
which can take additional time and bandwidth.
Enabling manpages in the base Ubuntu layer prevents this image bloat.
To achieve this, use the previous Dockerfile's commands with the original ubuntu image as your base image:

dockerfile
FROM ubuntu:26.04

Be sure to check the current base image in jupyter/docker-stacks-foundation before building.

JupyterHub

We also have contributed recipes for using JupyterHub.

Use JupyterHub's DockerSpawner

You can find an example of using DockerSpawner.

Containers with a specific version of JupyterHub

The version of jupyterhub in your image should match the
version of JupyterHub itself.
To use a specific version of JupyterHub, do the following:

{literalinclude} recipe_code/jupyterhub_version.dockerfile
:language: docker

Spark

A few suggestions have been made regarding using Docker Stacks with Spark.

Using PySpark with AWS S3

{warning}
This recipe is not tested and might be broken.

Using Spark session for Hadoop 2.7.3

python
import os

# To figure out what version of Hadoop, run:
# ls /usr/local/spark/jars/hadoop*
os.environ["PYSPARK_SUBMIT_ARGS"] = (
    '--packages "org.apache.hadoop:hadoop-aws:2.7.3" pyspark-shell'
)

import pyspark

myAccessKey = input()
mySecretKey = input()

spark = (
    pyspark.sql.SparkSession.builder.master("local[*]")
    .config("spark.hadoop.fs.s3a.access.key", myAccessKey)
    .config("spark.hadoop.fs.s3a.secret.key", mySecretKey)
    .getOrCreate()
)

df = spark.read.parquet("s3://myBucket/myKey")

Using Spark context for Hadoop 2.6.0

python
import os

os.environ["PYSPARK_SUBMIT_ARGS"] = (
    "--packages com.amazonaws:aws-java-sdk:1.10.34,org.apache.hadoop:hadoop-aws:2.6.0 pyspark-shell"
)

import pyspark

sc = pyspark.SparkContext("local[*]")

from pyspark.sql import SQLContext

sqlContext = SQLContext(sc)

hadoopConf = sc._jsc.hadoopConfiguration()
myAccessKey = input()
mySecretKey = input()
hadoopConf.set("fs.s3.impl", "org.apache.hadoop.fs.s3native.NativeS3FileSystem")
hadoopConf.set("fs.s3.awsAccessKeyId", myAccessKey)
hadoopConf.set("fs.s3.awsSecretAccessKey", mySecretKey)

df = sqlContext.read.parquet("s3://myBucket/myKey")

Ref: https://github.com/jupyter/docker-stacks/issues/127

Using Local Spark JARs

{warning}
This recipe is not tested and might be broken.
python
import os

os.environ["PYSPARK_SUBMIT_ARGS"] = (
    "--jars /home/jovyan/spark-streaming-kafka-assembly_2.10-1.6.1.jar pyspark-shell"
)
import pyspark
from pyspark.streaming.kafka import KafkaUtils
from pyspark.streaming import StreamingContext

sc = pyspark.SparkContext()
ssc = StreamingContext(sc, 1)
broker = "<my_broker_ip>"
directKafkaStream = KafkaUtils.createDirectStream(
    ssc, ["test1"], {"metadata.broker.list": broker}
)
directKafkaStream.pprint()
ssc.start()

Ref: https://github.com/jupyter/docker-stacks/issues/154

Using spark-packages.org

{warning}
This recipe is not tested and might be broken.

If you'd like to use packages from spark-packages, see
https://gist.github.com/parente/c95fdaba5a9a066efaab
for an example of how to specify the package identifier in the environment before creating a
SparkContext.

Ref: https://github.com/jupyter/docker-stacks/issues/43

Use jupyter/all-spark-notebooks with an existing Spark/YARN cluster

{warning}
This recipe is not tested and might be broken.
text
/* Detailed source-code truncated for AI context efficiency. */

Credit: britishbadger from docker-stacks/issues/369

Run Server inside an already secured environment (i.e., with no token)

The default security is very good.
There are use cases, encouraged by containers, where the Jupyter container and the system it runs within lie inside the security boundary.
It is convenient to launch the server without a password or token in these use cases.
In this case, you should use the start-notebook.py script to launch the server with no token:

For JupyterLab:

bash
docker run -it --rm \
    quay.io/jupyter/base-notebook \
    start-notebook.py --IdentityProvider.token=''

For Jupyter Notebook:

bash
docker run -it --rm \
    -e DOCKER_STACKS_JUPYTER_CMD=notebook \
    quay.io/jupyter/base-notebook \
    start-notebook.py --IdentityProvider.token=''

Enable nbclassic-extension spellchecker for markdown (or any other nbclassic-extension)

{note}
This recipe only works for NBClassic with Jupyter Notebook < 7.
It is recommended to use [jupyterlab-spellchecker](https://github.com/jupyterlab-contrib/spellchecker) in modern environments.
{literalinclude} recipe_code/spellcheck_notebook_v6.dockerfile
:language: docker

Enable Delta Lake in Spark notebooks

{warning}
This recipe is not tested and might be broken.

Please note that the Delta Lake packages are only available for Spark version > 3.0.
By adding the properties to spark-defaults.conf, the user no longer needs to enable Delta support in each notebook.

dockerfile
FROM quay.io/jupyter/pyspark-notebook

RUN mamba install --yes 'delta-spark' && \
    mamba clean --all -f -y && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"

USER root

RUN cat <<EOF >> "${SPARK_HOME}/conf/spark-defaults.conf"
spark.sql.extensions io.delta.sql.DeltaSparkSessionExtension
spark.sql.catalog.spark_catalog org.apache.spark.sql.delta.catalog.DeltaCatalog
EOF

USER ${NB_UID}

# Trigger download of delta lake files
RUN python <<EOF
from pyspark.sql import SparkSession
from delta import *
spark = configure_spark_with_delta_pip(SparkSession.builder).getOrCreate()
EOF

Add Custom Fonts in Scipy notebook

{warning}
This recipe is not tested and might be broken.

The example below is a Dockerfile to load Source Han Sans with normal weight, usually used for the web.

dockerfile
FROM quay.io/jupyter/scipy-notebook

RUN PYV=$(ls "${CONDA_DIR}/lib" | grep ^python) && \
    MPL_DATA="${CONDA_DIR}/lib/${PYV}/site-packages/matplotlib/mpl-data" && \
    wget --progress=dot:giga -P "${MPL_DATA}/fonts/ttf/" https://mirrors.cloud.tencent.com/adobe-fonts/source-han-sans/SubsetOTF/CN/SourceHanSansCN-Normal.otf && \
    sed -i 's/#font.family/font.family/g' "${MPL_DATA}/matplotlibrc" && \
    sed -i 's/#font.sans-serif:/font.sans-serif: Source Han Sans CN,/g' "${MPL_DATA}/matplotlibrc" && \
    sed -i 's/#axes.unicode_minus: True/axes.unicode_minus: False/g' "${MPL_DATA}/matplotlibrc" && \
    rm -rf "/home/${NB_USER}/.cache/matplotlib" && \
    python -c 'import matplotlib.font_manager;print("font loaded: ",("Source Han Sans CN" in [f.name for f in matplotlib.font_manager.fontManager.ttflist]))'

Enable clipboard in pandas on Linux systems

{warning}
This recipe is not tested and might be broken.
{admonition} additional notes
This solution works on Linux host systems.
    It is not required on Windows and won't work on macOS.

To enable the pandas.read_clipboard() functionality, you need to have xclip installed
(installed in minimal-notebook and all the inherited images)
and add these options when running docker: -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix, i.e.:

bash
docker run -it --rm \
    -e DISPLAY \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    quay.io/jupyter/minimal-notebook

Install ijavascript kernel in your image

The example below is a Dockerfile to install the IJavascript kernel.

{literalinclude} recipe_code/ijavascript.dockerfile
:language: docker

Add Microsoft SQL Server ODBC driver

The following recipe demonstrates how to add functionality to read from and write to an instance of Microsoft SQL server in your notebook.

{literalinclude} recipe_code/microsoft_odbc.dockerfile
:language: docker

You can now use pyodbc and sqlalchemy to interact with the database.

Pre-built images are hosted in the Realiserad/jupyter-docker-mssql repository.

Add Oracle SQL Instant client, SQL\*Plus, and other tools (Version 23.x)

{note}
This recipe only works for x86_64 architecture.

The following recipe demonstrates how to add functionality to connect to an Oracle Database using Oracle Instant Client
in your notebook.
This recipe installs version 23.6.0.24.10.

Nonetheless, go to the Oracle Instant Client Download page for the complete list of versions available.
You may need to perform different steps for older versions;
they may be explained in the "Installation instructions" section of the Downloads page.

{literalinclude} recipe_code/oracledb.dockerfile
:language: docker

Running Jupyter Docker Stacks with Singularity

You can also start Jupyter Docker Stacks containers using Singularity instead of Docker. For example:

bash
singularity run --bind "${PWD}:/home/${USER}/work" --containall docker://quay.io/jupyter/datascience-notebook:2026-07-28
  • --bind "${PWD}:/home/${USER}/work" mounts your current working directory into the container at /home/$USER/work.
    When running the image with Singularity, the container uses your host username inside the container.
    Therefore, the bind target is /home/${USER}/work instead of the usual /home/jovyan/work.

  • --containall starts the container in a fully isolated environment, ignoring most of the host's filesystem and environment except for explicitly bound paths.
    By default, Singularity would bind your home directory automatically.
    If you have Python packages installed there, this may cause conflicts with packages inside the container.
    Using --containall avoids such interference.


Using Running (`docs/using/running.md`)

Running a Container

Using one of the Jupyter Docker Stacks requires two choices:

  1. Which Docker image you wish to use
  2. How you wish to start Docker containers from that image

This section provides details about the second.

Using the Docker CLI

You can launch a local Docker container from the Jupyter Docker Stacks using the Docker command-line interface.
There are numerous ways to configure containers using CLI.
The following are some common patterns.

Example 1

This command pulls the jupyter/scipy-notebook image tagged 2026-07-28 from Quay.io if it is not already present on the local host.
It then starts a container running a Jupyter Server with the JupyterLab frontend and exposes the server on host port 8888.
The server logs appear in the terminal and include a URL to the server.

bash
docker run -it -p 8888:8888 quay.io/jupyter/scipy-notebook:2026-07-28

# Entered start.sh with args: start-notebook.py

# ...

#     To access the server, open this file in a browser:
#         file:///home/jovyan/.local/share/jupyter/runtime/jpserver-7-open.html
#     Or copy and paste one of these URLs:
#         http://eca4aa01751c:8888/lab?token=d4ac9278f5f5388e88097a3a8ebbe9401be206cfa0b83099
#         http://127.0.0.1:8888/lab?token=d4ac9278f5f5388e88097a3a8ebbe9401be206cfa0b83099

Pressing Ctrl-C twice shuts down the Server but leaves the container intact on disk for later restart or permanent deletion using commands like the following:

bash
# list containers
docker ps --all
# CONTAINER ID   IMAGE                                       COMMAND                  CREATED              STATUS                     PORTS     NAMES
# eca4aa01751c   quay.io/jupyter/scipy-notebook:2026-07-28   "tini -g -- start.sh…"   About a minute ago   Exited (0) 5 seconds ago             silly_panini

# start the stopped container
docker start --attach -i eca4aa01751c
# Entered start.sh with args: start-notebook.py
# ...

# remove the stopped container
docker rm eca4aa01751c
# eca4aa01751c

Example 2

This command pulls the jupyter/r-notebook image tagged 2026-07-28 from Quay.io if it is not already present on the local host.
It then starts a container running a Jupyter Server and exposes the server on host port 10000.
The server logs appear in the terminal and include a URL to the Server but with the internal container port (8888) instead of the correct host port (10000).

bash
docker run -it --rm -p 10000:8888 -v "${PWD}":/home/jovyan/work quay.io/jupyter/r-notebook:2026-07-28

Pressing Ctrl-C twice shuts down the Server and immediately destroys the Docker container.
New files and changes in ~/work in the container will be preserved.
Any other changes made in the container will be lost.

{note}
By default, [jupyter's root_dir](https://jupyter-server.readthedocs.io/en/latest/other/full-config.html) is `/home/jovyan`.
So, new notebooks will be saved there, unless you change the directory in the file browser.

To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to the previous command: `start-notebook.py --ServerApp.root_dir=/home/jovyan/work`.

Example 3

This command pulls the jupyter/all-spark-notebook image currently tagged latest from Quay.io if an image tagged latest is not already present on the local host.
It then starts a container named notebook running a JupyterLab server and exposes the server on a randomly selected port.

bash
docker run --detach -P --name notebook quay.io/jupyter/all-spark-notebook

where:

  • --detach: will run the container in detached mode

You can also use the following docker commands to see the port and Jupyter Server token:

bash
# get the random host port assigned to the container port 8888
docker port notebook 8888
# 0.0.0.0:49153
# :::49153

# get the notebook token from the logs
docker logs --tail 3 notebook
    # Or copy and paste one of these URLs:
    #     http://878f1a9b4dfa:8888/lab?token=d336fa63c03f064ff15ce7b269cab95b2095786cf9ab2ba3
    #  or http://127.0.0.1:8888/lab?token=d336fa63c03f064ff15ce7b269cab95b2095786cf9ab2ba3

Together, the URL to visit on the host machine to access the server, in this case, is http://127.0.0.1:49153/lab?token=d336fa63c03f064ff15ce7b269cab95b2095786cf9ab2ba3.

The container runs in the background until stopped and/or removed by additional Docker commands:

bash
# stop the container
docker stop notebook
# notebook

# remove the container permanently
docker rm notebook
# notebook

Using the Podman CLI

An alternative to using the Docker CLI is to use the Podman CLI.
Podman is mostly compatible with Docker.

Podman example

If we use Podman instead of Docker in the situation given in Example 2, it will look like this:

The example makes use of rootless Podman; in other words, the Podman command is run from a regular user account.
In a Bash shell, set the shell variables uid and gid to the UID and GID of the user jovyan in the container.

bash
uid=1000
gid=100

This command pulls the quay.io/jupyter/r-notebook image tagged 2026-07-28 from Quay.io if it is not already present on the local host.
It then starts a container running a Jupyter Server with the JupyterLab frontend and exposes the server on host port 10000.
The server logs appear in the terminal and include a URL to the server but with the internal container port (8888) instead of the correct host port (10000).

bash
podman run -it --rm -p 10000:8888 \
    -v "${PWD}":/home/jovyan/work --user $uid:$gid \
    --userns keep-id:uid=${uid},gid=${gid} \
    quay.io/jupyter/r-notebook:2026-07-28

If the commands fail with the error message:
Error: unrecognized namespace mode keep-id:uid=1000,gid=100 passed
see troubleshooting tip.

{note}
The `podman run` command in the example above maps all subuids and subgids of the user into the container.
That works fine but is actually more than needed.
The `podman run` option `--userns=auto` will, for instance, not be possible to use as long as there are no unused subuids and subgids available.
The example could be improved by investigating more in detail which UIDs and GIDs need to be available in the container and then only map them,
for example by setting the size option (`--userns keep-id:uid=${uid},gid=${gid},size=$size`).

Pressing Ctrl-C twice shuts down the Server and immediately destroys the container.
New files and changes in ~/work in the container will be preserved.
Any other changes made in the container will be lost.

Using Binder

A Binder is a service that allows you to create and share custom computing environments for projects in version control.
You can use any of the Jupyter Docker Stacks images as a basis for a Binder-compatible Dockerfile.
See the
docker-stacks example and
Using a Dockerfile section in the
Binder documentation for instructions.

Using JupyterHub

You can configure JupyterHub to launch Docker containers from the Jupyter Docker Stacks images.
If you've been following the Zero to JupyterHub with Kubernetes guide,
see the Use an existing Docker image section for details.
If you have a custom JupyterHub deployment, see the Picking or building a Docker image
instructions for the dockerspawner instead.

Using Other Tools and Services

You can use the Jupyter Docker Stacks with any Docker-compatible technology
(e.g., Docker Compose, docker-py, or your favorite cloud container service).
See the documentation of the tool, library, or service for details about how to reference, configure, and launch containers from these images.


Using Selecting (`docs/using/selecting.md`)

Selecting an Image

Pull vs Build: Quick Guidance

Images are published on Quay.io registry and most users should start by pulling an existing image that closely matches their needs.
Pulling a pre-built image is fast and suitable for common use cases such as running notebooks, teaching, or standard data science workflows.

Building images locally is recommended only when additional customization is required, for example:

  • adding system-level packages that cannot be installed at runtime,
  • preinstalling large or complex language-specific libraries,
  • creating organization-specific images or heavy customization for CI.

If unsure, try an existing Quay.io image first; if it falls short, follow the custom images guide to build a tailored image.

Using one of the Jupyter Docker Stacks requires two choices:

  1. Which Docker image you wish to use
  2. How you wish to start Docker containers from that image

This section provides details about the first.

Core Stacks

The Jupyter team maintains a set of Docker image definitions in the https://github.com/jupyter/docker-stacks GitHub repository.
The following sections describe these images, including their contents, relationships, and versioning strategy.

jupyter/docker-stacks-foundation

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/docker-stacks-foundation is a small image supporting a majority of options common across all core stacks.
It is the basis for all other stacks on which Jupyter-related applications can be built
(e.g., kernel-based containers, nbclient applications, etc.).
As such, it does not contain application-level software like JupyterLab, Jupyter Notebook, or JupyterHub.

It contains:

  • Package managers
    • conda: "cross-platform, language-agnostic binary package manager".
    • mamba: "reimplementation of the conda package manager in C++". We use this package manager by default when installing packages.
  • Unprivileged user jovyan (uid=1000, configurable, see options in the common features section of this documentation) in group users (gid=100)
    with ownership over the /home/jovyan and /opt/conda paths
  • tini and a start.sh script as the container entry point - useful for running alternative commands in the container as applications are added (e.g. ipython, jupyter kernelgateway, jupyter lab)
  • A run-hooks.sh script, which can source/run files in a given directory
  • Options for a passwordless sudo
  • Common system libraries like ca-certificates, locales
  • wget to download external files
  • No preinstalled scientific computing packages

jupyter/base-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/base-notebook adds base Jupyter Applications like JupyterLab, Jupyter Notebook, JupyterHub, and NBClassic
and serves as the basis for all other stacks besides jupyter/docker-stacks-foundation.

It contains:

  • Everything in jupyter/docker-stacks-foundation
  • Minimally functional Server (e.g., no LaTeX support for saving notebooks as PDFs)
  • notebook, nbclassic, jupyterhub-singleuser, and jupyterlab packages
  • A start-notebook.py script as the default command
  • A start-singleuser.py script useful for launching containers in JupyterHub
  • Options for a self-signed HTTPS certificate
{warning}
`jupyter/base-notebook` also contains `start-notebook.sh` and `start-singleuser.sh` files to maintain backward compatibility.
External config that explicitly refers to those files should instead
update to refer to `start-notebook.py` and `start-singleuser.py`.
The shim `.sh` files will be removed at some future date.

jupyter/minimal-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/minimal-notebook adds command-line tools useful when working in Jupyter applications.

It contains:

  • Everything in jupyter/base-notebook
  • Common useful utilities like
    curl,
    git,
    nano (actually nano-tiny),
    tzdata,
    unzip,
    and vi (actually vim-tiny),
  • TeX Live for notebook document conversion

jupyter/r-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/r-notebook includes popular packages from the R ecosystem listed below:

jupyter/julia-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/julia-notebook includes popular packages from the Julia ecosystem listed below:

  • Everything in jupyter/minimal-notebook and its ancestor images
  • The Julia compiler and base environment
  • IJulia to support Julia code in Jupyter notebook
  • Pluto.jl reactive Julia notebook interface, made accessible with jupyter-pluto-proxy
  • HDF5 package

jupyter/scipy-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/scipy-notebook includes popular packages from the scientific Python ecosystem.

jupyter/tensorflow-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/tensorflow-notebook includes popular Python deep learning libraries.

jupyter/pytorch-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/pytorch-notebook includes popular Python deep learning libraries.

  • Everything in jupyter/scipy-notebook and its ancestor images
  • pytorch machine learning library

jupyter/datascience-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/datascience-notebook includes libraries for data analysis from the Python, R, and Julia communities.

  • Everything in jupyter/scipy-notebook and its ancestor images
  • The R and Julia packages from the jupyter/r-notebook and jupyter/julia-notebook images
    (but not the additional system packages installed via apt in jupyter/r-notebook, such as unixodbc-dev)
  • rpy2 package

jupyter/pyspark-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/pyspark-notebook includes Python support for Apache Spark.

jupyter/all-spark-notebook

Source on GitHub |
Dockerfile commit history |
Quay.io image tags

jupyter/all-spark-notebook includes Python and R support for Apache Spark.

  • Everything in jupyter/pyspark-notebook and its ancestor images
  • IRKernel to support R code in Jupyter notebooks
  • rcurl,
    sparklyr,
    ggplot2
    packages

CUDA enabled variants

We provide CUDA accelerated versions of the pytorch-notebook and tensorflow-notebook images.
Prepend a CUDA prefix (versioned prefix like cuda12- for pytorch-notebook or just cuda- for tensorflow-notebook) to the image tag
to allow PyTorch or TensorFlow operations to use compatible NVIDIA GPUs for accelerated computation.
We only build pytorch-notebook for the last two major versions of CUDA.
The tensorflow-notebook image only supports the latest CUDA version listed in the officially tested build configurations.

For example, you could use the image quay.io/jupyter/pytorch-notebook:cuda12-python-3.13.14 or quay.io/jupyter/tensorflow-notebook:cuda-latest.

Image Relationships

The following diagram depicts the build dependency tree of the core images. (i.e., the FROM statements in their Dockerfiles).
Any given image inherits the complete content of all ancestor images pointing to it.

{mermaid}
flowchart TD
    ubuntu["ubuntu<br>(LTS with point release)"] --> docker-stacks-foundation
    docker-stacks-foundation --> base-notebook
    base-notebook --> minimal-notebook
    minimal-notebook --> scipy-notebook
    minimal-notebook --> r-notebook
    minimal-notebook --> julia-notebook
    scipy-notebook --> tensorflow-notebook["tensorflow-notebook<br>+cuda variant"]
    scipy-notebook --> pytorch-notebook["pytorch-notebook<br>+cuda12/cuda13 variants"]
    scipy-notebook --> datascience-notebook
    scipy-notebook --> pyspark-notebook
    pyspark-notebook --> all-spark-notebook

Builds

Every Monday and whenever a pull request is merged, images are rebuilt and pushed to the public container registry.

Versioning via image tags

Whenever a docker image is pushed to the container registry, it is tagged with:

  • the latest tag
  • a 12-character git commit SHA like 1ffe43816ba9
  • a date formatted like 2023-01-30
  • OS version like ubuntu-24.04
  • a set of software version tags like python-3.13.14 and lab-4.6.1 (exact versions vary with each build)
{warning}
- Tags before `2022-07-05` were sometimes incorrect.
  Please, do not rely on them.
- Single-platform images have either `aarch64-` or `x86_64-` tag prefixes, for example, `quay.io/jupyter/base-notebook:aarch64-python-3.13.14`

For stability and reproducibility, you should either reference a date-formatted
tag from a date before the current date (in UTC) or a git commit SHA older
than the latest git commit SHA in the default branch of the
jupyter/docker-stacks GitHub repository.

Verifying image signatures

Pushed images (both multi-platform and single-platform ones) are signed with cosign
using keyless signing with the GitHub Actions OIDC identity.
Signatures are stored in the Sigstore bundle format, which requires cosign v3+ to verify.
To verify that an image was built and pushed by this repository's CI:

bash
cosign verify quay.io/jupyter/base-notebook:latest \
    --certificate-identity-regexp='^https://github\.com/jupyter/docker-stacks/' \
    --certificate-oidc-issuer=https://token.actions.githubusercontent.com

Community Stacks

The core stacks are but a tiny sample of what's possible when combining Jupyter with other technologies.
We encourage members of the Jupyter community to create their own stacks based on the core images and link them below.
See the contributing guide for information about how to create your own Jupyter Docker Stack.

Flavor Binder Description
csharp bb More than 200 Jupyter Notebooks with example C# code
education bb nbgrader and RISE on top of the datascience-notebook image
ihaskell bb Based on IHaskell. Includes popular packages and example notebooks
java bb IJava kernel on top of the minimal-notebook image
sage bb sagemath kernel on top of the minimal-notebook image
cgspatial bb Major geospatial Python & R libraries on top of the datascience-notebook image
kotlin bb Kotlin kernel for Jupyter/IPython on top of the base-notebook image
transformers bb Transformers and NLP libraries such as Tensorflow, Keras, Jax and PyTorch
scraper bb Scraper tools (selenium, chromedriver, beatifulsoup4, requests) on minimal-notebook image
almond bb Scala kernel for Jupyter using Almond on top of the base-notebook image
lisp-stat bb Common Lisp statistical computing environment on top of the minimal-notebook image
sequencing bb Collection for bioinformatics sequencing data analysis, covering bulk RNA-seq, single-cell RNA-seq, spatial sequencing, and multi-omics

Other GPU-accelerated notebooks

Flavor Description
GPU-Jupyter Power of your NVIDIA GPU and GPU calculations using Tensorflow and Pytorch in collaborative notebooks. This is done by generating a Dockerfile that consists of the nvidia/cuda base image, the well-maintained docker-stacks that is integrated as a submodule, and GPU-able libraries like Tensorflow, Keras and PyTorch on top of it.
myLab TH LΓΌbeck Images Images based on the jupyter/docker-stacks, built and maintained at the myLab TH LΓΌbeck using build scripts similar to iot-salzburg. Several images include GPU libraries.
PRP-GPU PRP (Pacific Research Platform) maintained registry for jupyter stack based on NVIDIA CUDA-enabled image. Added the PRP image with Pytorch and some other Python packages and GUI Desktop notebook based on https://github.com/jupyterhub/jupyter-remote-desktop-proxy.
b-data GPU accelerated, multi-arch (linux/amd64, linux/arm64/v8) Docker images for R, Python , MAX and Julia. Derived from nvidia/cuda devel-flavored images. With code-server next to JupyterLab. Just Python – no Conda / Mamba.

Using Specifics (`docs/using/specifics.md`)

Image Specifics

This page provides details about features specific to one or more images.

Apache Sparkβ„’

Specific Docker Image Options

  • -p 4040:4040 - The jupyter/pyspark-notebook and jupyter/all-spark-notebook images open
    SparkUI (Spark Monitoring and Instrumentation UI) at default port 4040,
    this option maps the 4040 port inside the docker container to the 4040 port on the host machine.

    {note}
    Every new spark context that is created is put onto an incrementing port (i.e. 4040, 4041, 4042, etc.), and it might be necessary to open multiple ports.

    For example, docker run --detach -p 8888:8888 -p 4040:4040 -p 4041:4041 quay.io/jupyter/pyspark-notebook.

IPython low-level output capture and forward

Spark images (pyspark-notebook and all-spark-notebook) have been configured to disable IPython low-level output capture and forward system-wide.
The rationale behind this choice is that Spark logs can be verbose, especially at startup when Ivy is used to load additional jars.
Those logs are still available but only in the container's logs.

If you want to make them appear in the notebook, you can overwrite the configuration in a user-level IPython kernel profile.
To do that, you have to uncomment the following line in your ~/.ipython/profile_default/ipython_kernel_config.py and restart the kernel.

python
c.IPKernelApp.capture_fd_output = True

If you have no IPython profile, you can initiate a fresh one by running the following command.

bash
ipython profile create
# [ProfileCreate] Generating default config file: '/home/jovyan/.ipython/profile_default/ipython_config.py'
# [ProfileCreate] Generating default config file: '/home/jovyan/.ipython/profile_default/ipython_kernel_config.py'

Build an Image with a Different Version of Spark

You can build a pyspark-notebook image with a different Spark version by overriding the default value of the following arguments at build time.
all-spark-notebook inherits from pyspark-notebook, so you have to first build pyspark-notebook and then all-spark-notebook to get the same version in all-spark-notebook.

  • Spark distribution is defined by the combination of Spark, Hadoop, and Scala versions,
    see Download Apache Spark and the archive repo for more information.
    • openjdk_version: The version of the OpenJDK (JRE headless) distribution (21 by default).
    • spark_version (optional): The Spark version to install, for example 4.0.0.
      If not specified (this is the default), the latest stable Spark version will be installed.
    • hadoop_version: The Hadoop version (3 by default).
    • scala_version (optional): The Scala version, for example 2.13 (not specified by default).
      Starting with Spark >= 3.2, the distribution file might contain the Scala version.
    • spark_download_url: URL to use for Spark downloads.
      You may need to use https://archive.apache.org/dist/spark/ url if you want to download old Spark versions.
{note}
Only Spark >= 4.0 can be built this way:
the pandas version is resolved from the `dev/infra/Dockerfile` file of the Spark release,
and the Derby jar replacement expects the version bundled by modern Spark.
Building older versions requires modifying `images/pyspark-notebook/Dockerfile`.

For example, here is how to build a pyspark-notebook image with Spark 4.0.0 and OpenJDK 17.

{warning}
This recipe is not tested and might be broken.
bash
# From the root of the project
# Build the image with different arguments
docker build --rm --force-rm \
    -t my-pyspark-notebook ./images/pyspark-notebook \
    --build-arg openjdk_version=17 \
    --build-arg spark_version=4.0.0 \
    --build-arg spark_download_url="https://archive.apache.org/dist/spark/"

# Check the newly built image
docker run -it --rm my-pyspark-notebook pyspark --version

# WARNING: Using incubator modules: jdk.incubator.vector
# Welcome to
#       ____              __
#      / __/__  ___ _____/ /__
#     _\ \/ _ \/ _ `/ __/  '_/
#    /___/ .__/\_,_/_/ /_/\_\   version 4.0.0
#       /_/

# Using Scala version 2.13.16, OpenJDK 64-Bit Server VM, 17.0.19
# Branch HEAD
# Compiled by user wenchen on 2025-05-19T07:58:03Z
# Revision fa33ea000a0bda9e5a3fa1af98e8e85b8cc5e4d4
# Url https://github.com/apache/spark
# Type --help for more information.

Usage Examples

The jupyter/pyspark-notebook and jupyter/all-spark-notebook images support the use of Apache Spark in Python and R notebooks.
The following sections provide some examples of how to get started using them.

Using Spark Local Mode

Spark local mode is useful for experimentation on small data when you do not have a Spark cluster available.

{warning}
In these examples, Spark spawns all the main execution components in the same single JVM.
You can read additional info about local mode [here](https://books.japila.pl/apache-spark-internals/local/).
If you want to use all the CPU, one of the simplest ways is to set up a [Spark Standalone Cluster](https://spark.apache.org/docs/latest/spark-standalone.html).

Local Mode in Python

In a Python notebook.

python
from pyspark.sql import SparkSession

# Spark session & context
spark = SparkSession.builder.master("local").getOrCreate()
sc = spark.sparkContext

# Sum of the first 100 whole numbers
rdd = sc.parallelize(range(100 + 1))
rdd.sum()
# 5050

Local Mode in R

{warning}
SparkR is deprecated from Apache Spark 4.0.0 and will be removed in a future version.
Prefer the [sparklyr][sparklyr] examples below.

In an R notebook with SparkR.

r
library(SparkR)

# Spark session & context
sc <- sparkR.session("local")

# Sum of the first 100 whole numbers
sdf <- createDataFrame(list(1:100))
dapplyCollect(sdf,
              function(x)
              { x <- sum(x)}
             )
# 5050

In an R notebook with sparklyr.

r
library(sparklyr)

# Spark configuration
conf <- spark_config()
# Set the catalog implementation in-memory
conf$spark.sql.catalogImplementation <- "in-memory"

# Spark session & context
sc <- spark_connect(master = "local", config = conf)

# Sum of the first 100 whole numbers
sdf_len(sc, 100, repartition = 1) %>%
    spark_apply(function(e) sum(e))
# 5050

Connecting to a Spark Cluster in Standalone Mode

Connection to Spark Cluster on Standalone Mode requires the following set of steps:

  1. Verify that the docker image (check the Dockerfile) and the Spark Cluster, which is being
    deployed, run the same version of Spark.

  2. Deploy Spark in Standalone Mode.

  3. Run the Docker container with --net=host in a location that is network-addressable by all of
    your Spark workers.
    (This is a Spark networking requirement.)

    {note}
    When using `--net=host`, you must also use the flags `--pid=host -e TINI_SUBREAPER=true`. See <https://github.com/jupyter/docker-stacks/issues/64> for details.

Note: In the following examples, we are using the Spark master URL spark://master:7077 which shall be replaced by the URL of the Spark master.

Standalone Mode in Python

The same Python version needs to be used on the notebook (where the driver is located) and on the Spark workers.
The Python version used on the driver and worker side can be adjusted by setting the environment variables PYSPARK_PYTHON and/or PYSPARK_DRIVER_PYTHON,
see Spark Configuration for more information.

python
from pyspark.sql import SparkSession

# Spark session & context
spark = SparkSession.builder.master("spark://master:7077").getOrCreate()
sc = spark.sparkContext

# Sum of the first 100 whole numbers
rdd = sc.parallelize(range(100 + 1))
rdd.sum()
# 5050

Standalone Mode in R

In an R notebook with SparkR.

r
library(SparkR)

# Spark session & context
sc <- sparkR.session("spark://master:7077")

# Sum of the first 100 whole numbers
sdf <- createDataFrame(list(1:100))
dapplyCollect(sdf,
              function(x)
              { x <- sum(x)}
             )
# 5050

In an R notebook with sparklyr.

r
library(sparklyr)

# Spark session & context
# Spark configuration
conf <- spark_config()
# Set the catalog implementation in-memory
conf$spark.sql.catalogImplementation <- "in-memory"
sc <- spark_connect(master = "spark://master:7077", config = conf)

# Sum of the first 100 whole numbers
sdf_len(sc, 100, repartition = 1) %>%
    spark_apply(function(e) sum(e))
# 5050

Define Spark Dependencies

{note}
This example is given for [Elasticsearch](https://www.elastic.co/docs/reference/elasticsearch-hadoop/installation).

Spark dependencies can be declared thanks to the spark.jars.packages property
(see Spark Configuration for more information).

They can be defined as a comma-separated list of Maven coordinates at the creation of the Spark session.

python
from pyspark.sql import SparkSession

spark = (
    SparkSession.builder.appName("elasticsearch")
    .config(
        "spark.jars.packages", "org.elasticsearch:elasticsearch-spark-30_2.12:7.13.0"
    )
    .getOrCreate()
)

Dependencies can also be defined in the spark-defaults.conf.
However, it has to be done by root, so it should only be considered to build custom images.

dockerfile
USER root
RUN echo "spark.jars.packages org.elasticsearch:elasticsearch-spark-30_2.12:7.13.0" >> "${SPARK_HOME}/conf/spark-defaults.conf"
USER ${NB_UID}

Jars will be downloaded dynamically at the creation of the Spark session and stored by default in ${HOME}/.ivy2/jars (can be changed by setting spark.jars.ivy).

Tensorflow

The jupyter/tensorflow-notebook image supports the use of
Tensorflow in a single machine or distributed mode.

Single Machine Mode

python
import tensorflow as tf

print(tf.constant("Hello, TensorFlow"))
print(tf.reduce_sum(tf.random.normal([1000, 1000])))

Distributed Mode

TensorFlow 2 uses distribution strategies for distributed computations.
For example, tf.distribute.MirroredStrategy performs synchronous training across multiple devices on a single machine
(it also works on a machine without GPUs).

python
import tensorflow as tf

strategy = tf.distribute.MirroredStrategy()

with strategy.scope():
    model = tf.keras.Sequential([tf.keras.Input(shape=(1,)), tf.keras.layers.Dense(1)])
    model.compile(loss="mse", optimizer="sgd")

Using Troubleshooting (`docs/using/troubleshooting.md`)

Troubleshooting Common Problems

When troubleshooting, you may see unexpected behaviors or receive an error message.
This section provides advice on how to identify and fix some of the most commonly encountered issues.

Most of the docker run flags used in this document are explained in detail in the
Common Features, Docker Options section of the documentation.

Permission denied when mounting volumes

If you are running a Docker container while mounting a local volume or host directory using the -v flag like so:

bash
docker run -it --rm \
    -p 8888:8888 \
    -v <my-vol>:<container-dir> \
    quay.io/jupyter/minimal-notebook:latest

you might face permissions issues when trying to access the mounted volume:

bash
# assuming we mounted the volume in /home/jovyan/stagingarea
# root is the owner of the mounted volume
ls -ld ~/stagingarea/
# drwxr-xr-x 2 root root 4096 Feb  1 12:55 stagingarea/

touch stagingarea/kale.txt
# touch: cannot touch 'stagingarea/kale.txt': Permission denied

In this case, the user of the container (jovyan) and the owner of the mounted volume (root)
have different permission levels and ownership over the container's directories and mounts.
The following sections cover a few of these scenarios and how to fix them.

Some things to try:

  1. Change ownership of the volume mount

    You can change the ownership of the volume mount using the chown command.
    In the case of the docker-stacks images, you can set the CHOWN_EXTRA and CHOWN_EXTRA_OPTS environment variables.

    For example, to change the ownership of the volume mount to the jovyan user (non-privileged default user in the Docker images):

    bash
    # running in detached mode - can also be run in interactive mode
    docker run --detach \
        -v <my-vol>:<container-dir> \
        -p 8888:8888 \
        --user root \
        -e CHOWN_EXTRA="<container-dir>" \
        -e CHOWN_EXTRA_OPTS="-R" \
        quay.io/jupyter/minimal-notebook

    where:

    • CHOWN_EXTRA=<some-dir>,<some-other-dir>: will change the ownership and group of the specified container directory (non-recursive by default).
      You need to provide full paths starting with /.
    • CHOWN_EXTRA_OPTS="-R": will recursively change the ownership and group of the directory specified in CHOWN_EXTRA.
    • --user root: you must run the container with the root user to change ownership at runtime.

    Now accessing the mount should work as expected:

    bash
    # assuming we mounted the volume in /home/jovyan/stagingarea
    ls -ld ~/stagingarea
    # drwxr-xr-x 2 jovyan users 4096 Feb  1 12:55 stagingarea/
    
    touch stagingarea/kale.txt
    # jovyan is now the owner of /home/jovyan/stagingarea
    # ls -la ~/stagingarea/
    # -rw-r--r-- 1 jovyan users    0 Feb  1 14:41 kale.txt
    {admonition} additional notes
    - If you are mounting your volume inside the `/home/` directory, you can use the `-e CHOWN_HOME=yes` and `CHOWN_HOME_OPTS="-R"` flags
       instead of the `-e CHOWN_EXTRA` and `-e CHOWN_EXTRA_OPTS` in the example above.
       - This solution should work in most cases where you have created a docker volume
       (i.e. using the [`docker volume create --name <my-volume>` command](https://docs.docker.com/engine/storage/volumes/#create-and-manage-volumes)) and mounted it using the `-v` flag in `docker run`.
  2. Matching the container's UID/GID with the host's

    Docker handles mounting host directories differently from mounting volumes, even though the syntax is essentially the same (i.e. -v).

    When you initialize a Docker container using the -v flag, the host directories are bind-mounted directly into the container.
    Therefore, the permissions and ownership are copied over and will be the same as the ones in your local host
    (including user ids) which may result in permissions errors when trying to access directories or create/modify files inside.

    Suppose your local user has a UID and GID of 1234 and 5678, respectively.
    To fix the UID discrepancies between your local directories and the container's directories,
    you can run the container with an explicit NB_UID and NB_GID to match that of the local user:

    bash
    docker run -it --rm \
        --user root \
        -p 8888:8888 \
        -e NB_UID=1234 \
        -e NB_GID=5678 \
        -v "${PWD}"/test:/home/jovyan/work \
        quay.io/jupyter/minimal-notebook:latest
    
    # you should see an output similar to this
    # Update jovyan's UID:GID to 1234:5678
    # Running as jovyan: bash

    where:

    • NB_UID and NB_GID should match the local user's UID and GID.
    • You must use --user root to ensure that the UID and GID are updated at runtime.
{admonition} additional notes
- The caveat with this approach is that since these changes are applied at runtime,
   you will need to re-run the same command with the appropriate flags and environment variables
   if you need to recreate the container (i.e. after removing/destroying it).
 - If you pass a numeric UID, it **must** be in the range of 0-2147483647
 - This approach only updates the UID and GID of the **existing `jovyan` user** instead of creating a new user.
   From the above example:
   ```bash
   id
   # uid=1234(jovyan) gid=5678(jovyan) groups=5678(jovyan),100(users)
   ```

Permission issues after changing the UID/GID and USER in the container

If you have also created a new user, you might be experiencing any of the following issues:

  • the root user is the owner of /home or a mounted volume
  • when starting the container, you get an error such as Failed copy data from /home/jovyan to /home/ or to create symlink!
  • getting permission denied when trying to conda install packages

Some things to try:

  1. Ensure the new user has ownership of /home and volume mounts

    For example, say you want to create a user callisto with a GID and UID of 1234.
    You will have to add the following flags to the docker run command:

    bash
    docker run -it --rm \
         -p 8888:8888 \
         --user root \
         -e NB_USER=callisto \
         -e NB_UID=1234 \
         -e NB_GID=1234 \
         -e CHOWN_HOME=yes \
         -e CHOWN_HOME_OPTS="-R" \
         -w "/home/callisto" \
         -v "${PWD}"/test:/home/callisto/work \
         quay.io/jupyter/minimal-notebook
    
     # Updated the jovyan user:
     # - username: jovyan       -> callisto
     # - home dir: /home/jovyan -> /home/callisto
     # Update callisto's UID:GID to 1234:1234
     # Attempting to copy /home/jovyan to /home/callisto...
     # Success!
     # Ensuring /home/callisto is owned by 1234:1234 (chown options: -R)
     # Running as callisto: bash

    where:

    • -e NB_USER=callisto: will rename the jovyan user to callisto and attempt to copy the content of the home directory to /home/callisto,
      falling back to creating a symlink to /home/jovyan if the copy fails
    • -e NB_UID=1234 and -e NB_GID=1234: will set the UID and GID of the new user (callisto) to 1234
    • -e CHOWN_HOME_OPTS="-R" and -e CHOWN_HOME=yes: ensure that the new user is the owner of the /home directory and subdirectories
      (setting CHOWN_HOME_OPTS="-R will ensure this change is applied recursively)
    • -w "/home/callisto" sets the working directory to be the new user's home
    {admonition} additional notes
    In the example above, the `-v` flag is used to mount the local volume onto the new user's `/home` directory.
    
     However, if you are mounting a volume elsewhere,
     you also need to use the `-e CHOWN_EXTRA=<some-dir>` flag to avoid any permission issues
     (see the section [Permission denied when mounting volumes](#permission-denied-when-mounting-volumes) on this page).
  2. Dynamically assign the user ID and GID

    The above case ensures that the /home directory is owned by a newly created user with a specific UID and GID,
    but if you want to assign the UID and GID of the new user dynamically,
    you can make the following adjustments:

    bash
    docker run -it --rm \
        -p 8888:8888 \
        --user root \
        -e NB_USER=callisto \
        -e NB_UID="$(id -u)" \
        -e NB_GID="$(id -g)"  \
        -e CHOWN_HOME=yes \
        -e CHOWN_HOME_OPTS="-R" \
        -w "/home/callisto" \
        -v "${PWD}"/test:/home/callisto/work \
        quay.io/jupyter/minimal-notebook

    where:

    • "$(id -u)" and "$(id -g)" will dynamically assign the UID and GID of the user executing the docker run command to the new user (callisto)
  • Pass absolute paths to the -v flag:

    bash
    -v "${PWD}"/<my-vol>:/home/jovyan/work

    This example uses the syntax ${PWD}, which is replaced with the full path to the current directory at runtime.
    The destination path should also be an absolute path starting with a / such as /home/jovyan/work.

  • You might want to consider using the Docker native --user and --group-add users flags instead of -e NB_UID and -e NB_GID:

    bash
    # note this will use the same UID from
    # the user calling the command, thus matching the local host
    
    docker run -it --rm \
        -p 8888:8888 \
        --user "$(id -u)" --group-add users \
        -v <my-vol>:/home/jovyan/work quay.io/jupyter/datascience-notebook

    This command will launch the container with a specific user UID and add that user to the users group
    to modify the files in the default /home and /opt/conda directories.
    This further avoids issues when trying to conda install additional packages.

  • Use docker inspect <container_id> and look for the Mounts section
    to verify that the volume was created and mounted accordingly:

    json
    {
      "Mounts": [
        {
          "Type": "volume",
          "Name": "my-vol",
          "Source": "/var/lib/docker/volumes/stagingarea/_data",
          "Destination": "/home/jovyan/stagingarea",
          "Driver": "local",
          "Mode": "z",
          "RW": true,
          "Propagation": ""
        }
      ]
    }

Problems installing conda packages from specific channels

The docker-stacks images use the default flexible conda channels priority.

bash
conda config --show | grep priority
# channel_priority: flexible

# to see its meaning
conda config --describe channel_priority

# checking the current channels
conda config --show channels
# channels:
#   - conda-forge

Installing packages from alternative channels:

You can install packages from other conda channels (e.g. bioconda) by specifying the channel:

bash
conda install -c bioconda bioconductor-geoquery

If you have set the channels priority to strict, installing packages from a channel with lower priority may cause problems.
In this case, you can disable the channel_priority setting at the command level:

bash
conda install --no-channel-priority -c bioconda bioconductor-geoquery

Additional details are provided in the Using Alternative Channels section of the Common Features page.

Tokens are being rejected

If you are a regular user of VSCode and the Jupyter extension,
you might experience either of these issues when using any of the docker-stacks images:

  • when clicking on the URL displayed on your command line logs, you face a "This site cannot be reached" page on your web browser

  • using the produced token and/or URL results in an "Invalid credentials" error on the Jupyter "Token authentication is enabled" page

    bash
    # example log output from the docker run command
    
    # [...]
    # Or copy and paste one of these URLs:
    #   http://3d4cf3809e3f:8888/?token=996426e890f8dc22fa6835a44442b6026cba02ee61fee6a2
    #   or http://127.0.0.1:8888/?token=996426e890f8dc22fa6835a44442b6026cba02ee61fee6a2

Some things to try:

  1. Find lingering Jupyter processes in the background

    The first thing you want to try is to check that no other Jupyter processes are running in the background:

    bash
    ps aux | grep jupyter

    If there are existing processes running, you can kill them with:

    bash
    # example output from the above command
    # my-user 3412 ... --daemon-module=vscode_datascience_helpers.jupyter_daemon
    
    # using the pid from the above log
    kill 3412
  2. Turn off Jupyter auto-start in VSCode

    Alternatively - you might want to ensure that the Jupyter: Disable Jupyter Auto Start setting is turned on to avoid this issue in the future.

    You can achieve this from the Settings > Jupyter menu in VScode:

  3. Route container to unused local port

    Instead of mapping Docker port 8888 to local port 8888, map to another unused local port.
    You can see an example of mapping to local port 8001:

    bash
    docker run -it --rm -p 8001:8888 quay.io/jupyter/datascience-notebook

    When the terminal provides the link to access Jupyter: http://127.0.0.1:8888/lab?token=80d45d241a1ba4c2...,
    change the default port value of 8888 in the URL to the port value mapped with the docker run command.

    In this example, we use 8001, so the edited link would be: http://127.0.0.1:8001/lab?token=80d45d241a1ba4c2....

    Note: Port mapping for Jupyter has other applications outside of Docker.
    For example, it can be used to allow multiple Jupyter instances when using SSH to control cloud devices.


--- METRICS ---