Deploy the Jupyter Notebook container on VPS and use Nginx as the reverse proxy

Launch the container

I choose this image: jupyter/datascience-notebook.
Its quite large (7.53 GB). But it has many packages pre-installed:

  • For python: pandas, matplotlib, scipy, seaborn, scikit-learn, scikit-image, sympy, cython, patsy, statsmodel, cloudpickle, dill, numba, bokeh
  • For R: plyr, devtools, shiny, rmarkdown, forecast, rsqlite, reshape2, nycflights13, caret, rcurl, and randomforest

Create a password

Before starting the container, we need to prepare a hashed password. See this doc:
Running a notebook server

I choosed to open my local notebook and run this code:

In [1]: from notebook.auth import passwd
In [2]: passwd()
Enter password:
Verify password:
Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'

Run the container

Then run the container with the prepared hashed password.

docker run -d --name jupyter_notebook \
    -v {/path/to/folder}:/home/jovyan/work \
    -p {port}:8888 \
    jupyter/datascience-notebook \
    start-notebook.sh --NotebookApp.password='sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed' \
    --NotebookApp.base_url=/

Noted: If the {/path/to/folder} is under the root folder, then run chmod 777 /path/to/folder to allow creating the notebook.

Config the Nginx

Firstly, I thought I just need to proxy the ip and port to the DNS like proxy_pass http://host.of.notebook:port;.

But, then I found that the notebook is run and the chrome's console shows: failed: Error during WebSocket handshake: Unexpected response code: 400

The solution comes from this two post:
Websocket connection can't be made #412
Unable to establish websocket connection behind nginx reverse proxy #1311

For short, the conf file can be download from this Gist: nginx_reverse_for_notebook
The code is shown here. Just need to change the domain, hostname(ip) and port.

# /etc/nginx/sites-enabled/some.domain
# Change the server name {some.domain}
# Change the {host.of.notebook} and {port} in the following locations
server {
        listen 80;
        # Change the server name {some.domain}
        server_name some.domain;
        location / {
                # Change the {host.of.notebook} and {port}
                proxy_pass http://host.of.notebook:port;
                
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_read_timeout 86400;
        }

        client_max_body_size 50M;
        error_log /var/log/nginx/error.log;

        location ~* /(api/kernels/[^/]+/(channels|iopub|shell|stdin)|terminals/websocket)/? {
                # Change the {host.of.notebook} and {port}
                proxy_pass http://host.of.notebook:port;

                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

                proxy_http_version 1.1;
                
                #proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Upgrade "websocket";
                proxy_set_header Connection "upgrade";
                proxy_read_timeout 86400;
        }

}
  • Since the cloudflare has already allow websocket, so there is not need to turn off the cloud. The SSL is also working.