The design described here is for a very specific set of requirements; to create a reproducable and easy to configure environment to use Jupyter notebooks - one that is suitable for quickly experimenting and sharing ideas. Multiple virtual environments will be required for use across different projects.
Install Jupyter in a virtual environment
Create and setup the virtual environment:
python3 -m virtualenv env-name
source env-name/bin/activate
pip3 install jupyter
Run Jupyter Notebook:
jupyter notebook
While it is very easy to install modules by sourcing the correct virtualenv and using pip, there are a few drawbacks to this approach. Jupyter Notebook needs to be installed for each new environment created. If used as a server, this would become quite tedious and difficult to maintain - particularly if there is a need to use multiple environemnts at once with different users.
Install Jupyter as global system package
The Jupyter Notebook installation can be centralized using the system package manager. Virtual environments are created separately.
To install Jupyter Notebook on Debian based systems, use apt:
sudo apt install python3 python3-virtualenv jupyter
jupyter notebook
This is convenient because there is only one Jupyter Notebook installation - it can be accessed on localhost:8888. Next, virtual environments must created, and modules need to be installed in different virtual environments.
Commands
Commands can be run from within a notebook. A better idea of how Jupyter Notebook works is needed to understand this. Internally, it has a component called the IPython Kernel. This is responsible for communicating with the Python interpreter on the system. The ipykernel is included by default when Jupyter Notebook is installed, but it can also be installed independently.
Shell commands are provided by the operating system. They can be run by prefixing a line with an exclaimation mark:
!ls ; cd example_dir
This spawns a subprocess that exits upon completion; it is equivalent (on Linux) to:
import subprocess
subprocess.run(["bash", "-c", "ls ; cd example_dir"])
In this case, the ls will list the files in the current directory and cd will change the directory. The change in directory will not persist since the process exists after completion.
Magic commands (prefixed with a percentage sign) are provided by the IPython kernel. State changes persist because these commands are run inside the kernel process itself (and apply to the virtual environment in which the kernel was installed):
%cd example_dir
%page my_file.txt
%pip install bs4
This prints example_dir/my_file.txt and installs BeautifulSoup (a module for HTML parsing) in the current virtual environment.
For all available magic commands, refer to the IPython documentation or run:
%lsmagic
To install a module in the current virtual environment:
%pip install module_name
It is not possible to create and use a virtual environment directly within a notebook. For example:
!python -m virtualenv myvenv
!source myvenv/bin/activate
!pip install bs4
The first two commands succeed but the third fails with an externally-managed-environment error. This is because a new subprocess is created for each command - the sourced environment is not actually activated when pip is used. This can be resolved by chaining the command:
!python -m virtualenv myvenv; source myvenv/bin/activate; pip install bs4
BeautifulSoup now installs in the myvenv virtual environment, but the notebook does not use this environment. The virtual environment that the IPython kernel is running on is different to the one created - virtual environments cannot be nested!
Multiple Virtual Environments
To make a virtual environments available to Jupyter Notebook, the ipykernel module should be installed inside the virtual environment:
python3 -m virtualenv --system-site-packages env-name
source env-name/bin/activate
pip install ipykernel
python3 -m ipykernel install --user --name=env-name --display-name "Python (env-name)"
The
--system-site-packagesflag is particularly important if you depend on globally installed modules. For example,matplotlibhas to be specially packaged for ARM platforms like Raspberry Pi and installed using the system package manager. This flag ensures that these modules can be found.
Since the Jupyter Notebook installation is global, the above commands can be easily run from the interface by creating a new terminal.
When creating a new notebook, you can select Python (env-name) to use a particular virtual environment.
To list all kernels and remove a particular kernel:
jupyter-kernelspec list
jupyter-kernelspec uninstall env-name
There is still a problem - this is running directly on the server. If something goes wrong, it might affect other services. This is also not very easily portable.
Install Jupyter on Docker
Using docker, this can be containerized!
This Dockerfile uses Alpine linux as the base image and installs python, pip, virtualenv and jupyter-notebook using the Alpine package manager apk.
The last command starts the notebook. This probably isn’t very secure - you might want to modify the flags based on your security model.
FROM alpine:3.22
RUN apk add python3 py3-pip py3-virtualenv jupyter-notebook
EXPOSE 8888
CMD ["jupyter-notebook", "--ip=0.0.0.0", "--no-browser", "--allow-root", "--NotebookApp.token='your-very-long-and-secure-passphrase'"]
Now, creating the following docker-compose.yaml file and running docker compose up will build the image and expose Jupyter Notebook on localhost:4444. To manage the virtual environments, use the terminal and follow the steps in the previous section.
services:
jupyter:
container_name: jupyter
build: .
ports:
- 4444:8888
To reproduce this cionfiguration on a different service, just the Dockerfile and docker-compose.yaml need to be copied.