<ahref="#part-1-jupyterhub-and-jupyterlab"class="nav-link">Part 1: JupyterHub and JupyterLab</a><ulclass="nav section-nav flex-column">
<liclass="nav-item toc-entry toc-h3">
<ahref="#setup-the-jupyterhub-and-jupyterlab-in-a-virtual-environment"class="nav-link">Setup the JupyterHub and JupyterLab in a virtual environment</a>
</li>
<liclass="nav-item toc-entry toc-h3">
<ahref="#create-the-configuration-for-jupyterhub"class="nav-link">Create the configuration for JupyterHub</a>
<h1>Install JupyterHub and JupyterLab from the ground up<aclass="headerlink"href="#install-jupyterhub-and-jupyterlab-from-the-ground-up"title="Permalink to this headline">¶</a></h1>
<p>The combination of <aclass="reference external"href="https://jupyterhub.readthedocs.io/">JupyterHub</a> and <aclass="reference external"href="https://jupyterlab.readthedocs.io/">JupyterLab</a>
is a great way to make shared computing resources available to a group.</p>
<p>These instructions are a guide for a manual, ‘bare metal’ install of <aclass="reference external"href="https://jupyterhub.readthedocs.io/">JupyterHub</a>
and <aclass="reference external"href="https://jupyterlab.readthedocs.io/">JupyterLab</a>. This is ideal for running on a single server: build a beast
of a machine and share it within your lab, or use a virtual machine from any VPS or cloud provider.</p>
<p>This guide has similar goals to <aclass="reference external"href="https://the-littlest-jupyterhub.readthedocs.io/">The Littlest JupyterHub</a> setup
script. However, instead of bundling all these step for you into one installer, we will perform every step manually.
This makes it easy to customize any part (e.g. if you want to run other services on the same system and need to make them
work together), as well as giving you full control and understanding of your setup.</p>
<divclass="section"id="prerequisites">
<h2>Prerequisites<aclass="headerlink"href="#prerequisites"title="Permalink to this headline">¶</a></h2>
<p>Your own server with administrator (root) access. This could be a local machine, a remotely hosted one, or a cloud instance
or VPS. Each user who will access JupyterHub should have a standard user account on the machine. The install will be done
through the command line - useful if you log into your machine remotely using SSH.</p>
<p>This tutorial was tested on <strong>Ubuntu 18.04</strong>. No other Linux distributions have been tested, but the instructions
should be reasonably straightforward to adapt.</p>
</div>
<divclass="section"id="goals">
<h2>Goals<aclass="headerlink"href="#goals"title="Permalink to this headline">¶</a></h2>
<p>JupyterLab enables access to a multiple ‘kernels’, each one being a given environment for a given language. The most
common is a Python environment, for scientific computing usually one managed by the <codeclass="docutils literal notranslate"><spanclass="pre">conda</span></code> package manager.</p>
<p>This guide will set up JupyterHub and JupyterLab seperately from the Python environment. In other words, we treat
JupyterHub+JupyterLab as a ‘app’ or webservice, which will connect to the kernels available on the system. Specifically:</p>
<ulclass="simple">
<li><p>We will create an installation of JupyterHub and JupyterLab using a virtualenv under <codeclass="docutils literal notranslate"><spanclass="pre">/opt</span></code> using the system Python.</p></li>
<li><p>We will install conda globally.</p></li>
<li><p>We will create a shared conda environment which can be used (but not modified) by all users.</p></li>
<li><p>We will show how users can create their own private conda environments, where they can install whatever they like.</p></li>
</ul>
<p>The default JupyterHub Authenticator uses PAM to authenticate system users with their username and password. One can
<aclass="reference external"href="https://jupyterhub.readthedocs.io/en/stable/reference/authenticators.html#authenticators">choose the authenticator</a>
that best suits their needs. In this guide we will use the default
Authenticator because it makes it easy for everyone to manage data
in their home folder and to mix and match different services and access
methods (e.g. SSH) which all work using the
Linux system user accounts. Therefore, each user of JupyterHub will need
a standard system user account.</p>
<p>Another goal of this guide is to use system provided packages wherever possible. This has the advantage that these packages
get automatic patches and security updates (be sure to turn on automatic updates in Ubuntu). This means less maintenance
<h3>Setup the JupyterHub and JupyterLab in a virtual environment<aclass="headerlink"href="#setup-the-jupyterhub-and-jupyterlab-in-a-virtual-environment"title="Permalink to this headline">¶</a></h3>
<p>First we create a virtual environment under ‘/opt/jupyterhub’. The ‘/opt’ folder is where apps not belonging to the operating
system are <aclass="reference external"href="https://unix.stackexchange.com/questions/11544/what-is-the-difference-between-opt-and-usr-local">commonly installed</a>.
Both jupyterlab and jupyterhub will be installed into this virtualenv. Create it with the command:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Now we use pip to install the required Python packages into the new virtual environment. Be sure to install
<codeclass="docutils literal notranslate"><spanclass="pre">wheel</span></code> first. Since we are separating the user interface from the computing kernels, we don’t install
any Python scientific packages here. The only exception is <codeclass="docutils literal notranslate"><spanclass="pre">ipywidgets</span></code> because this is needed to allow connection
between interactive tools running in the kernel and the user interface.</p>
<p>Note that we use <codeclass="docutils literal notranslate"><spanclass="pre">/opt/jupyterhub/bin/python3</span><spanclass="pre">-m</span><spanclass="pre">pip</span><spanclass="pre">install</span></code> each time - this <aclass="reference external"href="https://snarky.ca/why-you-should-use-python-m-pip/">makes sure</a>
that the packages are installed to the correct virtual environment.</p>
<p>Perform the install using the following commands:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>JupyterHub also currently defaults to requiring <codeclass="docutils literal notranslate"><spanclass="pre">configurable-http-proxy</span></code>, which needs <codeclass="docutils literal notranslate"><spanclass="pre">nodejs</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">npm</span></code>. The versions
of these available in Ubuntu therefore need to be installed first (they are a bit old but this is ok for our needs):</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
<h3>Create the configuration for JupyterHub<aclass="headerlink"href="#create-the-configuration-for-jupyterhub"title="Permalink to this headline">¶</a></h3>
<p>Now we start creating configuration files. To keep everything together, we put all the configuration into the folder
created for the virtualenv, under <codeclass="docutils literal notranslate"><spanclass="pre">/opt/jupyterhub/etc/</span></code>. For each thing needing configuration, we will create a further
subfolder and necessary files.</p>
<p>First create the folder for the JupyterHub configuration and navigate to it:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Then generate the default configuration file</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>This will produce the default configuration file <codeclass="docutils literal notranslate"><spanclass="pre">/opt/jupyterhub/etc/jupyterhub/jupyterhub_config.py</span></code></p>
<p>You will need to edit the configuration file to make the JupyterLab interface by the default.
Set the following configuration option in your <codeclass="docutils literal notranslate"><spanclass="pre">jupyterhub_config.py</span></code> file:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Further configuration options may be found in the documentation.</p>
</div>
<divclass="section"id="setup-systemd-service">
<h3>Setup Systemd service<aclass="headerlink"href="#setup-systemd-service"title="Permalink to this headline">¶</a></h3>
<p>We will setup JupyterHub to run as a system service using Systemd (which is responsible for managing all services and
servers that run on startup in Ubuntu). We will create a service file in a suitable location in the virtualenv folder
and then link it to the system services. First create the folder for the service file:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Then create the following text file using your <aclass="reference external"href="https://micro-editor.github.io/">favourite editor</a> at</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Paste the following service unit definition into the file:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>This sets up the environment to use the virtual environment we created, tells Systemd how to start jupyterhub using
the configuration file we created, specifies that jupyterhub will be started as the <codeclass="docutils literal notranslate"><spanclass="pre">root</span></code> user (needed so that it can
start jupyter on behalf of other logged in users), and specifies that jupyterhub should start on boot after the network
is enabled.</p>
<p>Finally, we need to make systemd aware of our service file. First we symlink our file into systemd’s directory:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Then tell systemd to reload its configuration files</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>The service will start on reboot, but we can start it straight away using:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>…and check that it’s running using:</p>
<divclass="highlight-sh notranslate"><divclass="highlight"><preid="codecell14"><span></span>sudo systemctl status jupyterhub.service
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>You should now be already be able to access jupyterhub using <codeclass="docutils literal notranslate"><spanclass="pre"><your</span><spanclass="pre">servers</span><spanclass="pre">ip>:8000</span></code> (assuming you haven’t already set
up a firewall or something). However, when you log in the jupyter notebooks will be trying to use the Python virtualenv
that was created to install JupyterHub, this is not what we want. So on to part 2</p>
<h3>Install conda for the whole system<aclass="headerlink"href="#install-conda-for-the-whole-system"title="Permalink to this headline">¶</a></h3>
<p>We will use <codeclass="docutils literal notranslate"><spanclass="pre">conda</span></code> to manage Python environments. We will install the officially maintained <codeclass="docutils literal notranslate"><spanclass="pre">conda</span></code> packages for Ubuntu,
this means they will get automatic updates with the rest of the system. Setup repo for the official Conda debian packages,
instructions are copied from <aclass="reference external"href="https://docs.conda.io/projects/conda/en/latest/user-guide/install/rpm-debian.html">here</a>:</p>
<p>Install Anacononda public gpg key to trusted store</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Add Debian repo</p>
<divclass="highlight-sh notranslate"><divclass="highlight"><preid="codecell16"><span></span><spanclass="nb">echo</span><spanclass="s2">"deb [arch=amd64] https://repo.anaconda.com/pkgs/misc/debrepo/conda stable main"</span><spanclass="p">|</span> sudo tee /etc/apt/sources.list.d/conda.list
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>This will install conda into the folder <codeclass="docutils literal notranslate"><spanclass="pre">/opt/conda/</span></code>, with the conda command available at <codeclass="docutils literal notranslate"><spanclass="pre">/opt/conda/bin/conda</span></code>.</p>
<p>Finally, we can make conda more easily available to users by symlinking the conda shell setup script to the profile
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
<h3>Install a default conda environment for all users<aclass="headerlink"href="#install-a-default-conda-environment-for-all-users"title="Permalink to this headline">¶</a></h3>
<p>First create a folder for conda envs (might exist already):</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Then create a conda environment to your liking within that folder.
Here we have called it ‘python’ because it will
be the obvious default - call it whatever you like. You can install
whatever you like into this environment, but you MUST at least install <codeclass="docutils literal notranslate"><spanclass="pre">ipykernel</span></code>.</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Once your env is set up as desired, make it visible to Jupyter by installing the kernel spec. There are two options here:</p>
<p>1 ) Install into the JupyterHub virtualenv - this ensures it
overrides the default python version. It will only be visible
to the JupyterHub installation we have just created. This is useful to
avoid conda environments appearing where they are not expected.</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>2 ) Install it system-wide by putting it into <codeclass="docutils literal notranslate"><spanclass="pre">/usr/local</span></code>. It will be visible to any parallel install of JupyterHub or
JupyterLab, and will persist even if you later delete or modify the JupyterHub installation. This is useful if the kernels
might be used by other services, or if you want to modify the JupyterHub installation independently from the conda environments.</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
<h3>Setting up users’ own conda environments<aclass="headerlink"href="#setting-up-users-own-conda-environments"title="Permalink to this headline">¶</a></h3>
<p>There is relatively little for the administrator to do here, as users
will have to set up their own environments using the shell.
On login they should run <codeclass="docutils literal notranslate"><spanclass="pre">conda</span><spanclass="pre">init</span></code> or <codeclass="docutils literal notranslate"><spanclass="pre">/opt/conda/bin/conda</span></code>. The can then use conda to set up their environment,
although they must also install <codeclass="docutils literal notranslate"><spanclass="pre">ipykernel</span></code>. Once done, they can enable their kernel using:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>This will place the kernel spec into their home folder, where Jupyter will look for it on startup.</p>
<h2>Setting up a reverse proxy<aclass="headerlink"href="#setting-up-a-reverse-proxy"title="Permalink to this headline">¶</a></h2>
<p>The guide so far results in JupyterHub running on port 8000. It is not generally advisable to run open web services in
this way - instead, use a reverse proxy running on standard HTTP/HTTPS ports.</p>
<blockquote>
<div><p><strong>Important</strong>: Be aware of the security implications especially if you are running a server that is accessible from the open internet
i.e. not protected within an institutional intranet or private home/office network. You should set up a firewall and
HTTPS encryption, which is outside of the scope of this guide. For HTTPS consider using <aclass="reference external"href="https://letsencrypt.org/">LetsEncrypt</a>
or setting up a <aclass="reference external"href="https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-nginx-in-ubuntu-18-04">self-signed certificate</a>.
Firewalls may be set up using <codeclass="docutils literal notranslate"><spanclass="pre">ufw</span></code> or <codeclass="docutils literal notranslate"><spanclass="pre">firewalld</span></code> and combined with <codeclass="docutils literal notranslate"><spanclass="pre">fail2ban</span></code>.</p>
</div></blockquote>
<divclass="section"id="using-nginx">
<h3>Using Nginx<aclass="headerlink"href="#using-nginx"title="Permalink to this headline">¶</a></h3>
<p>Nginx is a mature and established web server and reverse proxy and is easy to install using <codeclass="docutils literal notranslate"><spanclass="pre">sudo</span><spanclass="pre">apt</span><spanclass="pre">install</span><spanclass="pre">nginx</span></code>.
Details on using Nginx as a reverse proxy can be found elsewhere. Here, we will only outline the additional steps needed
to setup JupyterHub with Nginx and host it at a given URL e.g. <codeclass="docutils literal notranslate"><spanclass="pre"><your-server-ip-or-url>/jupyter</span></code>.
This could be useful for example if you are running several services or web pages on the same server.</p>
<p>To achieve this needs a few tweaks to both the JupyterHub configuration and the Nginx config. First, edit the
configuration file <codeclass="docutils literal notranslate"><spanclass="pre">/opt/jupyterhub/etc/jupyterhub/jupyterhub_config.py</span></code> and add the line:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>where <codeclass="docutils literal notranslate"><spanclass="pre">/jupyter</span></code> will be the relative URL of the JupyterHub.</p>
<p>Now Nginx must be configured with a to pass all traffic from <codeclass="docutils literal notranslate"><spanclass="pre">/jupyter</span></code> to the the local address <codeclass="docutils literal notranslate"><spanclass="pre">127.0.0.1:8000</span></code>.
Add the following snippet to your nginx configuration file (e.g. <codeclass="docutils literal notranslate"><spanclass="pre">/etc/nginx/sites-available/default</span></code>).</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Also add this snippet before the <em>server</em> block:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>Nginx will not run if there are errors in the configuration, check your configuration using:</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
</a></div>
</div>
<p>If there are no errors, you can restart the Nginx service for the new configuration to take effect.</p>
<imgsrc="Install%20JupyterHub%20and%20JupyterLab%20from%20the%20ground%20up%20%E2%80%94%20JupyterHub%201.3.0%20documentation_files/copy-button.svg"alt="Copy to clipboard">
<h2>Getting started using your new JupyterHub<aclass="headerlink"href="#getting-started-using-your-new-jupyterhub"title="Permalink to this headline">¶</a></h2>
<p>Once you have setup JupyterHub and Nginx proxy as described, you can browse to your JupyterHub IP or URL
(e.g. if your server IP address is <codeclass="docutils literal notranslate"><spanclass="pre">123.456.789.1</span></code> and you decided to host JupyterHub at the <codeclass="docutils literal notranslate"><spanclass="pre">/jupyter</span></code> URL, browse
to <codeclass="docutils literal notranslate"><spanclass="pre">123.456.789.1/jupyter</span></code>). You will find a login page where you enter your Linux username and password. On login
you will be presented with the JupyterLab interface, with the file browser pane showing the contents of your users’