I use Jupyter Notebooks quite a lot for personal development, and to do this I set up an instance on a DigitalOcean droplet. However I also wanted to do something similar for some tasks at work which can access resources only available in our internal network. Since we use Windows that means I need to do something a little different. I could just use Anaconda to install and load it, but I find it a little clunky, I don't like how it tries to manage everything for you, I wanted a slightly nicer url than https://localhost:8000 and more importantly I wanted it to just be there without having to start anything up. So I set out to get Jupyter Notebook running on IIS using the ASP.NET Core Module V2 on http://notebook.local.
Make sure Python, IIS and the ASP.NET Core Hosting Bundle are installed. Open up a Terminal (I'm using bash, Powershell is probably file but I don't like it), create an new directory where our notebook application will be served, setup a venv there, and run the activation script, upgrade pip so that it doesn't complain at us and then finally install Jupyter Notebook:
$ cd ~/source/repos
$ mkdir notebook
$ python -m venv venv
$ source venv/Scripts/activate
$ pip install --upgrade pip
$ pip install notebook
$ jupyter notebook [I 20:39:42.322 NotebookApp] The port 8888 is already in use, trying another port. [I 20:39:42.328 NotebookApp] Serving notebooks from local directory: C:\Users\sean\source\external [I 20:39:42.329 NotebookApp] Jupyter Notebook 6.1.4 is running at: [I 20:39:42.329 NotebookApp] http://localhost:8889/?token=e27aa8d7754e1bb078dfdf48e7c55032ac3551360389fd65 [I 20:39:42.329 NotebookApp] or http://127.0.0.1:8889/?token=e27aa8d7754e1bb078dfdf48e7c55032ac3551360389fd65 [I 20:39:42.329 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). [C 20:39:42.476 NotebookApp] To access the notebook, open this file in a browser: file:///C:/Users/sean/AppData/Roaming/jupyter/runtime/nbserver-260-open.html Or copy and paste one of these URLs: http://localhost:8889/?token=e27aa8d7754e1bb078dfdf48e7c55032ac3551360389fd65 or http://127.0.0.1:8889/?token=e27aa8d7754e1bb078dfdf48e7c55032ac3551360389fd65
So here you can see why I'm doing all this - http://localhost:8888 isn't quite as nice as https://notebook.local. Next let's create a logs folder where our stdout logging will live and notebooks folder where we'll keep our notebook files.
$ mkdir logs notebooks
Next we'll create a Python script called launch.py that will launch Jupyter:
import os, sys from IPython.terminal.ipapp import launch_new_instance from IPython.lib import passwd import warnings # set the password you want to use notebook_password = "test" sys.argv.append("notebook") sys.argv.append("--IPKernelApp.pylab='inline'") sys.argv.append("--NotebookApp.ip='*'") sys.argv.append("--NotebookApp.port=" + os.environ["ASPNETCORE_PORT"]) sys.argv.append("--NotebookApp.open_browser=False") sys.argv.append("--NotebookApp.notebook_dir=./notebooks") sys.argv.append("--NotebookApp.password=" + passwd(notebook_password)) launch_new_instance()
This script is kind of interesting, but we'll go into it later once we've got everything up and running. Next we will need to create a Web.config file that'll be used by IIS and ANCMV2 to call this script with the Python executable and libraries from our venv. So let's create the following:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath=".\venv\Scripts\python.exe" arguments="launch.py" stdoutLogEnabled="true" stdoutLogFile="logs\stdout"> <environmentVariables> <environmentVariable name="PYTHONPATH" value="." /> </environmentVariables> </aspNetCore> </system.webServer> </configuration>
Next we'll set up the new Site in IIS - so open up IIS Manager (hit Start and start typing "Internet Information Services" - it'll appear in the suggestions) then:
- in the left-hand menu right click the top-level node, hit "Add Website..."
- in the dialog that appears call the site whatever you like
- in the "Physical Path" section click the "..." button, and choose the directory your notebooks live in
- in the "Binding" section type "notebook.local" in the "Host name" section
127.0.0.1 notebook.local
So now we have a Website bound to "notebook.local" running under IIS, which is configured to use ASP.NET Core Runtime Module V2 to launch a Python script that will run Jupyter Notebook. Let's fire up a browser and navigate to http://notebook.local and enter the password test:
It loads nicely so let's just create a simple Python 3 notebook to confirm everything works end-to-end:
Excellent. Ok so let's rewind a little bit and ask the question: why do we use a special script instead of having our Web.config file launch jupyter notebook directly? Well that would be ideal, but a bit of a hack required. Notice in launch.py that we grab the ASPNETCORE_PORT environment variable and use that as our port? Well that contains the port IIS wants to communicate with our app in - so we need to hook Jupyter up to listen on that port. While it is possible to call jupyter notebook --port 8080 to specify the port we want to use, it's actually not possible to use the ASPNETCORE_PORT variable in our Web.config. These environment variables are not expanded in our arguments="..." attribute, (see issue #117 in the aspnet/AspNetCoreModule repo on GitHub) - so we need to run a script that grabs this from the Environment and sets it up. It's a bit hacky, but it gets us where we need to be.