-
Notifications
You must be signed in to change notification settings - Fork 382
Add example demonstrating how to run jupyterhub with docker-compose #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2b08841
425d916
da00e52
d0b030a
2866810
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# The jupyterhub/jupyterhub instance is quite bulky... A stripped down version | ||
# can be built from the python:3.x-slim images. | ||
#FROM jupyterhub/jupyterhub | ||
FROM python:3.11-slim | ||
|
||
# Recommended minimal set of packages, allowing use of DockerSpawner | ||
# and OAuthenticator classes. | ||
# hadolint ignore=DL3008,DL3013 | ||
RUN apt-get update && \ | ||
apt-get install -y git --no-install-recommends && \ | ||
python3 -m pip install -U --no-cache-dir \ | ||
dockerspawner \ | ||
jupyterhub \ | ||
oauthenticator \ | ||
git+https://github.com/jupyterhub/traefik-proxy.git && \ | ||
apt-get remove -y git && \ | ||
rm -rf /var/lib/apt/lists | ||
|
||
CMD ["jupyterhub", "-f", "/srv/jupyterhub/jupyterhub_config.py"] | ||
WORKDIR /srv/jupyterhub |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# Docker Compose | ||
|
||
An example `docker-compose` file and related configuration files are provided | ||
here. | ||
|
||
There are four files included in this directory, which can be used as a | ||
starting point, and should be configured to suit your individual needs:- | ||
|
||
- [`Dockerfile`](#Dockerfile) | ||
- [`docker-compose.yaml`](#docker-compose.yaml) | ||
- [`jupyterhub_config.py`](#jupyterhub_config.py) | ||
- [`traefik.yaml`](#traefik.yaml) | ||
|
||
# Usage | ||
|
||
Configure the files appropriately, and launch the `traefik` and `jupyterhub` | ||
services with the command:- | ||
|
||
``` | ||
docker-compose up -d | ||
``` | ||
|
||
# Requirements | ||
|
||
- `docker` | ||
- [`docker-compose`](https://docs.docker.com/compose/). | ||
- Optionally, a domain name for LetsEncrypt certificates | ||
|
||
## `Dockerfile` | ||
|
||
Defines the docker build rules for the `jupyterhub` container image. See | ||
https://jupyterhub-dockerspawner.readthedocs.io/en/latest/docker-image.html for | ||
details on what must be included in this image. This example builds a slimmed | ||
down version of jupyterhub, installing `jupyterhub_traefik_proxy` from | ||
github (not PyPi), along with `dockerspawner` and `oauthenticator` jupyterhub | ||
modules. | ||
|
||
## `docker-compose.yaml` | ||
|
||
Defines the `jupyterhub_traefik_proxy` and `traefik` service containers that | ||
will be built and run. | ||
|
||
Also includes rules for how the traefik API will be accessed. Change the | ||
credentials allowed by the `basicauth` middleware, as it is configured by | ||
default with credentials of `admin` and `password`. | ||
|
||
## `jupyterhub_config.py` | ||
|
||
jupyterhub's configuration file. Spend some time working through this file. | ||
This is a minimal, but documented example that works for me. A full jupyterhub | ||
configuration can be obtained by running `jupyterhub --generate-config` in the | ||
jupyterhub container. i.e. | ||
|
||
``` | ||
# Launch the docker-compose project | ||
docker-compose up -d | ||
|
||
# Generate a full configuration file, save to jupyterhub_config-full.py | ||
docker-compose exec hub jupyterhub --generate-config > jupyterhub_config-full.py | ||
``` | ||
|
||
However, a newly generated configuration file won't include configuration | ||
directives for everything you might want to use, e.g. | ||
`jupyterhub_traefik_proxy`, | ||
[`oauthenticator`](https://github.com/jupyterhub/oauthenticator), or | ||
[`dockerspawner`](https://jupyterhub-dockerspawner.readthedocs.io/). The | ||
relevant documentation (or code) for non-default modules should be referred to. | ||
|
||
## `traefik.yaml` | ||
|
||
The static configuration file used by `traefik`. This file can be used to | ||
configure various features on traefik, including but not limited to:- | ||
|
||
- [ACME certificate resolvers](https://doc.traefik.io/traefik/https/acme/) | ||
- [traefik entrypoints](https://doc.traefik.io/traefik/routing/entrypoints/) | ||
- [traefik log](https://doc.traefik.io/traefik/observability/logs/) | ||
- [traefik API](https://doc.traefik.io/traefik/operations/api/) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
version: "3" | ||
|
||
services: | ||
# The JupyterHub service configuration { | ||
hub: | ||
image: jupyterhub-traefik-proxy:example | ||
build: . | ||
container_name: jupyterhub | ||
|
||
# Start traefik first | ||
depends_on: | ||
- traefik | ||
|
||
volumes: | ||
# Jupyterhub configuration file | ||
- ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py:ro | ||
|
||
# Shared volume for the file provider's dynamic config | ||
- traefik-dynamic-config:/var/run/traefik/ | ||
|
||
# jupyterhub's DockerSpawner needs read access to the docker socket. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm surprised it only needs read access 🤷♂️ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I don't think services should be modifying their own configuration file. e.g. all config files in (at least my) |
||
- /var/run/docker.sock:/var/run/docker.sock:ro | ||
|
||
# Volume to persist the jupyterhub sqlite database | ||
- data:/srv/jupyterhub | ||
|
||
networks: | ||
- traefik_internal | ||
|
||
# } /JupyterHub | ||
|
||
# The traefik service configuration { | ||
traefik: | ||
image: traefik:latest | ||
restart: unless-stopped | ||
container_name: traefik | ||
|
||
ports: | ||
- "80:80/tcp" | ||
- "443:443/tcp" | ||
|
||
volumes: | ||
# Static configuration file | ||
- ./traefik.yaml:/etc/traefik/traefik.yml:ro | ||
|
||
# Shared dynamic config volume | ||
- traefik-dynamic-config:/var/run/traefik | ||
|
||
# Traefik needs read-only access to the docker API socket | ||
- /var/run/docker.sock:/var/run/docker.sock:ro | ||
|
||
labels: | ||
# Tell traefik to enable the rules defined in the below labels. | ||
- "traefik.enable=true" | ||
|
||
# Dashboard configuration | ||
- "traefik.http.routers.dashboard.entryPoints=websecure" | ||
|
||
# Router rule for requests to the api service. The 'Host' rule must match the following in | ||
# jupyterhub_config.py:- | ||
# c.TraefikFileProviderProxy.traefik_api_url = "https://traefik" | ||
- "traefik.http.routers.dashboard.rule=Host(`traefik`) && PathPrefix(`/api`, `/dashboard`)" | ||
- "traefik.http.routers.dashboard.service=api@internal" | ||
|
||
# Connections to the dashboard and api should be encrypted | ||
- "traefik.http.routers.dashboard.tls=true" | ||
|
||
# Users should be authorised to access the dashboard and api | ||
- "traefik.http.routers.dashboard.middlewares=dashboard-auth" | ||
|
||
# User: "admin". Password: "password". (N.B. Each $ char must be escaped, with an extra $) | ||
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$uqxc0z9g$$ukB361ceL17eKK7gBZSkG1" | ||
|
||
networks: | ||
- default | ||
- traefik_internal | ||
|
||
# } /traefik | ||
|
||
volumes: | ||
# Jupyterhub data volume | ||
data: | ||
# traefik's dynamic configuration folder will be in a volume shared between | ||
# both services | ||
traefik-dynamic-config: | ||
|
||
networks: | ||
traefik_internal: | ||
# The default network name will have this folder's name prepended to it. | ||
# Fix its full name here, to match 'c.DockerSpawner.network_name', in | ||
# jupyterhub_config.py. | ||
name: traefik_internal |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
# Configuration file for jupyterhub. | ||
import os | ||
|
||
c = get_config() # noqa | ||
|
||
# Class for authenticating users. | ||
# | ||
# One of the benefits of using jupyterhub, is its ability to use a central | ||
# Identity Provider and Authentication service. | ||
# | ||
# Currently installed: | ||
# - default: jupyterhub.auth.PAMAuthenticator | ||
# - dummy: jupyterhub.auth.DummyAuthenticator | ||
# - null: jupyterhub.auth.NullAuthenticator | ||
# - pam: jupyterhub.auth.PAMAuthenticator | ||
# Default: 'jupyterhub.auth.PAMAuthenticator' | ||
# | ||
# Also, check the OAuth authenticators, at:- | ||
# https://oauthenticator.readthedocs.io/en/latest/tutorials/provider-specific-setup/index.html | ||
# | ||
# The 'dummy' authenticator will allow any user to login and launch a jupyter | ||
# notebook, so should definitely NOT BE USED in production or on publicly | ||
# accessible servers! | ||
c.JupyterHub.authenticator_class = "dummy" | ||
|
||
# The public facing URL of the whole JupyterHub application. | ||
# | ||
# This is the address on which the proxy will bind. | ||
# Sets protocol, ip, base_url | ||
# Default: 'http://:8000' | ||
# (dev note) This will be copied to c.Proxy.public_url | ||
c.JupyterHub.bind_url = "https://hub.example.com" | ||
|
||
# Whether to clean up the jupyterhub-managed traefik configuration | ||
# when the Hub shuts down. | ||
c.JupyterHub.cleanup_proxy = True | ||
|
||
# The URL on which the Hub will listen. This is a private URL for internal | ||
# communication. Typically set in combination with hub_connect_url. If a unix | ||
# socket, hub_connect_url **must** also be set. | ||
# | ||
# For example: | ||
# | ||
# "http://127.0.0.1:8081" | ||
# "unix+http://%2Fsrv%2Fjupyterhub%2Fjupyterhub.sock" | ||
# | ||
# .. versionadded:: 0.9 | ||
# Default: '' | ||
# | ||
# jupyterhub_traefik_proxy will configure the 'service' url in traefik, so this | ||
# needs to be accessible from traefik. By default, jupyterhub will bind to | ||
# 'localhost', but this will bind jupyterhub to its container name | ||
c.JupyterHub.hub_bind_url = "http://hub:8000" | ||
|
||
# This sets traefik's router rule for routing traffic to the jupyterhub | ||
# instance. | ||
# | ||
# Typically, you'll want a traefik Host-based configuration rule, e.g.:- | ||
# traefik.http.routers.jupyterhub.rule=Host(`hub.example.com`) | ||
# | ||
# The corresponding `hub_routespec` for the above would be:- | ||
# c.JupyterHub.hub_routespec = 'hub.example.com' | ||
# | ||
# The default is to bind to everything, creating a path-based rule. i.e. | ||
# traefik.http.routers.jupyterhub.rule=PathPrefix(`/`) | ||
# | ||
# Default: = '/' | ||
# | ||
c.JupyterHub.hub_routespec = "hub.example.com/" | ||
|
||
# Set the log level by value or name. | ||
# Choices: any of [0, 10, 20, 30, 40, 50, 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL'] | ||
# Default: 30 | ||
# See also: Application.log_level | ||
c.JupyterHub.log_level = "DEBUG" | ||
|
||
# Use jupyterhub_traefik_proxy's `TraefikFileProviderProxy` class | ||
c.JupyterHub.proxy_class = "traefik_file" | ||
|
||
# JupyterHub shouldn't start the proxy, docker-compose will launch it | ||
c.TraefikFileProviderProxy.should_start = False | ||
|
||
# The configuration file jupyterhub will write to, and traefik will watch | ||
c.TraefikFileProviderProxy.dynamic_config_file = "/var/run/traefik/jupyterhub.yaml" | ||
|
||
# Settings jupyterhub_traefik_proxy will use to access the traefik API | ||
# These must match traefik's dynamic configuration (check the labels in | ||
# docker-compose.yaml) | ||
c.TraefikFileProviderProxy.traefik_api_url = "https://traefik" | ||
c.TraefikFileProviderProxy.traefik_api_validate_cert = False | ||
c.TraefikFileProviderProxy.traefik_api_username = "admin" | ||
c.TraefikFileProviderProxy.traefik_api_password = "password" | ||
|
||
# Traefik can automatically retrieve certificates for each user container from | ||
# an ACME provider (e.g. Let's Encrypt), For an example, read the comments in | ||
# traefik's static configuraiton file, traefik.yaml, and refer to the | ||
# reference documentation at:- | ||
# https://doc.traefik.io/traefik/https/acme/ | ||
# c.TraefikFileProviderProxy.traefik_cert_resolver = "leresolver" | ||
|
||
# The class to use for spawning single-user servers. | ||
# | ||
# Currently installed: | ||
# - default: jupyterhub.spawner.LocalProcessSpawner | ||
# - localprocess: jupyterhub.spawner.LocalProcessSpawner | ||
# - simple: jupyterhub.spawner.SimpleLocalProcessSpawner | ||
# Default: 'jupyterhub.spawner.LocalProcessSpawner' | ||
# | ||
# Launch each user's notebook server in a separate container. | ||
c.JupyterHub.spawner_class = "dockerspawner.DockerSpawner" | ||
|
||
# Base Image to use for user notebook containers. You can build your own, | ||
# or use an image name and tag from hub.docker.com, or another image repository | ||
c.DockerSpawner.image = "jupyterhub/singleuser" | ||
|
||
# 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. | ||
notebook_dir = os.environ.get("DOCKER_NOTEBOOK_DIR") or "/home/jovyan/work" | ||
c.DockerSpawner.notebook_dir = notebook_dir | ||
|
||
# Create per-user docker volumes, mounted to the user's notebook_dir in the | ||
# container | ||
c.DockerSpawner.volumes = {"jupyterhub-user-{username}": notebook_dir} | ||
|
||
# The docker network name that single-user notebook containers should attach to | ||
c.DockerSpawner.network_name = "traefik_internal" | ||
|
||
# For jupyterhub to let traefik manage certificates, 'ssl_cert' needs a | ||
# value. (This gets around a validate rule on 'proxy.bind_url', which | ||
# forces redirects to 'http', unless there is a value in ssl_cert). | ||
# Otherwise, when logging in, there will always be 302 redirects to http:// | ||
c.JupyterHub.ssl_cert = "externally managed" | ||
|
||
# jupyterhub will only configure path-based routing by default. To stop | ||
# traefik from routing all requests to jupyterhub, a subdomain host should be | ||
# configured. | ||
# That is, by default, jupyterhub will create a router rule of just PathPrefix(`/`). | ||
# This could conflict with other traefik router rules, or just be too easily | ||
# accessible. | ||
# | ||
# If a subdomain_host is configured, each user container will be accessible at:- | ||
# https://<user>.<subdomain_host> | ||
# | ||
# e.g. A user of "jbloggs", logging into a hub with a subdomain_host of | ||
# "https://hub.example.com", will be redirected to their notebook at | ||
# https://jbloggs.hub.example.com | ||
c.JupyterHub.subdomain_host = "https://hub.example.com" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to build on top of the standard JupyterHub image, as in the other example?
jupyterhub-deploy-docker/basic-example/Dockerfile.jupyterhub
Line 4 in 89a2962
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, no problem