Skip to content
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

Add example docker compose environment and related docs #158

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ with examples for the three different implementations.
* [For TraefikEtcdProxy](https://jupyterhub-traefik-proxy.readthedocs.io/en/latest/etcd.html#example-setup)
* [For TraefikConsulProxy](https://jupyterhub-traefik-proxy.readthedocs.io/en/latest/consul.html#example-setup)

## Docker Compose

A fully functional `docker-compose` example environment is included in the
[examples](examples/docker-compose) directory. This can be configured to suit
your needs.

Running `docker-compose up` from that directory will start two containers, one
running `jupyterhub` and another running `traefik`. N.B. Two docker volumes
will be created, as well as two docker networks.

Users logging into this example jupyterhub environment will have
`jupyterhub/singleuser` notebook servers launched for them in separate docker
containers (using `JupyterHub.DockerSpawner`). See
https://jupyterhub-dockerspawner.readthedocs.io/en/latest/docker-image.html for
instructions on building custom user notebook images. User data will be
persisted to separate docker volumes created for each user.

## Running tests

Expand Down
80 changes: 80 additions & 0 deletions docs/source/docker-compose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Docker Compose

An example `docker-compose` file and related configuration files are provided
in [`examples/docker-compose`](https://github.com/jupyterhub/traefik-proxy/tree/main/examples).

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

(N.B. This has only been tested on Linux.)

## `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/)

1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Installation Guide
:maxdepth: 2

install
docker-compose

Getting Started
---------------
Expand Down
20 changes: 20 additions & 0 deletions examples/docker-compose/Dockerfile
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.
RUN apt update && \
apt install -y git && \
pip3 install -U --no-cache \
dockerspawner \
jupyterhub \
oauthenticator \
git+https://github.com/jupyterhub/traefik-proxy.git && \
apt remove -y git && \
rm -rf /var/lib/apt/lists

CMD jupyterhub -f /srv/jupyterhub/jupyterhub_config.py
WORKDIR /srv/jupyterhub

93 changes: 93 additions & 0 deletions examples/docker-compose/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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.
- /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
150 changes: 150 additions & 0 deletions examples/docker-compose/jupyterhub_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Configuration file for jupyterhub.

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.
import os
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'

Loading