commit
07be477148
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
||||
.git
|
||||
.gitignore
|
||||
examples/
|
||||
internal/
|
||||
README.md
|
27
.env
Normal file
27
.env
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
# Use this file to set default values for environment variables specified in
|
||||
# docker-compose configuration file. docker-compose will substitute these
|
||||
# values for environment variables in the configuration file IF the variables
|
||||
# are not set in the shell environment.
|
||||
|
||||
# To override these values, set the shell environment variables.
|
||||
|
||||
# Name of Docker machine
|
||||
DOCKER_MACHINE_NAME=jupyterhub
|
||||
|
||||
# Name of Docker network
|
||||
DOCKER_NETWORK_NAME=jupyterhub-network
|
||||
|
||||
# Single-user Jupyter Notebook server container image
|
||||
DOCKER_CONTAINER_IMAGE=jupyter/scipy-notebook:2d878db5cbff
|
||||
|
||||
# Docker run command to use when spawning single-user containers
|
||||
DOCKER_SPAWN_CMD=start-singleuser.sh
|
||||
|
||||
# Name of JupyterHub container data volume
|
||||
DATA_VOLUME_HOST=jupyterhub-data
|
||||
|
||||
# Data volume container mount point
|
||||
DATA_VOLUME_CONTAINER=/data
|
65
.gitignore
vendored
65
.gitignore
vendored
@ -1,62 +1,3 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
#Ipython Notebook
|
||||
.ipynb_checkpoints
|
||||
secrets/
|
||||
userlist
|
||||
.DS_Store
|
||||
|
19
Dockerfile.jupyterhub
Normal file
19
Dockerfile.jupyterhub
Normal file
@ -0,0 +1,19 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
FROM jupyter/jupyterhub:master
|
||||
|
||||
# Install dockerspawner and its dependencies
|
||||
RUN /opt/conda/bin/pip install \
|
||||
-e git+https://github.com/jupyter/jupyterhub@62a5e9dbce86cbb8992def81600ff9881d515935#egg=jupyterhub \
|
||||
-e git+https://github.com/jupyter/oauthenticator@011f6ea25c6bafca087d94a6c73d24dcbb0bf80e#egg=oauthenticator \
|
||||
-e git+https://github.com/jupyter/dockerspawner@3c5e36bc96a252a04bb7700fdb009bd572996f3a#egg=dockerspawner
|
||||
|
||||
# Copy TLS certificate and key
|
||||
ENV SSL_CERT /srv/jupyterhub/secrets/jupyterhub.cer
|
||||
ENV SSL_KEY /srv/jupyterhub/secrets/jupyterhub.key
|
||||
COPY ./secrets/*.cer $SSL_CERT
|
||||
COPY ./secrets/*.key $SSL_KEY
|
||||
RUN chmod 700 /srv/jupyterhub/secrets && \
|
||||
chmod 600 /srv/jupyterhub/secrets/*
|
||||
|
||||
COPY ./userlist /srv/jupyterhub/userlist
|
27
LICENSE
27
LICENSE
@ -1,27 +0,0 @@
|
||||
Copyright (c) 2016, JupyterHub
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of jupyterhub-deploy-docker nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
60
LICENSE.md
Normal file
60
LICENSE.md
Normal file
@ -0,0 +1,60 @@
|
||||
# Licensing terms
|
||||
|
||||
This project is licensed under the terms of the Modified BSD License
|
||||
(also known as New or Revised or 3-Clause BSD), as follows:
|
||||
|
||||
- Copyright (c) 2001-2015, IPython Development Team
|
||||
- Copyright (c) 2015-, Jupyter Development Team
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
Neither the name of the Jupyter Development Team nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
## About the Jupyter Development Team
|
||||
|
||||
The Jupyter Development Team is the set of all contributors to the Jupyter project.
|
||||
This includes all of the Jupyter subprojects.
|
||||
|
||||
The core team that coordinates development on GitHub can be found here:
|
||||
https://github.com/jupyter/.
|
||||
|
||||
## Our Copyright Policy
|
||||
|
||||
Jupyter uses a shared copyright model. Each contributor maintains copyright
|
||||
over their contributions to Jupyter. But, it is important to note that these
|
||||
contributions are typically only changes to the repositories. Thus, the Jupyter
|
||||
source code, in its entirety is not the copyright of any single person or
|
||||
institution. Instead, it is the collective copyright of the entire Jupyter
|
||||
Development Team. If individual contributors want to maintain a record of what
|
||||
changes/contributions they have specific copyright on, they should indicate
|
||||
their copyright in the commit message of the change, when they commit the
|
||||
change to one of the Jupyter repositories.
|
||||
|
||||
With this in mind, the following banner should be used in any source code file
|
||||
to indicate the copyright and license terms:
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
247
README.md
247
README.md
@ -1,2 +1,247 @@
|
||||
# jupyterhub-deploy-docker
|
||||
Reference deployment of JupyterHub with docker
|
||||
|
||||
This repository provides a reference deployment of [JupyterHub](https://github.com/jupyter/jupyterhub), a multi-user [Jupyter Notebook](http://jupyter.org/) environment, on a **single host** using [Docker](https://docs.docker.com).
|
||||
|
||||
This deployment:
|
||||
|
||||
* Runs the [JupyterHub components](https://jupyterhub.readthedocs.org/en/latest/getting-started.html#overview) in a Docker container on the host
|
||||
* Uses [DockerSpawner](https://github.com/jupyter/dockerspawner) to spawn single-user Jupyter Notebook servers in separate Docker containers on the same host
|
||||
* Persists JupyterHub data in a Docker volume on the host
|
||||
* Persists user notebook directories in Docker volumes on the host
|
||||
* Uses [OAuthenticator](https://github.com/jupyter/oauthenticator) and [GitHub OAuth](https://developer.github.com/v3/oauth/) to authenticate users
|
||||
|
||||

|
||||
|
||||
## Use Cases
|
||||
|
||||
Possible use cases for this deployment may include, but are not limited to:
|
||||
|
||||
* A JupyterHub demo environment that you can spin up relatively quickly.
|
||||
* A multi-user Jupyter Notebook environment for small classes, teams, or departments.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This deployment is **NOT** intended for a production environment.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* This deployment uses Docker for all the things. It assumes that you wiil be using [Docker Machine](https://docs.docker.com/machine/overview/) and [Docker Compose](https://docs.docker.com/compose/overview/) on a local workstation or laptop to create, build, and run Docker images on a remote host. It requires [Docker Toolbox](https://www.docker.com/products/docker-toolbox) 1.11.0 or higher. See the [installation instructions](https://docs.docker.com/engine/installation/) for your environment.
|
||||
* This example configures JupyterHub for HTTPS connections (the default). As such, you must provide TLS certificate chain and key files to the JupyterHub server. If you do not have your own certificate chain and key, you can either [create self-signed versions](https://jupyter-notebook.readthedocs.org/en/latest/public_server.html#using-ssl-for-encrypted-communication), or obtain real ones from [Let's Encrypt](https://letsencrypt.org) (see the [letsencrypt example](examples/letsencrypt/README.md) for instructions).
|
||||
|
||||
## Create a Docker Machine
|
||||
|
||||
Use [Docker Machine](https://docs.docker.com/machine/) to provision a new host, or to connect to an existing host. In either case, the result will be a "machine" configured on your local workstation or laptop that represents the remote host. After you activate the machine on your local workstation, when you run `docker` commands locally, Docker Machine will execute them on the remote host for you.
|
||||
|
||||
### Provision a new host
|
||||
|
||||
Docker Machine can provision new hosts on various platforms using one of its [supported drivers](https://docs.docker.com/machine/drivers/). When provisioning a host, Docker Machine will automatically install the latest version of [Docker Engine](https://www.docker.com/products/docker-engine) on the host. It will also generate local TLS certificate and key files to connect to the host and authenticate with the `docker` daemon on the host.
|
||||
|
||||
In the following example, we provision a new virtual server on [IBM SoftLayer](https://www.softlayer.com/promo/freeCloud/freecloud). (You can provision similarly on RackSpace, AWS, and other hosting providers). We set `DRIVER_OPTS` to the SoftLayer-specific options.
|
||||
|
||||
```
|
||||
# Set Docker Machine SoftLayer driver options
|
||||
export DRIVER_OPTS="--driver softlayer \
|
||||
--softlayer-api-key <my_softlayer_api_key> \
|
||||
--softlayer-user <my_softlayer_username> \
|
||||
--softlayer-domain mydomain \
|
||||
--softlayer-cpu 16 \
|
||||
--softlayer-memory 65536 \
|
||||
--softlayer-disk-size 100 \
|
||||
--softlayer-region wdc01"
|
||||
|
||||
# Create a machine named jupyterhub
|
||||
docker-machine create $DRIVER_OPTS jupyterhub
|
||||
|
||||
```
|
||||
|
||||
### Connect to an existing host
|
||||
|
||||
This example configures a "machine" on your local workstation that connects to an existing remote host at IP address `10.0.0.10`. To do this, you must use Docker Machine's `generic` driver, and your local workstation must have a private SSH key that allows you to perform password-less login to the host.
|
||||
|
||||
Substitute your own IP address and path to SSH key in the `DRIVER_OPTS` below. Note that when you run the `docker-machine create` command, Docker Machine will install and configure the latest Docker Engine on the remote host (or upgrade the Docker Engine on the host if it is already installed).
|
||||
|
||||
```
|
||||
# Use the generic driver to create a Docker machine that
|
||||
# controls the Docker daemon on an existing host
|
||||
export DRIVER_OPTS="--driver generic \
|
||||
--generic-ip-address 10.0.0.10 \
|
||||
--generic-ssh-key /Users/jtyberg/.ssh/myhost_rsa.pem"
|
||||
|
||||
# Create a machine named jupyterhub
|
||||
docker-machine create $DRIVER_OPTS jupyterhub
|
||||
```
|
||||
|
||||
## Activate Docker Machine
|
||||
|
||||
You must set specific `DOCKER_*` environment variables to tell `docker` that it should run commands against a particular machine. The remainder of this document assumes that the machine called `jupyterhub` is active, and therefore, all `docker` commands will run on the `jupyterhub` remote host.
|
||||
|
||||
To set the `jupyterhub` machine as active:
|
||||
|
||||
```
|
||||
eval "$(docker-machine env jupyterhub)"
|
||||
```
|
||||
|
||||
All this does is set the right environment variables:
|
||||
|
||||
```
|
||||
env|grep DOCKER
|
||||
|
||||
DOCKER_HOST=tcp://10.0.0.10:2376
|
||||
DOCKER_MACHINE_NAME=jupyterhub
|
||||
DOCKER_TLS_VERIFY=1
|
||||
DOCKER_CERT_PATH=/Users/jtyberg/.docker/machine/machines/jupyterhub
|
||||
```
|
||||
|
||||
|
||||
To see which machine is active:
|
||||
|
||||
```
|
||||
docker-machine active
|
||||
```
|
||||
|
||||
## Create a Docker Network
|
||||
|
||||
Create a Docker network for inter-container communication. The benefits of using a Docker network are:
|
||||
|
||||
* container isolation - only the containers on the network can access one another
|
||||
* name resolution - Docker daemon runs an embedded DNS server to provide automatic service discovery for containers connected to user-defined networks. This allows us to access containers on the same network by name.
|
||||
|
||||
Here we create a Docker network named `jupyterhub-network`. Later, we will configure the JupyterHub and single-user Jupyter Notebook containers to run attached to this network.
|
||||
|
||||
```
|
||||
docker network create jupyterhub-network
|
||||
```
|
||||
|
||||
## Setup GitHub Authentication
|
||||
|
||||
This deployment uses GitHub OAuth to authenticate users. It requires that you create a [GitHub application](https://github.com/settings/applications/new). You will need to specify an OAuth callback URL in the following form:
|
||||
|
||||
```
|
||||
https://<myhost.mydomain>/hub/oauth_callback
|
||||
```
|
||||
|
||||
You must pass the secrets that GitHub provides for your application to JupyterHub at runtime. You can do this by setting the `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, and `OAUTH_CALLBACK_URL` environment variables when you run the JupyterHub container, or you can add them to the `.env` file in the root directory of this repository. For example,
|
||||
|
||||
```
|
||||
GITHUB_CLIENT_ID=<github_client_id>
|
||||
GITHUB_CLIENT_SECRET=<github_client_secret>
|
||||
OAUTH_CALLBACK_URL=https://<myhost.mydomain>/hub/oauth_callback
|
||||
```
|
||||
|
||||
**Note:** The `.env` file is a special file that Docker Compose uses to lookup environment variables. If you choose to place the GitHub secrets in this file, you should ensure that this file remains private (e.g., do not commit the secrets to source control).
|
||||
|
||||
## Build the JupyterHub Docker image
|
||||
|
||||
Configure JupyterHub and build it into a Docker image.
|
||||
|
||||
1. Copy your TLS certificate and key to a directory named `secrets` within this repository directory. These will be added to the Docker image at build time.
|
||||
|
||||
```
|
||||
mkdir -p secrets
|
||||
cp jupyterhub.cer jupyterhub.key secrets/
|
||||
```
|
||||
|
||||
1. Create a `userlist` file with a list of authorized users. At a minimum, this file should contain a single admin user. The username should be a GitHub username. For example:
|
||||
|
||||
```
|
||||
jtyberg admin
|
||||
```
|
||||
|
||||
The admin user will have the ability to add more users in the JupyterHub admin console.
|
||||
|
||||
1. Build the JupyterHub Docker image. For convenience, this repo provides a `hub.sh` script that wraps [docker-compose](https://docs.docker.com/compose/reference/), so you can run it with the docker-compose [command line arguments](https://docs.docker.com/compose/reference/overview/). To build the JupyterHub image on the active Docker machine host, run:
|
||||
|
||||
```
|
||||
./hub.sh build
|
||||
```
|
||||
|
||||
## Create a JupyterHub Data Volume
|
||||
|
||||
Create a Docker volume to persist JupyterHub data. This volume will reside on the host machine. Using a volume allows user lists, cookies, etc., to persist across JupyterHub container restarts.
|
||||
|
||||
```
|
||||
docker volume create --name jupyterhub-data
|
||||
```
|
||||
|
||||
## Pull the Jupyter Notebook Image
|
||||
|
||||
Pull the Jupyter Notebook Docker image that you would like JupyterHub to spawn for each user.
|
||||
|
||||
Note: Even though Docker will pull the image to the host the first time a user container is spawned, JupyterHub may timeout if the image is large, so it's better to do it beforehand.
|
||||
|
||||
This deployment uses the [jupyter/scipy-notebook](https://hub.docker.com/r/jupyter/scipy-notebook/) Docker image, which is built from the `scipy-notebook` [Docker stacks](https://github.com/jupyter/docker-stacks).
|
||||
|
||||
Note that the Docker stacks `*-notebook` images tagged `2d878db5cbff` include the `start-singleuser.sh` script required to start a single-user instance of the Notebook server that is compatible with JupyterHub.
|
||||
|
||||
```
|
||||
docker pull jupyter/scipy-notebook:2d878db5cbff
|
||||
```
|
||||
|
||||
Note: If you choose to use a different container image, be sure to set the `DOCKER_CONTAINER_IMAGE` environment variable either in the shell you use to launch JupyterHub or in the `.env` file.
|
||||
|
||||
## Run the JupyterHub container
|
||||
|
||||
Run the JupyterHub container on the host.
|
||||
|
||||
To run the JupyterHub container in detached mode:
|
||||
|
||||
```
|
||||
./hub.sh up -d
|
||||
```
|
||||
|
||||
Once the container is running, you should be able to access the JupyterHub console at
|
||||
|
||||
```
|
||||
https://myhost.mydomain
|
||||
```
|
||||
|
||||
To bring down the JupyterHub container:
|
||||
|
||||
```
|
||||
./hub.sh down
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### How can I view the logs for JupyterHub or users' Notebook servers?
|
||||
|
||||
Use `docker logs <container>`. For example, to view the logs of the `jupyterhub` container
|
||||
|
||||
```
|
||||
docker logs jupyterhub
|
||||
```
|
||||
|
||||
### How can I backup a user's notebook directory?
|
||||
|
||||
There are multiple ways to [backup and restore](https://docs.docker.com/engine/userguide/containers/dockervolumes/#backup-restore-or-migrate-data-volumes) data in Docker containers.
|
||||
|
||||
Suppose you have the following running containers:
|
||||
|
||||
```
|
||||
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Names}}"
|
||||
|
||||
CONTAINER ID IMAGE NAMES
|
||||
bc02dd6bb91b jupyter/minimal-notebook jupyter-jtyberg
|
||||
7b48a0b33389 jupyterhub jupyterhub
|
||||
```
|
||||
|
||||
In this deployment, the user's notebook directories (`/home/jovyan/work`) are backed by Docker volumes.
|
||||
|
||||
```
|
||||
docker inspect -f '{{ .Mounts }}' jupyter-jtyberg
|
||||
|
||||
[{jtyberg /var/lib/docker/volumes/jtyberg/_data /home/jovyan/work local rw true rprivate}]
|
||||
```
|
||||
|
||||
We can backup the user's notebook directory by running a separate container that mounts the user's volume and creates a tarball of the directory.
|
||||
|
||||
```
|
||||
docker run --rm \
|
||||
-u root \
|
||||
-v /tmp:/backups \
|
||||
-v jtyberg:/notebooks \
|
||||
jupyter/minimal-notebook \
|
||||
tar cvf /backups/jtyberg-backup.tar /notebooks
|
||||
```
|
||||
|
||||
The above command creates a tarball in the `/tmp` directory on the host.
|
52
docker-compose.yml
Normal file
52
docker-compose.yml
Normal file
@ -0,0 +1,52 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
# JupyterHub docker-compose configuration file
|
||||
version: "2"
|
||||
|
||||
services:
|
||||
hub:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.jupyterhub
|
||||
image: jupyterhub
|
||||
container_name: jupyterhub
|
||||
volumes:
|
||||
# Bind Docker binary from host machine so we can invoke Docker commands
|
||||
# from inside container
|
||||
- "/usr/local/bin/docker:/usr/local/bin/docker:ro"
|
||||
# Bind Docker TLS certs from host machine so we can authenticate with the
|
||||
# daemon on the host (DOCKER_HOST should be set to host's IP)
|
||||
- "/etc/docker:/etc/docker:ro"
|
||||
# Bind Docker volume on host for JupyterHub database and cookie secrets
|
||||
- "data:${DATA_VOLUME_CONTAINER}"
|
||||
ports:
|
||||
- "443:443"
|
||||
environment:
|
||||
# Pass DOCKER_HOST to container to allow it to connect to daemon on host
|
||||
DOCKER_HOST: ${DOCKER_HOST}
|
||||
# Locations of TLS certificate and key needed to auth with daemon on host
|
||||
DOCKER_TLS_CERT: "/etc/docker/server.pem"
|
||||
DOCKER_TLS_KEY: "/etc/docker/server-key.pem"
|
||||
# All containers will join this network
|
||||
DOCKER_NETWORK_NAME: ${DOCKER_NETWORK_NAME}
|
||||
# JupyterHub will spawn this image for users
|
||||
DOCKER_CONTAINER_IMAGE: ${DOCKER_CONTAINER_IMAGE}
|
||||
# Using this run command (optional)
|
||||
DOCKER_SPAWN_CMD: ${DOCKER_SPAWN_CMD}
|
||||
# Required to authenticate users using GitHub OAuth
|
||||
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
|
||||
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
|
||||
OAUTH_CALLBACK_URL: ${OAUTH_CALLBACK_URL}
|
||||
command: >
|
||||
jupyterhub -f /srv/jupyterhub/jupyterhub_config.py
|
||||
|
||||
volumes:
|
||||
data:
|
||||
external:
|
||||
name: ${DATA_VOLUME_HOST}
|
||||
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: ${DOCKER_NETWORK_NAME}
|
21
examples/custom-notebook-server/Dockerfile
Normal file
21
examples/custom-notebook-server/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
# Pin to version of notebook image that includes start-singleuser.sh script
|
||||
FROM jupyter/scipy-notebook:2d878db5cbff
|
||||
|
||||
# Install packages in default Python 3 environment
|
||||
RUN pip install \
|
||||
beautifulsoup4==4.4.*
|
||||
|
||||
# Install packages in Python 2 environment
|
||||
RUN $CONDA_DIR/envs/python2/bin/pip install \
|
||||
beautifulsoup4==4.4.*
|
||||
|
||||
# Use custom startup script
|
||||
USER root
|
||||
COPY docker-entrypoint.sh /srv/docker-entrypoint.sh
|
||||
ENTRYPOINT ["tini", "--", "/srv/docker-entrypoint.sh"]
|
||||
CMD ["start-singleuser.sh"]
|
||||
|
||||
USER jovyan
|
28
examples/custom-notebook-server/README.md
Normal file
28
examples/custom-notebook-server/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Custom Jupyter Notebook Server Image
|
||||
|
||||
This is an example of using a custom Jupyter Notebook server Docker image with JupyterHub. The `Dockerfile` builds from the `jupyter/scipy-notebook` image, but customizes the image in the following ways:
|
||||
|
||||
* installs an additional Python package to make it available to notebooks
|
||||
* uses a custom entrypoint script that copies sample notebooks to the user's notebook directory before executing the run command that was provided to the container
|
||||
|
||||
## Build the Image
|
||||
|
||||
Build and tag the image using the `Dockerfile` in this directory.
|
||||
|
||||
```
|
||||
docker build -t my-custom-notebook .
|
||||
```
|
||||
|
||||
## Run JupyterHub Container
|
||||
|
||||
To have JupyterHub spawn the `my-custom-notebook` image for single-user Notebook servers, set the `DOCKER_CONTAINER_IMAGE` environment variable to the image name when you run the JupyterHub container. For example, run the following **from the root directory** of this repository:
|
||||
|
||||
```
|
||||
export DOCKER_CONTAINER_IMAGE=my-custom-notebook
|
||||
|
||||
# bring down the JupyterHub container, if running
|
||||
./hub.sh down
|
||||
|
||||
# bring it back up
|
||||
./hub.sh up -d
|
||||
```
|
20
examples/custom-notebook-server/docker-entrypoint.sh
Executable file
20
examples/custom-notebook-server/docker-entrypoint.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
set -e
|
||||
|
||||
# If the run command is the default, do some initialization first
|
||||
if [ "$(which "$1")" = "/usr/local/bin/start-singleuser.sh" ]; then
|
||||
# Clone sample notebooks to user's notebook directory. Assume $NB_USER's work
|
||||
# directory if notebook directory not explicitly set. `git clone` will fail
|
||||
# if target directory already exists and is not empty, which likely means
|
||||
# that we've already done it, so just ignore.
|
||||
: ${NOTEBOOK_DIR:=/home/$NB_USER/work}
|
||||
git clone https://gist.github.com/parente/facb555dfbae28e817e0 \
|
||||
--depth 1 \
|
||||
"$NOTEBOOK_DIR/notebook_count" || true
|
||||
fi
|
||||
|
||||
# Run the command provided
|
||||
exec "$@"
|
47
examples/letsencrypt/README.md
Normal file
47
examples/letsencrypt/README.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Let's Encrypt
|
||||
|
||||
This example includes a Docker Compose configuration file that you can use to deploy [JupyterHub](https://github.com/jupyter/jupyterhub) with TLS certificate and key files generated by [Let's Encrypt](https://letsencrypt.org).
|
||||
|
||||
The `docker-compose.yml` configuration file in this example extends the JupyterHub service defined in the `docker-compose.yml` file in the root directory of this repository.
|
||||
|
||||
When you run the JupyterHub Docker container using the configuration file in this directory, Docker mounts an additional volume containing the Let's Encrypt TLS certificate and key files, and overrides the `SSL_CERT` and `SSL_KEY` environment variables to point to these files.
|
||||
|
||||
## Create a secrets volume
|
||||
|
||||
This example stores the Let's Encrypt TLS certificate and key files in a Docker volume, and mounts the volume to the JupyterHub container at runtime.
|
||||
|
||||
Create a volume to store the certificate and key files.
|
||||
|
||||
```
|
||||
# Activate Docker machine where JupyterHub will run
|
||||
eval "$(docker-machine env jupyterhub)"
|
||||
|
||||
docker volume create --name jupyterhub-secrets
|
||||
```
|
||||
|
||||
## Generate Let's Encrypt certificate and key
|
||||
|
||||
Run the `letsencrypt.sh` script to create a TLS full-chain certificate and key.
|
||||
|
||||
The script downloads and runs the `letsencrypt` Docker image to create a full-chain certificate and private key, and stores the files in a Docker volume. You must provide a valid, routable, fully-qualified domain name (you must own it), and you must activate the Docker machine host that the domain points to before you run this script. You must also provide a valid email address and the name of the volume you created above.
|
||||
|
||||
_Notes:_ The script hard codes several `letsencrypt` options, one of which automatically agrees to the Let's Encrypt Terms of Service.
|
||||
|
||||
```
|
||||
# Activate Docker machine where JupyterHub will run
|
||||
eval "$(docker-machine env jupyterhub)"
|
||||
|
||||
./letsencrypt.sh \
|
||||
--domain myhost.mydomain \
|
||||
--email me@mydomain \
|
||||
--volume jupyterhub-secrets
|
||||
```
|
||||
|
||||
## Run JupyterHub container
|
||||
|
||||
To run the JupyterHub container using the configuration in this directory, run the `hub.sh` script **from the root directory** of this repository and specify the `docker-compose.yml` file in this directory. Set the `SECRETS_VOLUME` environment variable to the name of the Docker volume containing the TLS certificate and key files.
|
||||
|
||||
```
|
||||
SECRETS_VOLUME=jupyterhub-secrets \
|
||||
./hub.sh -f examples/letsencrypt/docker-compose.yml up -d
|
||||
```
|
38
examples/letsencrypt/docker-compose.yml
Normal file
38
examples/letsencrypt/docker-compose.yml
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
# JupyterHub docker-compose configuration that uses Let's Encrypt TLS
|
||||
# certificate and key.
|
||||
|
||||
# Extends the JupyterHub configuration in the root directory of this repository.
|
||||
# Mounts an additional secrets volume that stores the Let's Encrypt TLS
|
||||
# certificate and key files, and overrides the `SSL_CERT` and `SSL_KEY`
|
||||
# environment variables to point to these files.
|
||||
|
||||
version: "2"
|
||||
|
||||
services:
|
||||
hub:
|
||||
extends: # hub service in repository root directory
|
||||
file: ../../docker-compose.yml
|
||||
service: hub
|
||||
volumes:
|
||||
- "secrets:/etc/letsencrypt"
|
||||
environment:
|
||||
SSL_KEY: "/etc/letsencrypt/privkey.pem"
|
||||
SSL_CERT: "/etc/letsencrypt/cert.pem"
|
||||
|
||||
# Explicitly declare volume and network dependencies
|
||||
# (they cannot be extended)
|
||||
volumes:
|
||||
data:
|
||||
external:
|
||||
name: ${DATA_VOLUME_HOST}
|
||||
secrets:
|
||||
external:
|
||||
name: ${SECRETS_VOLUME}
|
||||
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: ${DOCKER_NETWORK_NAME}
|
84
examples/letsencrypt/letsencrypt.sh
Executable file
84
examples/letsencrypt/letsencrypt.sh
Executable file
@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
# Wrapper script that runs https://letsencrypt.org Docker container to generate
|
||||
# a certificate for a single domain and store it in a Docker volume.
|
||||
|
||||
set -e
|
||||
|
||||
USAGE="
|
||||
Usage: `basename $0` --domain FQDN --email EMAIL --volume SECRETS_VOLUME
|
||||
[--staging]
|
||||
"
|
||||
|
||||
while [[ $# > 0 ]]
|
||||
do
|
||||
key="$1"
|
||||
case $key in
|
||||
--domain)
|
||||
FQDN="$2"
|
||||
shift # past argument
|
||||
;;
|
||||
--email)
|
||||
EMAIL="$2"
|
||||
shift # past argument
|
||||
;;
|
||||
--volume)
|
||||
SECRETS_VOLUME="$2"
|
||||
shift # past argument
|
||||
;;
|
||||
--staging)
|
||||
CERT_SERVER=--staging
|
||||
;;
|
||||
*) # unknown option
|
||||
;;
|
||||
esac
|
||||
shift # past argument or value
|
||||
done
|
||||
|
||||
if [ -z "${FQDN:+x}" ]; then
|
||||
echo "ERROR: Must provide --domain option or set FQDN environment varable"
|
||||
echo "$USAGE" && exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${EMAIL:+x}" ]; then
|
||||
echo "ERROR: Must provide --email option set EMAIL environment varable"
|
||||
echo "$USAGE" && exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${SECRETS_VOLUME:+x}" ]; then
|
||||
echo "ERROR: Must provide --volume option or set SECRETS_VOLUME environment varable"
|
||||
echo "$USAGE" && exit 1
|
||||
fi
|
||||
|
||||
# letsencrypt certificate server type (default is production).
|
||||
# Set `CERT_SERVER=--staging` for staging.
|
||||
: ${CERT_SERVER=''}
|
||||
|
||||
# Generate the cert and save it to the Docker volume
|
||||
docker run --rm -it \
|
||||
-p 80:80 \
|
||||
-v $SECRETS_VOLUME:/etc/letsencrypt \
|
||||
quay.io/letsencrypt/letsencrypt:latest \
|
||||
certonly \
|
||||
--non-interactive \
|
||||
--keep-until-expiring \
|
||||
--standalone \
|
||||
--standalone-supported-challenges http-01 \
|
||||
--agree-tos \
|
||||
--force-renewal \
|
||||
--domain "$FQDN" \
|
||||
--email "$EMAIL" \
|
||||
$CERT_SERVER
|
||||
|
||||
# Set permissions so nobody can read the cert and key.
|
||||
# Also symlink the certs into the root of the /etc/letsencrypt
|
||||
# directory so that the FQDN doesn't have to be known later.
|
||||
docker run --rm -it \
|
||||
-v $SECRETS_VOLUME:/etc/letsencrypt \
|
||||
--entrypoint=/bin/bash \
|
||||
quay.io/letsencrypt/letsencrypt:latest \
|
||||
-c "find /etc/letsencrypt/* -maxdepth 1 -type l -delete && \
|
||||
ln -s /etc/letsencrypt/live/$FQDN/* /etc/letsencrypt/ && \
|
||||
find /etc/letsencrypt -type d -exec chmod 755 {} +"
|
27
hub.sh
Executable file
27
hub.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
# Wrapper script around docker-compose
|
||||
|
||||
set -e
|
||||
|
||||
for i in "$@" ; do
|
||||
if [[ "$i" == "up" ]]; then
|
||||
# Check for required environment variables on startup
|
||||
if [ -z ${GITHUB_CLIENT_ID:+x} ]; then
|
||||
echo "ERROR: Must set GITHUB_CLIENT_ID environment variable"; exit 1;
|
||||
fi
|
||||
if [ -z ${GITHUB_CLIENT_SECRET:+x} ]; then
|
||||
echo "ERROR: Must set GITHUB_CLIENT_SECRET environment variable"; exit 1;
|
||||
fi
|
||||
if [ -z ${OAUTH_CALLBACK_URL:+x} ]; then
|
||||
echo "ERROR: Must set OAUTH_CALLBACK_URL environment variable"; exit 1;
|
||||
fi
|
||||
|
||||
# Set DOCKER_HOST to daemon of target machine
|
||||
DOCKER_HOST=$(docker-machine url $(docker-machine active))
|
||||
fi
|
||||
done
|
||||
|
||||
exec docker-compose "$@"
|
BIN
internal/jupyterhub-docker.png
Normal file
BIN
internal/jupyterhub-docker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
81
jupyterhub_config.py
Normal file
81
jupyterhub_config.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
# Configuration file for JupyterHub
|
||||
import os
|
||||
|
||||
c = get_config()
|
||||
|
||||
# We rely on environment variables to configure JupyterHub so that we
|
||||
# avoid having to rebuild the JupyterHub container every time we change a
|
||||
# configuration parameter.
|
||||
|
||||
# Spawn single-user servers as Docker containers
|
||||
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
|
||||
# Spawn containers from this image
|
||||
c.DockerSpawner.container_image = os.environ['DOCKER_CONTAINER_IMAGE']
|
||||
# JupyterHub requires a single-user instance of the Notebook server, so we
|
||||
# default to using the `start-singleuser.sh` script included in the
|
||||
# jupyter/docker-stacks *-notebook images as the Docker run command when
|
||||
# spawning containers. Optionally, you can override the Docker run command
|
||||
# using the DOCKER_SPAWN_CMD environment variable.
|
||||
spawn_cmd = os.environ.get('DOCKER_SPAWN_CMD', "start-singleuser.sh")
|
||||
c.DockerSpawner.extra_create_kwargs.update({ 'command': spawn_cmd })
|
||||
# Connect containers to this Docker network
|
||||
network_name = os.environ['DOCKER_NETWORK_NAME']
|
||||
c.DockerSpawner.use_internal_ip = True
|
||||
c.DockerSpawner.network_name = network_name
|
||||
# Pass the network name as argument to spawned containers
|
||||
c.DockerSpawner.extra_host_config = { 'network_mode': network_name }
|
||||
c.DockerSpawner.extra_start_kwargs = { 'network_mode': network_name }
|
||||
# Explicitly set notebook directory because we'll be mounting a host volume to
|
||||
# it. Most jupyter/docker-stacks *-notebook images run the Notebook server as
|
||||
# user `jovyan`, and set the notebook directory to `/home/jovyan/work`.
|
||||
# We follow the same convention.
|
||||
c.DockerSpawner.notebook_dir = '/home/jovyan/work'
|
||||
# Mount the real user's Docker volume on the host to the notebook user's
|
||||
# notebook directory in the container
|
||||
c.DockerSpawner.volumes = { '{username}': '/home/jovyan/work' }
|
||||
c.DockerSpawner.extra_create_kwargs.update({ 'volume_driver': 'local' })
|
||||
# Remove containers once they are stopped
|
||||
c.DockerSpawner.remove_containers = True
|
||||
# Specify paths to TLS certificate and key used to authenticate to Docker
|
||||
# daemon at DOCKER_HOST
|
||||
c.DockerSpawner.tls_cert = os.environ['DOCKER_TLS_CERT']
|
||||
c.DockerSpawner.tls_key = os.environ['DOCKER_TLS_KEY']
|
||||
# For debugging arguments passed to spawned containers
|
||||
c.DockerSpawner.debug = True
|
||||
|
||||
# User containers will access hub by container name on the Docker network
|
||||
c.JupyterHub.hub_ip = 'jupyterhub'
|
||||
c.JupyterHub.hub_port = 8080
|
||||
|
||||
# TLS config
|
||||
c.JupyterHub.port = 443
|
||||
c.JupyterHub.ssl_key = os.environ['SSL_KEY']
|
||||
c.JupyterHub.ssl_cert = os.environ['SSL_CERT']
|
||||
|
||||
# Authenticate users with GitHub OAuth
|
||||
c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'
|
||||
c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
|
||||
|
||||
# Persist hub data on volume mounted inside container
|
||||
data_dir = os.environ.get('DATA_VOLUME_CONTAINER', '/data')
|
||||
c.JupyterHub.db_url = os.path.join('sqlite:///', data_dir, 'jupyterhub.sqlite')
|
||||
c.JupyterHub.cookie_secret_file = os.path.join(data_dir,
|
||||
'jupyterhub_cookie_secret')
|
||||
|
||||
# Whitlelist users and admins
|
||||
c.Authenticator.whitelist = whitelist = set()
|
||||
c.Authenticator.admin_users = admin = set()
|
||||
c.JupyterHub.admin_access = True
|
||||
pwd = os.path.dirname(__file__)
|
||||
with open(os.path.join(pwd, 'userlist')) as f:
|
||||
for line in f:
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split()
|
||||
name = parts[0]
|
||||
whitelist.add(name)
|
||||
if len(parts) > 1 and parts[1] == 'admin':
|
||||
admin.add(name)
|
Loading…
Reference in New Issue
Block a user