Step 4 - JupyterHub

In the last lesson you learned how to build a docker image that contained all of the software and notebooks needed for the workshop-in-a-workshop. You also learned how to run the container and connect to the workshop.

While this worked, the user experience was not satisfactory. You had to copy and paste an access token, and for this to work, the user has to have docker installed. Ideally, we would like to move this docker container to the cloud. This would enable your learner to simply connect to the cloud-based container from their webbrowser, so will not need docker (or your container image) installed.

The first step to moving to the cloud is we need to prepare the container so that it can be used by multiple learners. To do this, we will use JupyterHub. JupyterHub is a multi-user hub that can spawn, manage and proxy multiple instances of single-user Jupyter notebooks. JupyterHub provides a single server to which learners can connect. When a learner connects, JupyterHub will spin out a new jupyter-notebook kernel, one for each learner, based on the docker container image that you supply.

Building your JupyterHub image

To get this to work, we need to add JupyterHub to our workshop-in-a-workshop jupyter image. This is a little complex. To make it easier, JupyterHub provides a range of base images. I have modified this to generate the below Dockerfile to build a JupyterHub container for the workshop-in-a-workshop. This can be found in the in the jupyterhub directory.

# Based on the JupyterHub docker image that is
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
# Ubuntu 16.04 (xenial) from 2017-07-23

# This has been extensively modified by C.Woods, so blame him
# for any errors or problems

# Start from ubuntu :-)
FROM ubuntu@sha256:84c334414e2bfdcae99509a6add166bbb4fa4041dc3fa6af08046a66fed3005f

LABEL maintainer="Christopher Woods <>"

USER root

# Install all OS dependencies for notebook server that starts but lacks all
# features (e.g., download as all possible file formats)
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get -yq dist-upgrade \
 && apt-get install -yq --no-install-recommends \
    git \
    sudo \
    locales \
    ca-certificates \
    fonts-liberation \
    wget \
    bzip2 \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \

# Install Tini - this is a tiny init system that is useful
# for containers that need to start many processes
RUN wget --quiet && \
    echo "1361527f39190a7338a0b434bd8c88ff7233ce7b9a4876f3315c22fce7eca1b0 *tini" | sha256sum -c - && \
    mv tini /usr/local/bin/tini && \
    chmod +x /usr/local/bin/tini

# Configure environment
ENV CONDA_DIR=/opt/conda \
    SHELL=/bin/bash \
    NB_USER=jovyan \
    NB_UID=1000 \
    NB_GID=100 \
    LC_ALL=en_US.UTF-8 \
    LANG=en_US.UTF-8 \


ADD fix-permissions /usr/local/bin/fix-permissions

# Create jovyan user with UID=1000 and in the 'users' group
# and make sure these dirs are writable by the `users` group.
RUN useradd -m -s $SHELL -N -u $NB_UID $NB_USER && \
    fix-permissions $HOME

# Install a mini anaconda for Python 3. This is needed as
# JupyterHub is best installed from conda
RUN wget -O && \
    chmod a+x && \
    ./ -b -p /opt/conda && \
    chown -R $NB_USER:$NB_GID /opt/conda && \
    fix-permissions /opt/conda && \

# Configure container startup. This starts the tini init system
# telling it to run the as the primary process.
# This is a script provided by JupyterHub that will start different
# processes depending on whether this is the hub or a notebook
ENTRYPOINT ["tini", "--"]
CMD [""]

# Make sure that the jupyter notebook port is accessible from
# outside the container

# Now copy in all of the files that are needed to run JupyterHub
# This includes that is used to
# configure the jupyterhub server
COPY /usr/local/bin/
COPY /usr/local/bin/
COPY /usr/local/bin/
COPY /etc/jupyter/
RUN fix-permissions /etc/jupyter/

# Do all conda work as $NB_USER

# Install Jupyter Notebook and Hub and other useful packages
RUN conda config --system --prepend channels conda-forge && \
    conda config --system --set auto_update_conda false && \
    conda config --system --set show_channel_urls true && \
    conda install --quiet --yes 'notebook=5.2.*' 'jupyterhub=0.8.*' 'jupyterlab=0.31.*'

# Now install the dependencies of our workshop
RUN pip install qrcode[pil] && \
    pip install git+git:// && \
    pip install git+git://

# Now installed tmpauthenticator. This is a module that allows anybody
# to log into this jupyterhub using a hidden URL
RUN pip install jupyterhub-tmpauthenticator

# Reinstall an older version of tornado as tornado 5 doesn't work
# with the current version of jupyterhub
# Also clean up after conda, including clearing the cache
RUN conda install tornado=4.5.3 && \
    conda clean -tipsy && \
    rm -rf $CONDA_DIR/share/jupyter/lab/staging && \
    fix-permissions $CONDA_DIR && \
    rm -rf $HOME/.cache $HOME/.jupyter $HOME/.local/share/jupyter

# Add all of the workshop files to the home directory
ADD example_workshop/lesson*.ipynb $HOME/

# End as the User to make sure that we don't
# accidentally run the container as root

Before we build this image, we should clean out all old docker images. This saves space, particularly if you are running this as part of the RSE workshop, for which disk space is very limited. The below command will remove all unused and untagged docker image containers;

$ docker system prune -a

We need at least 1 GB of free space, so you may need to also delete other workshops, e.g. choose a workshop that takes up a lot of space a which you are not attending and remove that;

$ rm -rf /home/workshops/Cownie

You can build the image using

$ cd jupyterhub
$ docker build . -t workshop-hub

This build will take a while and will create a docker container that is tagged as “workshop-hub”. You can see this image using

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
workshop-hub        latest              9a3dd661420c        5 seconds ago       1.09GB
ubuntu              <none>              14f60031763d        12 months ago       120MB

You can now run the container using the command

$ docker run -p 8888:8888 workshop-hub
Executing the command: jupyter notebook
[I 11:12:15.753 NotebookApp] Writing notebook server cookie secret to /home/jovyan/.local/share/jupyter/runtime/notebook_cookie_secret
[W 11:12:16.356 NotebookApp] WARNING: The notebook server is listening on all IP addresses and not using encryption. This is not recommended.
[I 11:12:16.393 NotebookApp] JupyterLab beta preview extension loaded from /opt/conda/lib/python3.6/site-packages/jupyterlab
[I 11:12:16.393 NotebookApp] JupyterLab application directory is /opt/conda/share/jupyter/lab
[I 11:12:16.397 NotebookApp] Serving notebooks from local directory: /home/jovyan
[I 11:12:16.397 NotebookApp] 0 active kernels
[I 11:12:16.397 NotebookApp] The Jupyter Notebook is running at:
[I 11:12:16.397 NotebookApp] http://[all ip addresses on your system]:8888/?token=09f92d061506b98662a24213d53d42677176bede8ca66378
[I 11:12:16.398 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 11:12:16.398 NotebookApp] 
    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:

You should see similar output as above, including the link at the bottom that you can use to connect to the workshop. Copy the link and paste it into your web browser.


Check through the lessons in your workshop and make sure that they all work.

Previous Up Next