Skip to content

Commit dc234b9

Browse files
ahkuibestlong
authored andcommitted
Add Jupyterhub (laradock#1686)
1 parent 29483ba commit dc234b9

11 files changed

Lines changed: 370 additions & 0 deletions

File tree

docker-compose.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,41 @@ services:
722722
ports:
723723
- 9010:9000
724724

725+
### JupyterHub #########################################
726+
jupyterhub:
727+
build:
728+
context: ./jupyterhub
729+
depends_on:
730+
- postgres
731+
- jupyterhub-user
732+
restart: always
733+
volumes:
734+
- /var/run/docker.sock:/var/run/docker.sock:rw
735+
- ${DATA_PATH_HOST}/jupyterhub/:/data
736+
- ${JUPYTERHUB_CUSTOM_CONFIG}:/jupyterhub_config.py
737+
- ${JUPYTERHUB_USER_DATA}:/user-data
738+
- ${JUPYTERHUB_USER_LIST}:/userlist
739+
networks:
740+
- backend
741+
ports:
742+
- "${JUPYTERHUB_PORT}:80"
743+
environment:
744+
- TERM=xterm
745+
- JUPYTERHUB_USER_DATA=${JUPYTERHUB_USER_DATA}
746+
- JUPYTERHUB_POSTGRES_DB=${JUPYTERHUB_POSTGRES_DB}
747+
- JUPYTERHUB_POSTGRES_USER=${JUPYTERHUB_POSTGRES_USER}
748+
- JUPYTERHUB_POSTGRES_HOST=${JUPYTERHUB_POSTGRES_HOST}
749+
- JUPYTERHUB_POSTGRES_PASSWORD=${JUPYTERHUB_POSTGRES_PASSWORD}
750+
- JUPYTERHUB_OAUTH_CALLBACK_URL=${JUPYTERHUB_OAUTH_CALLBACK_URL}
751+
- JUPYTERHUB_OAUTH_CLIENT_ID=${JUPYTERHUB_OAUTH_CLIENT_ID}
752+
- JUPYTERHUB_OAUTH_CLIENT_SECRET=${JUPYTERHUB_OAUTH_CLIENT_SECRET}
753+
- JUPYTERHUB_LOCAL_NOTEBOOK_IMAGE=${JUPYTERHUB_LOCAL_NOTEBOOK_IMAGE}
754+
jupyterhub-user:
755+
build:
756+
context: ./jupyterhub
757+
dockerfile: Dockerfile.user
758+
command: ["sh", "-c", "echo \"build only\""]
759+
725760
### IPython #########################################
726761
ipython-controller:
727762
build:

env-example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,20 @@ SOLR_VERSION=5.5
360360
SOLR_PORT=8983
361361
SOLR_DATAIMPORTHANDLER_MYSQL=false
362362

363+
### JUPYTERHUB ###############################################
364+
JUPYTERHUB_POSTGRES_HOST=postgres
365+
JUPYTERHUB_POSTGRES_USER=laradock_jupyterhub
366+
JUPYTERHUB_POSTGRES_PASSWORD=laradock_jupyterhub
367+
JUPYTERHUB_POSTGRES_DB=laradock_jupyterhub
368+
JUPYTERHUB_PORT=9991
369+
JUPYTERHUB_OAUTH_CALLBACK_URL=http://laradock:9991/hub/oauth_callback
370+
JUPYTERHUB_OAUTH_CLIENT_ID={GITHUB_CLIENT_ID}
371+
JUPYTERHUB_OAUTH_CLIENT_SECRET={GITHUB_CLIENT_SECRET}
372+
JUPYTERHUB_LOCAL_NOTEBOOK_IMAGE=laradock_jupyterhub-user
373+
JUPYTERHUB_CUSTOM_CONFIG=./jupyterhub/jupyterhub_config.py
374+
JUPYTERHUB_USER_DATA=/jupyterhub
375+
JUPYTERHUB_USER_LIST=./jupyterhub/userlist
376+
363377
### IPYTHON ##################################################
364378
LARADOCK_IPYTHON_CONTROLLER_IP=127.0.0.1
365379

jupyterhub/Dockerfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
FROM python
2+
LABEL maintainer="ahkui <ahkui@outlook.com>"
3+
4+
ENV JUPYTERHUB_USER_DATA ${JUPYTERHUB_USER_DATA}
5+
ENV JUPYTERHUB_POSTGRES_DB ${JUPYTERHUB_POSTGRES_DB}
6+
ENV JUPYTERHUB_POSTGRES_USER ${JUPYTERHUB_POSTGRES_USER}
7+
ENV JUPYTERHUB_POSTGRES_HOST ${JUPYTERHUB_POSTGRES_HOST}
8+
ENV JUPYTERHUB_POSTGRES_PASSWORD ${JUPYTERHUB_POSTGRES_PASSWORD}
9+
ENV JUPYTERHUB_OAUTH_CALLBACK_URL ${JUPYTERHUB_OAUTH_CALLBACK_URL}
10+
ENV JUPYTERHUB_OAUTH_CLIENT_ID ${JUPYTERHUB_OAUTH_CLIENT_ID}
11+
ENV JUPYTERHUB_OAUTH_CLIENT_SECRET ${JUPYTERHUB_OAUTH_CLIENT_SECRET}
12+
ENV JUPYTERHUB_LOCAL_NOTEBOOK_IMAGE ${JUPYTERHUB_LOCAL_NOTEBOOK_IMAGE}
13+
14+
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
15+
16+
RUN apt update -yqq && \
17+
apt-get install -y nodejs
18+
19+
RUN npm install -g configurable-http-proxy
20+
21+
RUN pip install jupyterhub
22+
RUN pip install oauthenticator
23+
RUN pip install dockerspawner
24+
RUN pip install psycopg2 psycopg2-binary
25+
26+
CMD ["sh", "-c", "jupyterhub upgrade-db && jupyterhub -f /jupyterhub_config.py"]

jupyterhub/Dockerfile.user

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
FROM tensorflow/tensorflow:latest-gpu
2+
3+
MAINTAINER ahkui <ahkui@outlook.com>
4+
5+
RUN apt-get update && apt-get install -y --no-install-recommends \
6+
python \
7+
python-dev \
8+
&& \
9+
apt-get autoremove -y && \
10+
apt-get autoclean && \
11+
apt-get clean && \
12+
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
13+
14+
RUN apt-get update && apt-get install -y --no-install-recommends \
15+
wget \
16+
git \
17+
&& \
18+
apt-get autoremove -y && \
19+
apt-get autoclean && \
20+
apt-get clean && \
21+
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
22+
23+
RUN curl -O https://bootstrap.pypa.io/get-pip.py && \
24+
python3 get-pip.py && \
25+
rm get-pip.py
26+
27+
RUN python3 -m pip --quiet --no-cache-dir install \
28+
Pillow \
29+
h5py \
30+
ipykernel \
31+
jupyter \
32+
notebook \
33+
jupyterhub \
34+
matplotlib \
35+
numpy \
36+
pandas \
37+
scipy \
38+
sklearn \
39+
Flask \
40+
gunicorn \
41+
pymongo \
42+
redis \
43+
requests \
44+
ipyparallel \
45+
bs4 \
46+
&& \
47+
python3 -m ipykernel.kernelspec
48+
49+
RUN pip --no-cache-dir install \
50+
https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.8.0-cp35-cp35m-linux_x86_64.whl
51+
52+
RUN ln -s -f /usr/bin/python3 /usr/bin/python
53+
54+
COPY start.sh /usr/local/bin/
55+
COPY start-notebook.sh /usr/local/bin/
56+
COPY start-singleuser.sh /usr/local/bin/
57+
RUN chmod +x /usr/local/bin/start.sh
58+
RUN chmod +x /usr/local/bin/start-notebook.sh
59+
RUN chmod +x /usr/local/bin/start-singleuser.sh
60+
61+
RUN wget --quiet https://github.com/krallin/tini/releases/download/v0.10.0/tini && \
62+
mv tini /usr/local/bin/tini && \
63+
chmod +x /usr/local/bin/tini
64+
65+
# cleanup
66+
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
67+
68+
ENTRYPOINT ["tini", "--"]
69+
70+
CMD ["start-notebook.sh"]
71+
72+

jupyterhub/jupyterhub_config.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Copyright (c) Jupyter Development Team.
2+
# Distributed under the terms of the Modified BSD License.
3+
4+
# Configuration file for JupyterHub
5+
import os
6+
7+
c = get_config()
8+
9+
def create_dir_hook(spawner):
10+
username = spawner.user.name # get the username
11+
volume_path = os.path.join('/user-data', username)
12+
if not os.path.exists(volume_path):
13+
# create a directory with umask 0755
14+
# hub and container user must have the same UID to be writeable
15+
# still readable by other users on the system
16+
os.mkdir(volume_path, 0o755)
17+
os.chown(volume_path, 1000,100)
18+
# now do whatever you think your user needs
19+
# ...
20+
pass
21+
22+
# attach the hook function to the spawner
23+
c.Spawner.pre_spawn_hook = create_dir_hook
24+
25+
# We rely on environment variables to configure JupyterHub so that we
26+
# avoid having to rebuild the JupyterHub container every time we change a
27+
# configuration parameter.
28+
29+
# Spawn single-user servers as Docker containers
30+
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
31+
32+
# Spawn containers from this image
33+
c.DockerSpawner.image = os.environ['JUPYTERHUB_LOCAL_NOTEBOOK_IMAGE']
34+
35+
# JupyterHub requires a single-user instance of the Notebook server, so we
36+
# default to using the `start-singleuser.sh` script included in the
37+
# jupyter/docker-stacks *-notebook images as the Docker run command when
38+
# spawning containers. Optionally, you can override the Docker run command
39+
# using the DOCKER_SPAWN_CMD environment variable.
40+
spawn_cmd = os.environ.get('JUPYTERHUB_DOCKER_SPAWN_CMD', "start-singleuser.sh")
41+
c.DockerSpawner.extra_create_kwargs.update({ 'command': spawn_cmd })
42+
43+
# Connect containers to this Docker network
44+
network_name = os.environ.get('JUPYTERHUB_NETWORK_NAME','laradock_backend')
45+
c.DockerSpawner.use_internal_ip = True
46+
c.DockerSpawner.network_name = network_name
47+
48+
# Pass the network name as argument to spawned containers
49+
c.DockerSpawner.extra_host_config = { 'network_mode': network_name, 'runtime': 'nvidia' }
50+
# c.DockerSpawner.extra_host_config = { 'network_mode': network_name, "devices":["/dev/nvidiactl","/dev/nvidia-uvm","/dev/nvidia0"] }
51+
# Explicitly set notebook directory because we'll be mounting a host volume to
52+
# it. Most jupyter/docker-stacks *-notebook images run the Notebook server as
53+
# user `jovyan`, and set the notebook directory to `/home/jovyan/work`.
54+
# We follow the same convention.
55+
# notebook_dir = os.environ.get('JUPYTERHUB_DOCKER_NOTEBOOK_DIR') or '/home/jovyan/work'
56+
notebook_dir = '/notebooks'
57+
c.DockerSpawner.notebook_dir = notebook_dir
58+
59+
# Mount the real user's Docker volume on the host to the notebook user's
60+
# notebook directory in the container
61+
user_data = os.environ.get('JUPYTERHUB_USER_DATA','/jupyterhub')
62+
c.DockerSpawner.volumes = {
63+
user_data+'/{username}': notebook_dir
64+
}
65+
66+
c.DockerSpawner.extra_create_kwargs.update({ 'user': 'root'})
67+
68+
# volume_driver is no longer a keyword argument to create_container()
69+
# c.DockerSpawner.extra_create_kwargs.update({ 'volume_driver': 'local' })
70+
# Remove containers once they are stopped
71+
c.DockerSpawner.remove_containers = True
72+
73+
# For debugging arguments passed to spawned containers
74+
c.DockerSpawner.debug = True
75+
76+
# User containers will access hub by container name on the Docker network
77+
c.JupyterHub.hub_ip = 'jupyterhub'
78+
c.JupyterHub.hub_port = 8000
79+
80+
# TLS config
81+
c.JupyterHub.port = 80
82+
# c.JupyterHub.ssl_key = os.environ['SSL_KEY']
83+
# c.JupyterHub.ssl_cert = os.environ['SSL_CERT']
84+
85+
# Authenticate users with GitHub OAuth
86+
c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'
87+
c.GitHubOAuthenticator.oauth_callback_url = os.environ['JUPYTERHUB_OAUTH_CALLBACK_URL']
88+
c.GitHubOAuthenticator.client_id = os.environ['JUPYTERHUB_OAUTH_CLIENT_ID']
89+
c.GitHubOAuthenticator.client_secret = os.environ['JUPYTERHUB_OAUTH_CLIENT_SECRET']
90+
91+
# Persist hub data on volume mounted inside container
92+
data_dir = '/data'
93+
94+
c.JupyterHub.cookie_secret_file = os.path.join(data_dir,
95+
'jupyterhub_cookie_secret')
96+
97+
print(os.environ)
98+
99+
c.JupyterHub.db_url = 'postgresql://{user}:{password}@{host}/{db}'.format(
100+
user=os.environ['JUPYTERHUB_POSTGRES_USER'],
101+
host=os.environ['JUPYTERHUB_POSTGRES_HOST'],
102+
password=os.environ['JUPYTERHUB_POSTGRES_PASSWORD'],
103+
db=os.environ['JUPYTERHUB_POSTGRES_DB'],
104+
)
105+
106+
# Whitlelist users and admins
107+
c.Authenticator.whitelist = whitelist = set()
108+
c.Authenticator.admin_users = admin = set()
109+
c.JupyterHub.admin_access = True
110+
pwd = os.path.dirname(__file__)
111+
with open(os.path.join(pwd, 'userlist')) as f:
112+
for line in f:
113+
if not line:
114+
continue
115+
parts = line.split()
116+
name = parts[0]
117+
print(name)
118+
whitelist.add(name)
119+
if len(parts) > 1 and parts[1] == 'admin':
120+
admin.add(name)
121+
admin.add('laradock')

jupyterhub/start-notebook.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
# Copyright (c) Jupyter Development Team.
3+
# Distributed under the terms of the Modified BSD License.
4+
5+
set -e
6+
7+
if [[ ! -z "${JUPYTERHUB_API_TOKEN}" ]]; then
8+
# launched by JupyterHub, use single-user entrypoint
9+
exec /usr/local/bin/start-singleuser.sh $*
10+
else
11+
. /usr/local/bin/start.sh jupyter notebook $*
12+
fi

jupyterhub/start-singleuser.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
# Copyright (c) Jupyter Development Team.
3+
# Distributed under the terms of the Modified BSD License.
4+
5+
set -e
6+
7+
# set default ip to 0.0.0.0
8+
if [[ "$NOTEBOOK_ARGS $@" != *"--ip="* ]]; then
9+
NOTEBOOK_ARGS="--ip=0.0.0.0 $NOTEBOOK_ARGS"
10+
fi
11+
12+
# handle some deprecated environment variables
13+
# from DockerSpawner < 0.8.
14+
# These won't be passed from DockerSpawner 0.9,
15+
# so avoid specifying --arg=empty-string
16+
# if [ ! -z "$NOTEBOOK_DIR" ]; then
17+
# NOTEBOOK_ARGS="--notebook-dir='$NOTEBOOK_DIR' $NOTEBOOK_ARGS"
18+
# fi
19+
if [ ! -z "$JPY_PORT" ]; then
20+
NOTEBOOK_ARGS="--port=$JPY_PORT $NOTEBOOK_ARGS"
21+
fi
22+
if [ ! -z "$JPY_USER" ]; then
23+
NOTEBOOK_ARGS="--user=$JPY_USER $NOTEBOOK_ARGS"
24+
fi
25+
if [ ! -z "$JPY_COOKIE_NAME" ]; then
26+
NOTEBOOK_ARGS="--cookie-name=$JPY_COOKIE_NAME $NOTEBOOK_ARGS"
27+
fi
28+
if [ ! -z "$JPY_BASE_URL" ]; then
29+
NOTEBOOK_ARGS="--base-url=$JPY_BASE_URL $NOTEBOOK_ARGS"
30+
fi
31+
if [ ! -z "$JPY_HUB_PREFIX" ]; then
32+
NOTEBOOK_ARGS="--hub-prefix=$JPY_HUB_PREFIX $NOTEBOOK_ARGS"
33+
fi
34+
if [ ! -z "$JPY_HUB_API_URL" ]; then
35+
NOTEBOOK_ARGS="--hub-api-url=$JPY_HUB_API_URL $NOTEBOOK_ARGS"
36+
fi
37+
38+
NOTEBOOK_ARGS=" --allow-root --notebook-dir='/notebooks' $NOTEBOOK_ARGS"
39+
40+
. /usr/local/bin/start.sh jupyterhub-singleuser $NOTEBOOK_ARGS $@

jupyterhub/start.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
# Copyright (c) Jupyter Development Team.
3+
# Distributed under the terms of the Modified BSD License.
4+
5+
set -e
6+
7+
exec sh -c "env PATH=$PATH $*"

jupyterhub/userlist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
laradock
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
*.sh
2+
!init_jupyterhub_db.sh

0 commit comments

Comments
 (0)