Managing custom Jupyter environments with Singularity


In this tutorial we will cover the basic workflow for managing custom software environments for Jupyter Notebooks using Singularity.

Singularity is a developing platform, so version matters a lot. The version we will be using is the latest development HEAD of 2.3.


In [3]:
singularity --version


2.3-HEAD.gadf5259

Pull the base image from SingularityHub DockerHub

We maintain a base image on SingularityHub for running Jupyter* (https://singularity-hub.org/collections/440/). This image contains the minimum dependencies and configuration needed to run containerized Notebooks (standalone or JupyterHub-spawned), and is intended to serve as a base for user-built software environments.

At this time though bootstrapping from SingularityHub is still an upcoming feature (https://github.com/singularityware/singularity/issues/833) so we will instead be using the jupyter/base-notebook docker container as a base image. The base-notebook is provided by the Jupyter Docker Stacks project (https://github.com/jupyter/docker-stacks), which provides pre-built stacks ready to be run standalone or behind JupyterHub.

A basic pull

Start by pulling the Jupyter base image from SingularityHub. We specifically want commit ae885c0a6226, so we'll specify that by adding the :ae885c0a6226 tag to the end of the repo path:


In [1]:
singularity pull --name "jupyter-base.img" docker://jupyter/base-notebook:ae885c0a6226


Initializing Singularity image subsystem
Opening image file: jupyter-base.img
Creating 892MiB image
Binding image to loop
Creating file system within image
Image is done: jupyter-base.img
Docker image path: index.docker.io/jupyter/base-notebook:ae885c0a6226
Cache folder set to /home/vagrant/.singularity/docker
Importing: base Singularity environment
Importing: /home/vagrant/.singularity/docker/sha256:e0a742c2abfd5e2a6f8ed15b1c78e873cf9559b96a04204daf6de5df01e3124c.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:486cb8339a27635fa93dc47aa0c689326a0a7cce388966d16daf8d265436cf7f.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:dc6f0d824617ad8a5d1163a5b2084814665dd83156317ad06ccf14deb517a053.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:4f7a5649a30e3f318ce5d7e4dbcbbeb6c0938c4cbae4d4a641fe910562ff4978.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:672363445ad2c734e29221a6b47f4e614b5adc8a3cdca3364f62db2ed2bdff0c.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:b337aaee648d9f87e96fae8b24ae2dd887a2ded309b38dbee691fcdb040878cc.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:7f77b4eaa7ff7da43552cca1a34f9d16a1bbe90ba779d2c4355a4c4d1ab6dd0f.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:94e7bffb8046310f285abb93104a0cddd073c5a44ec7cfab05a3db6128fb3006.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:fa94652128009709e53b85cf57087d739108eaa98fd6e599343a32267deda620.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:79c35484f704c0792d062e5749c5df82fa85820bf072531daccefb532d88f294.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:f1756f88332d61a74c658fae7b6a5b8509e59b24e7f4ae7412bb183e23c17bc8.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:e73fef5319c18fa96bf5654b990f993622087a8caa56c2a5673db488a549ab1f.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:ea6e64c66b3a6af070c29a97433acaf94f875d99b4d43509f874686afa66a7d8.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:f3e50561218a351fb914efee8fb3bcb05eb619f70615520375e864b40a0227c4.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:f2de9a241e20a4ca75822db878a3eecf6afee5c4a96f099b76fe24306a51c1ca.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:6684df1dccb5994df682ca325871418c86770373ca27638919dff8dbb42b063a.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:1033de5a165ffa098da08b2da7325b577e5585d55bee04439033d0e72103bd64.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:ff59b2aa8f66f44d88ee7f218edd92bf0ced0d585b880ea6e9fc267100718f1b.tar.gz
Importing: /home/vagrant/.singularity/metadata/sha256:583daeeee9e135a869cbe39637eecb8a3b3790096af37bfdf5a2d98b51016110.tar.gz
Done. Container is at: jupyter-base.img

There it is! Your container is good to go.


In [2]:
singularity exec -e jupyter-base.img jupyter -h


usage: jupyter [-h] [--version] [--config-dir] [--data-dir] [--runtime-dir]
               [--paths] [--json]
               [subcommand]

Jupyter: Interactive Computing

positional arguments:
  subcommand     the subcommand to launch

optional arguments:
  -h, --help     show this help message and exit
  --version      show the jupyter command's version and exit
  --config-dir   show Jupyter config dir
  --data-dir     show Jupyter data dir
  --runtime-dir  show Jupyter runtime dir
  --paths        show all Jupyter paths. Add --json for machine-readable
                 format.
  --json         output paths as machine-readable json

Available subcommands: bundlerextension kernelspec lab labextension labhub
migrate nbconvert nbextension notebook run serverextension troubleshoot trust

Customizing the base image

The base image is meant to capture the minimum config and dependencies to run Jupyter Notebooks. Here we detail how to customize the base image to better suit your needs.

Resize the image

This image uses the default size set by Singularity (image size + 200M of padding) which is great for quick builds and pulls, but it is likely you'll need more space to accommodate your custom software stack.


In [3]:
ls -lsah | grep jupyter-base.img


669M -rwxr-xr-x.  1 vagrant vagrant 893M Sep 27 15:41 jupyter-base.img

When pulling from a Docker registry, you can use the --size flag to specify the built image size. Notice that Singularity isn't grabbing Docker layers from the registry, because the specified commit (ae885c0a6226) has already been pulled. Singularity Docker cache is located in $HOME/.singularity/docker.


In [9]:
singularity pull --size 3000 --name "jupyter-ext.img" docker://jupyter/base-notebook:ae885c0a6226


Initializing Singularity image subsystem
Opening image file: jupyter-ext.img
Creating 3000MiB image
Binding image to loop
Creating file system within image
Image is done: jupyter-ext.img
Docker image path: index.docker.io/jupyter/base-notebook:latest
Cache folder set to /home/vagrant/.singularity/docker
Importing: base Singularity environment
Importing: /home/vagrant/.singularity/docker/sha256:e0a742c2abfd5e2a6f8ed15b1c78e873cf9559b96a04204daf6de5df01e3124c.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:486cb8339a27635fa93dc47aa0c689326a0a7cce388966d16daf8d265436cf7f.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:dc6f0d824617ad8a5d1163a5b2084814665dd83156317ad06ccf14deb517a053.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:4f7a5649a30e3f318ce5d7e4dbcbbeb6c0938c4cbae4d4a641fe910562ff4978.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:672363445ad2c734e29221a6b47f4e614b5adc8a3cdca3364f62db2ed2bdff0c.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:b337aaee648d9f87e96fae8b24ae2dd887a2ded309b38dbee691fcdb040878cc.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:7f77b4eaa7ff7da43552cca1a34f9d16a1bbe90ba779d2c4355a4c4d1ab6dd0f.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:94e7bffb8046310f285abb93104a0cddd073c5a44ec7cfab05a3db6128fb3006.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:fa94652128009709e53b85cf57087d739108eaa98fd6e599343a32267deda620.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:79c35484f704c0792d062e5749c5df82fa85820bf072531daccefb532d88f294.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:f1756f88332d61a74c658fae7b6a5b8509e59b24e7f4ae7412bb183e23c17bc8.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:e73fef5319c18fa96bf5654b990f993622087a8caa56c2a5673db488a549ab1f.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:ea6e64c66b3a6af070c29a97433acaf94f875d99b4d43509f874686afa66a7d8.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:f3e50561218a351fb914efee8fb3bcb05eb619f70615520375e864b40a0227c4.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:f2de9a241e20a4ca75822db878a3eecf6afee5c4a96f099b76fe24306a51c1ca.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:6684df1dccb5994df682ca325871418c86770373ca27638919dff8dbb42b063a.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:1033de5a165ffa098da08b2da7325b577e5585d55bee04439033d0e72103bd64.tar.gz
Importing: /home/vagrant/.singularity/docker/sha256:ff59b2aa8f66f44d88ee7f218edd92bf0ced0d585b880ea6e9fc267100718f1b.tar.gz
Importing: /home/vagrant/.singularity/metadata/sha256:583daeeee9e135a869cbe39637eecb8a3b3790096af37bfdf5a2d98b51016110.tar.gz
Done. Container is at: jupyter-ext.img

In [3]:
singularity exec -e jupyter-ext.img jupyter -h


usage: jupyter [-h] [--version] [--config-dir] [--data-dir] [--runtime-dir]
               [--paths] [--json]
               [subcommand]

Jupyter: Interactive Computing

positional arguments:
  subcommand     the subcommand to launch

optional arguments:
  -h, --help     show this help message and exit
  --version      show the jupyter command's version and exit
  --config-dir   show Jupyter config dir
  --data-dir     show Jupyter data dir
  --runtime-dir  show Jupyter runtime dir
  --paths        show all Jupyter paths. Add --json for machine-readable
                 format.
  --json         output paths as machine-readable json

Available subcommands: bundlerextension kernelspec lab labextension labhub
migrate nbconvert nbextension notebook run serverextension troubleshoot trust

Installing software (the quick way)

By default Singularity containers mounted as read-only volumes, which means you won't be able to add content or install software (even as a privileged user), save for default or system-mounted paths. In order to add content you must run your Singularity command with the --writable flag.

For an interactive shell into your container, use the shell subcommand. The command below also passes the -e flag, which tells Singularity to strip the host environment before entering the container.


In [ ]:
sudo singularity shell -e --writable jupyter-ext.img


Singularity: Invoking an interactive shell within container...

Alternatively, you can use the exec subcommand to execute commands in your container without leaving your host environment.


In [10]:
singularity exec -e --writable jupyter-ext.img /opt/conda/bin/conda install -y matplotlib
singularity exec -e --writable jupyter-ext.img /opt/conda/bin/conda install -y seaborn


Fetching package metadata ...........
Solving package specifications: .

Package plan for installation in environment /opt/conda:

The following NEW packages will be INSTALLED:

    cycler:           0.10.0-py36_0 conda-forge
    dbus:             1.10.22-0     conda-forge
    expat:            2.2.1-0       conda-forge
    fontconfig:       2.12.1-4      conda-forge
    freetype:         2.7-1         conda-forge
    gettext:          0.19.7-1      conda-forge
    glib:             2.51.4-0      conda-forge
    gst-plugins-base: 1.8.0-0       conda-forge
    gstreamer:        1.8.0-2       conda-forge
    icu:              58.1-1        conda-forge
    jpeg:             9b-1          conda-forge
    libiconv:         1.14-4        conda-forge
    libpng:           1.6.28-0      conda-forge
    libxcb:           1.12-1        conda-forge
    libxml2:          2.9.5-0       conda-forge
    matplotlib:       2.0.2-py36_2  conda-forge
    mkl:              2017.0.3-0    defaults   
    numpy:            1.13.1-py36_0 defaults   
    pcre:             8.39-0        conda-forge
    pyqt:             5.6.0-py36_4  conda-forge
    pytz:             2017.2-py36_0 conda-forge
    qt:               5.6.2-3       conda-forge
    sip:              4.18-py36_1   conda-forge
    xorg-libxau:      1.0.8-3       conda-forge
    xorg-libxdmcp:    1.1.2-3       conda-forge

expat-2.2.1-0. 100% |################################| Time: 0:00:00   1.48 MB/s
gettext-0.19.7 100% |################################| Time: 0:00:00   9.27 MB/s
icu-58.1-1.tar 100% |################################| Time: 0:00:01  21.29 MB/s
jpeg-9b-1.tar. 100% |################################| Time: 0:00:00  25.47 MB/s
libiconv-1.14- 100% |################################| Time: 0:00:00  16.77 MB/s
mkl-2017.0.3-0 100% |################################| Time: 0:00:03  35.19 MB/s
pcre-8.39-0.ta 100% |################################| Time: 0:00:00   1.21 MB/s
xorg-libxau-1. 100% |################################| Time: 0:00:00  22.14 MB/s
xorg-libxdmcp- 100% |################################| Time: 0:00:00  20.33 MB/s
dbus-1.10.22-0 100% |################################| Time: 0:00:00   3.40 MB/s
glib-2.51.4-0. 100% |################################| Time: 0:00:00  13.82 MB/s
libpng-1.6.28- 100% |################################| Time: 0:00:00  68.13 MB/s
libxcb-1.12-1. 100% |################################| Time: 0:00:00  11.46 MB/s
libxml2-2.9.5- 100% |################################| Time: 0:00:00  12.21 MB/s
freetype-2.7-1 100% |################################| Time: 0:00:00  14.38 MB/s
gstreamer-1.8. 100% |################################| Time: 0:00:00  21.42 MB/s
fontconfig-2.1 100% |################################| Time: 0:00:00  15.21 MB/s
gst-plugins-ba 100% |################################| Time: 0:00:00  24.25 MB/s
numpy-1.13.1-p 100% |################################| Time: 0:00:00  36.10 MB/s
pytz-2017.2-py 100% |################################| Time: 0:00:00  48.89 MB/s
sip-4.18-py36_ 100% |################################| Time: 0:00:00   7.02 MB/s
cycler-0.10.0- 100% |################################| Time: 0:00:00  22.60 MB/s
qt-5.6.2-3.tar 100% |################################| Time: 0:00:02  20.00 MB/s
pyqt-5.6.0-py3 100% |################################| Time: 0:00:00  10.44 MB/s
matplotlib-2.0 100% |################################| Time: 0:00:00  21.28 MB/s
Fetching package metadata ...........
Solving package specifications: .

Package plan for installation in environment /opt/conda:

The following NEW packages will be INSTALLED:

    libgfortran: 3.0.0-1            defaults   
    pandas:      0.20.3-py36_1      conda-forge
    patsy:       0.4.1-py36_0       conda-forge
    scipy:       0.19.1-np113py36_0 defaults   
    seaborn:     0.8.1-py36_0       conda-forge
    statsmodels: 0.8.0-py36_0       conda-forge

libgfortran-3. 100% |################################| Time: 0:00:00  17.90 MB/s
scipy-0.19.1-n 100% |################################| Time: 0:00:01  35.76 MB/s
pandas-0.20.3- 100% |################################| Time: 0:00:01  16.39 MB/s
patsy-0.4.1-py 100% |################################| Time: 0:00:00  58.21 MB/s
statsmodels-0. 100% |################################| Time: 0:00:00  11.16 MB/s
seaborn-0.8.1- 100% |################################| Time: 0:00:00  55.75 MB/s

Now seaborn is installed in your image.


In [11]:
singularity exec -e jupyter-ext.img conda list | grep seaborn


seaborn                   0.8.1                    py36_0    conda-forge

Installing Software (the reproducible way)

Shelling into your container and making ad-hoc changes is excellent for debugging and initial development, but it is considered bad practice as the steps needed to construct your software environment are not captured and cannot be reproduced.

To make durable, reproducible changes you need to build a spec file from which you can bootstrap your container. Bootstrapping must be done by a privileged user


In [19]:
cat jupyter-bootstrapped.def


BootStrap: docker
From: jupyter/base-notebook

%environment
  export PATH=/opt/conda/bin:$PATH

%post
  export PATH=/opt/conda/bin:$PATH
  echo "Installing seaborn..."
  conda install matplotlib
  conda install seaborn

In [17]:
singularity create --force --size 2500 jupyter-bootstrapped.img
sudo /usr/local/bin/singularity bootstrap jupyter-bootstrapped.img jupyter-bootstrapped.def


Initializing Singularity image subsystem
Opening image file: jupyter-bootstrapped.img
Creating 2500MiB image
Binding image to loop
Creating file system within image
Image is done: jupyter-bootstrapped.img
Sanitizing environment
Building from bootstrap definition recipe
Adding base Singularity environment to container
Docker image path: index.docker.io/jupyter/base-notebook:latest
Cache folder set to /root/.singularity/docker
Exploding layer: sha256:e0a742c2abfd5e2a6f8ed15b1c78e873cf9559b96a04204daf6de5df01e3124c.tar.gz
Exploding layer: sha256:486cb8339a27635fa93dc47aa0c689326a0a7cce388966d16daf8d265436cf7f.tar.gz
Exploding layer: sha256:dc6f0d824617ad8a5d1163a5b2084814665dd83156317ad06ccf14deb517a053.tar.gz
Exploding layer: sha256:4f7a5649a30e3f318ce5d7e4dbcbbeb6c0938c4cbae4d4a641fe910562ff4978.tar.gz
Exploding layer: sha256:672363445ad2c734e29221a6b47f4e614b5adc8a3cdca3364f62db2ed2bdff0c.tar.gz
Exploding layer: sha256:b337aaee648d9f87e96fae8b24ae2dd887a2ded309b38dbee691fcdb040878cc.tar.gz
Exploding layer: sha256:7f77b4eaa7ff7da43552cca1a34f9d16a1bbe90ba779d2c4355a4c4d1ab6dd0f.tar.gz
Exploding layer: sha256:94e7bffb8046310f285abb93104a0cddd073c5a44ec7cfab05a3db6128fb3006.tar.gz
Exploding layer: sha256:fa94652128009709e53b85cf57087d739108eaa98fd6e599343a32267deda620.tar.gz
Exploding layer: sha256:79c35484f704c0792d062e5749c5df82fa85820bf072531daccefb532d88f294.tar.gz
Exploding layer: sha256:f1756f88332d61a74c658fae7b6a5b8509e59b24e7f4ae7412bb183e23c17bc8.tar.gz
Exploding layer: sha256:e73fef5319c18fa96bf5654b990f993622087a8caa56c2a5673db488a549ab1f.tar.gz
Exploding layer: sha256:ea6e64c66b3a6af070c29a97433acaf94f875d99b4d43509f874686afa66a7d8.tar.gz
Exploding layer: sha256:f3e50561218a351fb914efee8fb3bcb05eb619f70615520375e864b40a0227c4.tar.gz
Exploding layer: sha256:f2de9a241e20a4ca75822db878a3eecf6afee5c4a96f099b76fe24306a51c1ca.tar.gz
Exploding layer: sha256:6684df1dccb5994df682ca325871418c86770373ca27638919dff8dbb42b063a.tar.gz
Exploding layer: sha256:1033de5a165ffa098da08b2da7325b577e5585d55bee04439033d0e72103bd64.tar.gz
Exploding layer: sha256:ff59b2aa8f66f44d88ee7f218edd92bf0ced0d585b880ea6e9fc267100718f1b.tar.gz
Exploding layer: sha256:0ec5668b017f4b07564d4b53a6f4bd19c61dc4892342ed092c076c99ad065c2e.tar.gz
Running post scriptlet
+ export PATH=/opt/conda/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
+ echo Installing seaborn...
Installing seaborn...
+ conda install matplotlib
Fetching package metadata ...........
Solving package specifications: .

Package plan for installation in environment /opt/conda:

The following NEW packages will be INSTALLED:

    cycler:           0.10.0-py36_0 conda-forge
    dbus:             1.10.22-0     conda-forge
    expat:            2.2.1-0       conda-forge
    fontconfig:       2.12.1-4      conda-forge
    freetype:         2.7-1         conda-forge
    gettext:          0.19.7-1      conda-forge
    glib:             2.51.4-0      conda-forge
    gst-plugins-base: 1.8.0-0       conda-forge
    gstreamer:        1.8.0-2       conda-forge
    icu:              58.1-1        conda-forge
    jpeg:             9b-1          conda-forge
    libiconv:         1.14-4        conda-forge
    libpng:           1.6.28-0      conda-forge
    libxcb:           1.12-1        conda-forge
    libxml2:          2.9.5-0       conda-forge
    matplotlib:       2.0.2-py36_2  conda-forge
    mkl:              2017.0.3-0    defaults   
    numpy:            1.13.1-py36_0 defaults   
    pcre:             8.39-0        conda-forge
    pyqt:             5.6.0-py36_4  conda-forge
    pytz:             2017.2-py36_0 conda-forge
    qt:               5.6.2-3       conda-forge
    sip:              4.18-py36_1   conda-forge
    xorg-libxau:      1.0.8-3       conda-forge
    xorg-libxdmcp:    1.1.2-3       conda-forge

Proceed ([y]/n)? 
expat-2.2.1-0. 100% |################################| Time: 0:00:00   1.50 MB/s
gettext-0.19.7 100% |################################| Time: 0:00:00   7.85 MB/s
icu-58.1-1.tar 100% |################################| Time: 0:00:01  23.17 MB/s
jpeg-9b-1.tar. 100% |################################| Time: 0:00:00  20.14 MB/s
libiconv-1.14- 100% |################################| Time: 0:00:00  12.02 MB/s
mkl-2017.0.3-0 100% |################################| Time: 0:00:03  35.69 MB/s
pcre-8.39-0.ta 100% |################################| Time: 0:00:00   1.31 MB/s
xorg-libxau-1. 100% |################################| Time: 0:00:00  18.72 MB/s
xorg-libxdmcp- 100% |################################| Time: 0:00:00  23.09 MB/s
dbus-1.10.22-0 100% |################################| Time: 0:00:00   2.58 MB/s
glib-2.51.4-0. 100% |################################| Time: 0:00:00   9.27 MB/s
libpng-1.6.28- 100% |################################| Time: 0:00:00  55.61 MB/s
libxcb-1.12-1. 100% |################################| Time: 0:00:00  11.54 MB/s
libxml2-2.9.5- 100% |################################| Time: 0:00:00  17.10 MB/s
freetype-2.7-1 100% |################################| Time: 0:00:00  23.24 MB/s
gstreamer-1.8. 100% |################################| Time: 0:00:00   9.55 MB/s
fontconfig-2.1 100% |################################| Time: 0:00:00   5.83 MB/s
gst-plugins-ba 100% |################################| Time: 0:00:00   8.40 MB/s
numpy-1.13.1-p 100% |################################| Time: 0:00:00  41.53 MB/s
pytz-2017.2-py 100% |################################| Time: 0:00:00   4.68 MB/s
sip-4.18-py36_ 100% |################################| Time: 0:00:00   6.29 MB/s
cycler-0.10.0- 100% |################################| Time: 0:00:00  23.92 MB/s
qt-5.6.2-3.tar 100% |################################| Time: 0:00:02  21.20 MB/s
pyqt-5.6.0-py3 100% |################################| Time: 0:00:00  19.36 MB/s
matplotlib-2.0 100% |################################| Time: 0:00:00  20.04 MB/s
+ conda install seaborn
Fetching package metadata ...........
Solving package specifications: .

Package plan for installation in environment /opt/conda:

The following NEW packages will be INSTALLED:

    libgfortran: 3.0.0-1            defaults   
    pandas:      0.20.3-py36_1      conda-forge
    patsy:       0.4.1-py36_0       conda-forge
    scipy:       0.19.1-np113py36_0 defaults   
    seaborn:     0.8.1-py36_0       conda-forge
    statsmodels: 0.8.0-py36_0       conda-forge

Proceed ([y]/n)? 
libgfortran-3. 100% |################################| Time: 0:00:00   4.70 MB/s
scipy-0.19.1-n 100% |################################| Time: 0:00:01  29.71 MB/s
pandas-0.20.3- 100% |################################| Time: 0:00:01  12.90 MB/s
patsy-0.4.1-py 100% |################################| Time: 0:00:00  10.76 MB/s
statsmodels-0. 100% |################################| Time: 0:00:00  14.35 MB/s
seaborn-0.8.1- 100% |################################| Time: 0:00:00  55.54 MB/s
Adding environment to container
Finalizing Singularity container

In [18]:
singularity exec -e jupyter-bootstrapped.img conda list | grep seaborn


seaborn                   0.8.1                    py36_0    conda-forge

Using your environment in a notebook

This next section will cover basic strategies for using your very new, very custom software environment in a Jupyter Notebook.

Custom kernels

IPython notebooks interface with the system via an abstraction called Kernels. A wide variety of languages are supported via Kernels, and they can be customized by editing the kernelspec JSON file that defines them. Here is the default Python 3 kernelspec for reference:

"argv": [
  "python",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}"
 ],
 "display_name": "Python 3",
 "language": "python"
}

The argv key in this JSON object is the list that Jupyter uses to construct the kernel command when a notebook is started.

Remember the singularity exec subcommand? We can leverage that here to start a kernel in our container from a notebook server running in our host environment. All we need to do is prepend the components of the exec command to the argv list:

"argv": [
  "singularity",
  "exec",
  "-e",
  "jupyter-bootstrapped.img",
  "python",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}"
 ],
 "display_name": "Python 3",
 "language": "python"
}

Generating a new kernel

We'll start by generating a new kernelspec in a temporary location:


In [21]:
ipython kernel install --prefix /tmp


[InstallIPythonKernelSpecApp] WARNING | Installing to /tmp/share/jupyter/kernels, which is not in ['/home/vagrant/.local/share/jupyter/kernels', '/home/vagrant/venv-jupyter/share/jupyter/kernels', '/usr/local/share/jupyter/kernels', '/usr/share/jupyter/kernels', '/home/vagrant/.ipython/kernels']. The kernelspec may not be found.
Installed kernelspec python3 in /tmp/share/jupyter/kernels/python3

Editing the kernel

Now edit your kernelspec. An example can be found in this repo at singularity-kernel.json. Make sure to rename the kernelspec directory to avoid conflicts with existing kernels.


In [6]:
mv /tmp/share/jupyter/kernels/python3 /tmp/share/jupyter/kernels/seaborn
# Then edit /tmp/share/jupyter/kernels/seaborn/kernel.json (in our case we'll just copy the example)
cp singularity-kernel.json /tmp/share/jupyter/kernels/seaborn/kernel.json

Install the kernel

Finish by installing your new kernel to a location where your notebook will look when it starts. The --user flag specifies that you wish to install the kernel only for your user, and prevents the install from attempting to use sys.prefix.


In [7]:
jupyter kernelspec install --user /tmp/share/jupyter/kernels/seaborn


[InstallKernelSpec] Removing existing kernelspec in /home/vagrant/.local/share/jupyter/kernels/seaborn
[InstallKernelSpec] Installed kernelspec seaborn in /home/vagrant/.local/share/jupyter/kernels/seaborn

IPython Parallel

A similar approach can be used to execute ipengines on Summit, providing a mechanism for multi-node ipyparallel workflows that run in Singularity containers. This approach is detailed in the ipyparallel-using-singularity notebook in this directory.