Dear Friends:
I’ve have JupyterHub
successfully running (which I built from scratch via a Dockerfile
and docker-compose.yml
that I created from scratch). I’m using DockerSpawner
. It all works very well (thanks to you guys).
However, I can’t seem to get an image selection-menu
to appear upon login (i.e. for a user to be able to select an image). Upon login, the jupyterhub/singleuser
image is automatically launched, with no selection-menu appearing to offer an opportunity to select one.
I think I’m missing something(s) or overriding something due to a lack of understanding somewhere.
Here is some configuration information to help, and thank you very much in advance!
Docker
images available on the bare-metal HOST:
root@HOST# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
acme/jupyterhub 1.0 4fe8efebb49a 2 days ago 931MB
jupyter/all-spark-notebook latest 172d25132463 10 days ago 4.05GB
jupyter/minimal-notebook latest d7e600ddb99c 10 days ago 1.57GB
jupyterhub/singleuser 1.2 500ec59dd69a 2 weeks ago 607MB
postgres latest c96f8b6bc0d9 5 weeks ago 314MB
Enter the running JupyterHub Docker
container as root
:
root@HOST# docker exec -it --workdir /root --user root jupyterhub /bin/bash
Jupyter component versions:
# Activate the virtual-environment used to run JupyterHub ...
bash-5.0# source /opt/jupyterhub.d/pyvenv.d/bin/activate
# Dump the various versions of Jupyter components ...
(pyvenv.d) bash-5.0# pip freeze | grep -i jupyter
jupyter==1.0.0
jupyter-client==6.1.7
jupyter-console==6.2.0
jupyter-core==4.6.3
jupyter-telemetry==0.1.0
jupyterhub==1.2.1
jupyterlab-pygments==0.1.2
Unix processes:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 20:00 ? 00:00:00 bash /opt/jupyterhub.d/usr/bin/jupyterhub.sh
root 269 265 0 20:00 ? 00:00:01 /opt/jupyterhub.d/pyvenv.d/bin/python3 /opt/jupyterhub.d/pyvenv.d/bin/jupyterhub --config /opt/jupyterhub.d/etc/conf.d/jupyterhub_config.py
root 273 269 0 20:00 ? 00:00:00 node /opt/jupyterhub.d/pyvenv.d/bin/configurable-http-proxy --ip --port 443 --api-ip 127.0.0.1 --api-port 8001 --error-target http://jupyterhub:8080/hub/error --ssl-key /opt/jupyterhub.d/etc/ssl.d/ide.example.com.key --ssl-cert /opt/jupyterhub.d/etc/ssl.d/ide.example.com.crt
Unix Environment (some values set by ./docker-compose.yml
or by ./.env
file:
SSL_CRT=/opt/jupyterhub.d/etc/ssl.d/ide.example.com.crt
POSTGRES_HOST=jupyterhub-db
DOCKER_NETWORK_NAME=jupyterhub-backend
DOCKER_NOTEBOOK_IMAGE=jupyter/minimal-notebook:latest
HOSTNAME=791c63772cbf
DB_VOLUME_CONTAINER=/var/lib/postgresql/data
USERLIST_FILE=/opt/jupyterhub.d/etc/conf.d/userlist.txt
JUPYTERHUB_DOCKER_MACHINE_NAME=jupyterhub
PWD=/root
GITLAB_HOST=https://gitlab.example.com:443
container=oci
HOME=/root
LANG=en_US.UTF-8
FGC=f32
VIRTUAL_ENV=/opt/jupyterhub.d/pyvenv.d
JUPYTERHUB_IMAGE_FQ_NAME=acme/jupyterhub:1.0
DATA_VOLUME_CONTAINER=/data
NODE_VIRTUAL_ENV=/opt/jupyterhub.d/pyvenv.d
NPM_CONFIG_PREFIX=/opt/jupyterhub.d/pyvenv.d
JUPYTER_ENABLE_LAB=yes
npm_config_prefix=/opt/jupyterhub.d/pyvenv.d
POSTGRES_USER=jhubadmin
DOCKER_NOTEBOOK_DIR=/home/jovyan/work
SSL_KEY=/opt/jupyterhub.d/etc/ssl.d/ide.example.com.key
NODE_PATH=/opt/jupyterhub.d/pyvenv.d/lib/node_modules
OAUTH_CALLBACK_URL=https://ide.example.com:443/hub/oauth_callback
PATH=/opt/jupyterhub.d/pyvenv.d/lib/node_modules/.bin:/opt/jupyterhub.d/pyvenv.d/bin:/opt/jupyterhub.d/pyvenv.d/
bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DB_VOLUME_HOST=jupyterhub-db-data
FEDORA_VERSION=32
DATA_VOLUME_HOST=jupyterhub-data
POSTGRES_DB=jupyterhub
DATABASE_DOCKER_MACHINE_NAME=jupyterhub-db
DOCKER_SUBNET_CIDR='172.10.0.0/16'
GITLAB_URL=https://gitlab.example.com:443
JupyterHub
configuration file /opt/jupyterhub.d/etc/conf.d/jupyterhub_config.py
:
# ===========================================================================
# SEE: https://jupyterhub.readthedocs.io/en/stable/api/app.html
# Configuration file for JupyterHub. This file is evaluated inside the
# JupyterHub container, once is boots. All environment variables referenced
# below are injected by way of docker-compose.yml(5). (See next comment).
# ===========================================================================
import os, secrets
c = get_config()
# ===========================================================================
# ===========================================================================
# Spawn containers from this image.
# REF: https://github.com/jupyter/docker-stacks
# ===========================================================================
# c.DockerSpawner.image = os.environ['DOCKER_NOTEBOOK_IMAGE']
# ===========================================================================
c.JupyterHub.allow_named_servers = True
c.DockerSpawner.image_whitelist = {
"minimal" : "jupyter/minimal-notebook:latest",
"all-spark" : "jupyter/all-spark-notebook:latest",
"single-user" : "jupyterhub/singleuser:1.2",
}
# ===========================================================================
# 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 = "start-singleuser.sh --SingleUserNotebookApp.default_url=/lab"
##spawn_cmd += " --SingleUserNotebookApp.disable_user_config=True"
#spawn_cmd = os.environ.get('DOCKER_SPAWN_CMD', spawn_cmd)
#c.DockerSpawner.extra_create_kwargs.update({ 'command': spawn_cmd })
# ===========================================================================
# ===========================================================================
# Connect containers to this Docker network. Pass the network name as
# argument to spawned Notebook containers.
# ===========================================================================
network_name = os.environ['DOCKER_NETWORK_NAME']
c.DockerSpawner.use_internal_ip = True
c.DockerSpawner.network_name = network_name
c.DockerSpawner.extra_host_config = { '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`.
# ===========================================================================
notebook_dir = os.environ.get('DOCKER_NOTEBOOK_DIR') or '/home/jovyan/work'
c.DockerSpawner.notebook_dir = notebook_dir
# ===========================================================================
# ===========================================================================
# Mount the real user's Docker volume on the host to the notebook user's
# notebook directory in the container.
# ===========================================================================
c.DockerSpawner.volumes = { 'jupyterhub-user-{username}': notebook_dir }
# ===========================================================================
# ===========================================================================
# volume_driver is no longer a keyword argument to create_container()
# c.DockerSpawner.extra_create_kwargs.update({ 'volume_driver': 'local' })
# ===========================================================================
# ===========================================================================
# Remove containers once they are stopped.
# ===========================================================================
c.DockerSpawner.remove_containers = True
# ===========================================================================
# ===========================================================================
# For debugging arguments passed to spawned containers.
# ===========================================================================
c.DockerSpawner.debug = True
# ===========================================================================
# ===========================================================================
# User Notebook containers will access jupyterhub by container-name on
# backend Docker network.
# ===========================================================================
from jupyter_client.localinterfaces import public_ips
# ===========================================================================
c.JupyterHub.hub_port = 8080
c.JupyterHub.hub_ip = 'jupyterhub'
c.JupyterHub.hub_connect_ip = 'jupyterhub'
# ===========================================================================
# ===========================================================================
# TLS config
# NOTE: 'c.JupyterHub.port = 443' refers to the Container-side port, not the
# Host-side port. The Host-side requires mapping to 8443 port, which we do
# within 'docker-compose.yml'. Reason: Port 443 is taken by GitLab nginx.
# ===========================================================================
c.JupyterHub.port = 443
c.JupyterHub.ssl_key = os.environ['SSL_KEY']
c.JupyterHub.ssl_cert = os.environ['SSL_CRT']
# ===========================================================================
# ===========================================================================
# This JupyterHub auth uses GitLab OAuth on https://gitlab example.io:443
# ===========================================================================
from oauthenticator.gitlab import GitLabOAuthenticator
c.JupyterHub.authenticator_class = GitLabOAuthenticator
c.GitLabOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
c.GitLabOAuthenticator.client_id = os.environ['OAUTH_CLIENT_ID']
c.GitLabOAuthenticator.client_secret = os.environ['OAUTH_CLIENT_SECRET']
# ===========================================================================
# ===========================================================================
# Persist jupyterhub data on volume mounted inside container.
# ===========================================================================
data_dir = os.environ.get('DATA_VOLUME_CONTAINER', '/data')
# ===========================================================================
# ===========================================================================
# TBD...
# ===========================================================================
#c.JupyterHub.cookie_secret_file = os.path.join(data_dir, 'jupyterhub_cookie_secret')
os.environ['JPY_COOKIE_SECRET'] = secrets.token_hex(16)
c.JupyterHub.cookie_secret = bytes.fromhex(os.environ['JPY_COOKIE_SECRET'])
# ===========================================================================
# ===========================================================================
# - POSTGRES_HOST is injected via docker-compose.yml (environment: section).
# - POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB are also injected via
# docker-compose.yml (env_file: -./etc/secrets.d/postgres.env)
# ===========================================================================
#c.JupyterHub.db_url = 'postgresql://postgres:{password}@{host}/{db}'.format(
c.JupyterHub.db_url = 'postgresql://{user}:{password}@{host}/{db}'.format(
user=os.environ['POSTGRES_USER'],
password=os.environ['POSTGRES_PASSWORD'],
host=os.environ['POSTGRES_HOST'],
db=os.environ['POSTGRES_DB'],)
# ===========================================================================
# ===========================================================================
# Whitlelist users and admins.
# ================================================================================
c.Authenticator.whitelist = whitelist = set()
c.Authenticator.admin_users = admin = set()
c.JupyterHub.admin_access = True
userlist = os.environ['USERLIST_FILE']
# ===========================================================================
with open(userlist) as f:
for line in f:
if not line: continue
parts = line.split()
# In case of newline at the end of userlist.txt file.
if len(parts) >= 1:
name = parts[0]
whitelist.add(name)
if len(parts) > 1 and parts[1] == 'admin':
admin.add(name)
# ===========================================================================
# ================================================================================
# Uncommented-out settings (i.e. to explicitly set them).
# ================================================================================
c.JupyterHub.active_server_limit = 20
c.Spawner.disable_user_config = True # Increases security.
c.JupyterHub.cookie_max_age_days = 14 # Maybe change to 7.
c.JupyterHub.shutdown_on_logout = True
c.Authenticator.admin_users = set('jdoeAdmin',) # Set of users w/ admin rights.
c.Spawner.default_url = '/lab' # Starts JupyterLab by default. \o/
# ================================================================================
If you made it this far, thank you for your help. =:)