├── .env ├── .gitignore ├── LICENSE ├── README.md ├── binder ├── docker-compose.yml ├── jupyterhub ├── Dockerfile └── jupyterhub_config.py ├── jupyterlab ├── Dockerfile └── conda-activate.sh └── reverse-proxy └── traefik.toml /.env: -------------------------------------------------------------------------------- 1 | # A name for this Docker Compose application, it can be whatever you like 2 | COMPOSE_PROJECT_NAME=jupyterhub 3 | 4 | # The server where this JupyterHub server is hosted 5 | HOST=jupyter.ens.uvsq.fr -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *~ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Luca De Feo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JupyterHub deployment in use at Université de Versailles 2 | 3 | This is a [JupyterHub](https://jupyter.org/hub) deployment based on 4 | Docker currently in use at [Université de 5 | Versailles](https://jupyter.ens.uvsq.fr/). 6 | 7 | ## Features 8 | 9 | - Containerized single user Jupyter servers, using 10 | [DockerSpawner](https://github.com/jupyterhub/dockerspawner); 11 | - Central authentication to the University CAS server; 12 | - User data persistence; 13 | - HTTPS proxy. 14 | 15 | ## Learn more 16 | 17 | This deployment is described in depth in [this blog 18 | post](https://opendreamkit.org/2018/10/17/jupyterhub-docker/). 19 | 20 | ### Adapt to your needs 21 | 22 | This deployment is ready to clone and roll on your own server. Read 23 | the [blog 24 | post](https://opendreamkit.org/2018/10/17/jupyterhub-docker/) first, 25 | to be sure you understand the configuration. 26 | 27 | Then, if you like, clone this repository and apply (at least) the 28 | following changes: 29 | 30 | - In [`.env`](.env), set the variable `HOST` to the name of the server you 31 | intend to host your deployment on. 32 | - In [`reverse-proxy/traefik.toml`](reverse-proxy/traefik.toml), edit 33 | the paths in `certFile` and `keyFile` and point them to your own TLS 34 | certificates. Possibly edit the `volumes` section in the 35 | `reverse-proyx` service in 36 | [`docker-compose.yml`](docker-compose.yml). 37 | - In 38 | [`jupyterhub/jupyterhub_config.py`](jupyterhub/jupyterhub_config.py), 39 | edit the *"Authenticator"* section according to your institution 40 | authentication server. If in doubt, [read 41 | here](https://jupyterhub.readthedocs.io/en/stable/getting-started/authenticators-users-basics.html). 42 | 43 | Other changes you may like to make: 44 | 45 | - Edit [`jupyterlab/Dockerfile`](jupyterlab/Dockerfile) to include the 46 | software you like. Do not forget to change 47 | [`jupyterhub/jupyterhub_config.py`](jupyterhub/jupyterhub_config.py) 48 | accordingly, in particular the *"user data persistence"* section. 49 | 50 | ### Run! 51 | 52 | Once you are ready, build and launch the application with 53 | 54 | ``` 55 | docker-compose build 56 | docker-compose up -d 57 | ``` 58 | 59 | Read the [Docker Compose manual](https://docs.docker.com/compose/) to 60 | learn how to manage your application. 61 | 62 | ## Acknowledgements 63 | 64 | Work partially funded by the EU H2020 project 65 | [OpenDreamKit](https://opendreamkit.org/). 66 | -------------------------------------------------------------------------------- /binder: -------------------------------------------------------------------------------- 1 | jupyterlab/ -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | jupyterhub: 5 | build: jupyterhub 6 | image: jupyterhub_img 7 | container_name: jupyterhub 8 | volumes: 9 | - /var/run/docker.sock:/var/run/docker.sock 10 | - jupyterhub_data:/srv/jupyterhub 11 | environment: 12 | - DOCKER_JUPYTER_CONTAINER=jupyterlab_img 13 | - DOCKER_NETWORK_NAME=${COMPOSE_PROJECT_NAME}_default 14 | - HUB_IP=jupyterhub 15 | - HOST 16 | labels: 17 | - "traefik.enable=true" 18 | - "traefik.frontend.rule=Host:${HOST}" 19 | restart: on-failure 20 | 21 | jupyterlab: 22 | build: jupyterlab 23 | image: jupyterlab_img 24 | container_name: jupyterlab-throaway 25 | network_mode: none 26 | command: echo 27 | 28 | reverse-proxy: 29 | image: traefik 30 | container_name: reverse-proxy 31 | ports: 32 | - "80:80" 33 | - "443:443" 34 | - "8080:8080" 35 | volumes: 36 | - ./reverse-proxy/traefik.toml:/etc/traefik/traefik.toml 37 | - /etc/certs:/etc/certs 38 | - /var/run/docker.sock:/var/run/docker.sock 39 | restart: on-failure 40 | 41 | volumes: 42 | jupyterhub_data: 43 | -------------------------------------------------------------------------------- /jupyterhub/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyterhub/jupyterhub:0.9.3 2 | 3 | COPY jupyterhub_config.py . 4 | 5 | RUN wget https://raw.githubusercontent.com/jupyterhub/jupyterhub/0.9.3/examples/cull-idle/cull_idle_servers.py 6 | 7 | RUN pip install \ 8 | dockerspawner==0.10.0 \ 9 | jhub_cas_authenticator==1.0.0 10 | -------------------------------------------------------------------------------- /jupyterhub/jupyterhub_config.py: -------------------------------------------------------------------------------- 1 | # JupyterHub configuration 2 | # 3 | ## If you update this file, do not forget to delete the `jupyterhub_data` volume before restarting the jupyterhub service: 4 | ## 5 | ## docker volume rm jupyterhub_jupyterhub_data 6 | ## 7 | ## or, if you changed the COMPOSE_PROJECT_NAME to : 8 | ## 9 | ## docker volume rm _jupyterhub_data 10 | ## 11 | 12 | import os 13 | 14 | ## Generic 15 | c.JupyterHub.admin_access = True 16 | c.Spawner.default_url = '/lab' 17 | 18 | ## Authenticator 19 | from jhub_cas_authenticator.cas_auth import CASAuthenticator 20 | c.JupyterHub.authenticator_class = CASAuthenticator 21 | 22 | # The CAS URLs to redirect (un)authenticated users to. 23 | c.CASAuthenticator.cas_login_url = 'https://cas.uvsq.fr/login' 24 | c.CASLocalAuthenticator.cas_logout_url = 'https://cas.uvsq/logout' 25 | 26 | # The CAS endpoint for validating service tickets. 27 | c.CASAuthenticator.cas_service_validate_url = 'https://cas.uvsq.fr/serviceValidate' 28 | 29 | # The service URL the CAS server will redirect the browser back to on successful authentication. 30 | c.CASAuthenticator.cas_service_url = 'https://%s/hub/login' % os.environ['HOST'] 31 | 32 | c.Authenticator.admin_users = { 'lucadefe' } 33 | 34 | 35 | ## Docker spawner 36 | c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner' 37 | c.DockerSpawner.image = os.environ['DOCKER_JUPYTER_CONTAINER'] 38 | c.DockerSpawner.network_name = os.environ['DOCKER_NETWORK_NAME'] 39 | # See https://github.com/jupyterhub/dockerspawner/blob/master/examples/oauth/jupyterhub_config.py 40 | c.JupyterHub.hub_ip = os.environ['HUB_IP'] 41 | 42 | # user data persistence 43 | # see https://github.com/jupyterhub/dockerspawner#data-persistence-and-dockerspawner 44 | notebook_dir = os.environ.get('DOCKER_NOTEBOOK_DIR') or '/home/jovyan' 45 | c.DockerSpawner.notebook_dir = notebook_dir 46 | c.DockerSpawner.volumes = { 'jupyterhub-user-{username}': notebook_dir } 47 | 48 | # Other stuff 49 | c.Spawner.cpu_limit = 1 50 | c.Spawner.mem_limit = '10G' 51 | 52 | 53 | ## Services 54 | c.JupyterHub.services = [ 55 | { 56 | 'name': 'cull_idle', 57 | 'admin': True, 58 | 'command': 'python /srv/jupyterhub/cull_idle_servers.py --timeout=3600'.split(), 59 | }, 60 | ] 61 | -------------------------------------------------------------------------------- /jupyterlab/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyter/scipy-notebook:137a295ff71b 2 | 3 | LABEL maintainer="Luca De Feo " 4 | 5 | USER root 6 | 7 | # APT packages 8 | RUN apt-get update && \ 9 | apt-get install -y --no-install-recommends \ 10 | fonts-dejavu \ 11 | tzdata \ 12 | gfortran \ 13 | gcc \ 14 | scilab \ 15 | pari-gp \ 16 | libpari-dev \ 17 | sagemath \ 18 | sagemath-jupyter \ 19 | libgmp-dev \ 20 | && apt-get clean && \ 21 | rm -rf /var/lib/apt/lists/* 22 | 23 | USER $NB_UID 24 | 25 | # Conda packages 26 | # Sage conflicts with the latest jupyterhub, thus we must relax the pinning 27 | RUN conda install --quiet --yes \ 28 | 'r-base=3.4.1' \ 29 | 'r-irkernel=0.8*' \ 30 | 'r-plyr=1.8*' \ 31 | 'r-devtools=1.13*' \ 32 | 'r-tidyverse=1.1*' \ 33 | 'r-shiny=1.0*' \ 34 | 'r-rmarkdown=1.8*' \ 35 | 'r-forecast=8.2*' \ 36 | 'r-rsqlite=2.0*' \ 37 | 'r-reshape2=1.4*' \ 38 | 'r-nycflights13=0.2*' \ 39 | 'r-caret=6.0*' \ 40 | 'r-rcurl=1.95*' \ 41 | 'r-crayon=1.3*' \ 42 | 'r-randomforest=4.6*' \ 43 | 'r-htmltools=0.3*' \ 44 | 'r-sparklyr=0.7*' \ 45 | 'r-htmlwidgets=1.0*' \ 46 | 'r-hexbin=1.27*' \ 47 | 'jupyterhub' \ 48 | # 'sage=8.*' \ 49 | 'julia=1.0*' && \ 50 | conda clean -tipsy && \ 51 | fix-permissions $CONDA_DIR 52 | 53 | ENV CPATH=$CONDA_DIR/include 54 | 55 | RUN pip install \ 56 | pari_jupyter \ 57 | # PySingular jupyter_kernel_singular \ 58 | scilab-kernel && \ 59 | fix-permissions $CONDA_DIR 60 | 61 | # Fix SageMath kernel 62 | USER root 63 | RUN sed -i 's/"\/usr\/bin\/sage"/"env", "PATH=\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin", "\/usr\/bin\/sage"/' /usr/share/jupyter/kernels/sagemath/kernel.json 64 | USER $NB_UID 65 | 66 | # Add conda env hook 67 | COPY ./conda-activate.sh /usr/local/bin/before-notebook.d/ 68 | -------------------------------------------------------------------------------- /jupyterlab/conda-activate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source /opt/conda/bin/activate base 4 | -------------------------------------------------------------------------------- /reverse-proxy/traefik.toml: -------------------------------------------------------------------------------- 1 | debug = true 2 | 3 | logLevel = "ERROR" 4 | defaultEntryPoints = ["https","http"] 5 | 6 | [entryPoints] 7 | [entryPoints.http] 8 | address = ":80" 9 | [entryPoints.http.redirect] 10 | entryPoint = "https" 11 | [entryPoints.https] 12 | address = ":443" 13 | [entryPoints.https.tls] 14 | [[entryPoints.https.tls.certificates]] 15 | certFile = "/etc/certs/jupyter_ens_uvsq_fr.crt" 16 | keyFile = "/etc/certs/jupyter.ens.uvsq.fr.key" 17 | 18 | [docker] 19 | domain = "docker.local" 20 | watch = true 21 | 22 | [api] 23 | [api.statistics] 24 | recentErrors = 10 25 | --------------------------------------------------------------------------------