JupyterHub + SingleUserServer in API only mode

Hi,

My goal is to run both JupyterHub and the SingleUser servers in API only mode. I have not been able to do so for either and any guidance would be greatly appreciated. Does anyone have any tips on what I could do to put JupyterHub and SingleUser server in API only mode?

Here is what I have tried so far
For JupyterHub
I have read through the following:

This is what my values.yml looks like:

singleuser:
    storage:
        ...
    cmd:
        - jupyterhub-singleuser
        - "--ip=0.0.0.0"
        - '--ServerApp.allow_origin="http://localhost:3000"'
        - "--ServerApp.allow_credentials=True"
        - "--NotebookApp.allow_origin=http://localhost:3000"
        - "--NotebookApp.allow_credentials=True"

    extraEnv:
        JUPYTERHUB_SINGLEUSER_APP: "jupyter_server.serverapp.ServerApp"

    image:
        name: ...
        tag: ...

imagePullSecret:
    create: true
    registry: ...
    username: ...
    password: ...
    email:

hub:
    config:
        JupyterHub:
            hub_routespec: "/hub/api"

However, after applying this using helm upgrade, when I navigate to my JupyterHub, I still get the login screen presented:

SingleUser Server API Only mode
To make the Single User Server API only, I tried to modify the following Docker image to make it so that it ran jupyter server instead of jupyter lab.

Since that did not work, I tried to edit the values.yml to run jupyter server instead:

    cmd:
        - "jupyter"
        - "server"
        - "--ip=0.0.0.0"
        - '--ServerApp.allow_origin="http://localhost:3000"'
        - "--ServerApp.allow_credentials=True"
        - "--NotebookApp.allow_origin=http://localhost:3000"
        - "--NotebookApp.allow_credentials=True"

Although this successfully runs the server, the CORS options I set are seeminly ignored when trying to access the Jupyter Server API, likely because I am connecting through the hub URL and jupyter server is not proxying correctly.

I looked through jupyterhub-singleuser and realized that it always seems to create a UI: https://github.com/jupyterhub/jupyterhub/blob/7bf4efd3f814d24481bc755462723740c3f8c900/jupyterhub/singleuser/mixins.py#L950

Is there a way to put jupyterhub-singleuser in API only mode as well?

I know this was long so I really appreciate all your time if you made it this far :slight_smile:

I’m afraid I don’t quite understand. What do you mean by “API only” mode?

Hi manics,

Thanks so much again for the help.

What I meant by API only mode is I am hoping to disable the standard UI when running jupyterhub-singleuser. Ideally it would be the same as running jupyter server locally.

I noticed when I run juptyerhub-singleuser a UI is always created I assume from this part of the code:

I think if you uninstall JupyterLab and any other front-ends, leaving just jupyter-server, this may work? Otherwise there might be a way to disable JupyterLab in your configuration since JupyterLab is a jupyter-server extension, but I’m not sure.

So according the docs that alishobeiri linked in the original post, it should only require setting the c.JupyterHub.hub_routespec to /hub/api/ but that doesn’t seem to work for me either.

Can you provide more information on what doesn’t work?

So based on the docs I’ve found here: Deploying JupyterHub in “API only mode” — JupyterHub documentation, I added c.JupyterHub.hub_routespec = '/hub/api/'. However, I am still able to access the /hub/home. Am I misunderstanding what that configuration does, or doing something incorrectly. Any help would be much appreciated, thank you!

Specifically I would prefer if we could prevent the jupyterhub home page from being accessed directly, I would like to link the user from my application directly to their server.

I’ve just run JupyterHub 4.1.5 with c.JupyterHub.hub_routespec = "/hub/api/" and I get a 404 if I go to / and /hub/home.

Can you share your full configuration, and give full details of how you’ve installed JupyterHub?

I’ve got jupyter hub installed via helm and here’s the load config
I tried to add the prefix to the hub_routespec since I set the base_url, but it still didn’t work

Loaded config files:
  /usr/local/etc/jupyterhub/jupyterhub_config.py

ConfigurableHTTPProxy
  .api_url = 'http://proxy-api:8001'
  .should_start = False
CryptKeeper
  .keys = ['']
JupyterHub
  .admin_access = True
  .allow_named_servers = True
  .authenticator_class = 'dummy'
  .base_url = '/jupyter/'
  .cleanup_servers = False
  .concurrent_spawn_limit = 64
  .config_file = '/usr/local/etc/jupyterhub/jupyterhub_config.py'
  .cookie_secret = ''
  .db_url = 'sqlite:///jupyterhub.sqlite'
  .hub_bind_url = 'http://:8081'
  .hub_connect_url = 'http://hub:8081'
  .hub_routespec = '/jupyter/hub/api/'
  .last_activity_interval = 60
  .load_roles = [   {   'name': 'service-role',
        'scopes': ['admin:users', 'tokens', 'read:hub', 'servers'],
        'services': ['core-api']}]
  .services = [{'api_token': 'change-me', 'name': 'core-api'}]
  .spawner_class = 'kubespawner.KubeSpawner'
  .template_paths = []
  .template_vars = {}
  .tornado_settings = {'headers': {'Access-Control-Allow-Origin': 'https://local.flywheel.io'}}
KubeSpawner
  .allow_privilege_escalation = False
  .common_labels = {   'app': 'jupyterhub',
    'chart': 'jupyterhub-3.3.7',
    'heritage': 'jupyterhub',
    'release': 'flywheel'}
  .environment = {}
  .events_enabled = True
  .extra_annotations = {}
  .extra_containers = []
  .extra_labels = {'hub.jupyter.org/network-access-hub': 'true'}
  .extra_pod_config = {}
  .extra_resource_guarantees = {}
  .extra_resource_limits = {}
  .fs_gid = 100
  .image = 'quay.io/jupyterhub/k8s-singleuser-sample:3.3.7'
  .init_containers = [   {'args': None,
 'command': ['iptables',
             '--append',
             'OUTPUT',
             '--protocol',
             'tcp',
             '--destination',
             '169.254.169.254',
             '--destination-port',
             '80',
             '--jump',
             'DROP'],
 'env': None,
 'env_from': None,
 'image': 'quay.io/jupyterhub/k8s-network-tools:3.3.7',
 'image_pull_policy': None,
 'lifecycle': None,
 'liveness_probe': None,
 'name': 'block-cloud-metadata',
 'ports': None,
 'readiness_probe': None,
 'resize_policy': None,
 'resources': {},
 'restart_policy': None,
 'security_context': {'allow_privilege_escalation': None,
                      'capabilities': {'add': ['NET_ADMIN'], 'drop': None},
                      'privileged': True,
                      'proc_mount': None,
                      'read_only_root_filesystem': None,
                      'run_as_group': None,
                      'run_as_non_root': None,
                      'run_as_user': 0,
                      'se_linux_options': None,
                      'seccomp_profile': None,
                      'windows_options': None},
 'startup_probe': None,
 'stdin': None,
 'stdin_once': None,
 'termination_message_path': None,
 'termination_message_policy': None,
 'tty': None,
 'volume_devices': None,
 'volume_mounts': None,
 'working_dir': None}]
  .lifecycle_hooks = {}
  .mem_guarantee = '1G'
  .namespace = 'default'
  .node_affinity_preferred = [   {   'preference': {   'matchExpressions': [   {   'key': 'hub.jupyter.org/node-purpose',
                                                      'operator': 'In',
                                                      'values': ['user']}]},
        'weight': 100}]
  .node_affinity_required = []
  .node_selector = {}
  .pod_affinity_preferred = []
  .pod_affinity_required = []
  .pod_anti_affinity_preferred = []
  .pod_anti_affinity_required = []
  .pre_spawn_hook = <function pre_spawn_hook at 0xffff91803560>
  .profile_list = []
  .pvc_name_template = 'claim-{username}{servername}'
  .scheduler_name = 'flywheel-user-scheduler'
  .start_timeout = 300
  .storage_access_modes = ['ReadWriteOnce']
  .storage_capacity = '10Gi'
  .storage_extra_labels = {}
  .storage_pvc_ensure = True
  .tolerations = [   {   'effect': 'NoSchedule',
        'key': 'hub.jupyter.org/dedicated',
        'operator': 'Equal',
        'value': 'user'},
    {   'effect': 'NoSchedule',
        'key': 'hub.jupyter.org_dedicated',
        'operator': 'Equal',
        'value': 'user'}]
  .uid = 1000
  .volume_mounts = [{'mountPath': '/home/jovyan', 'name': 'volume-{username}{servername}'}]
  .volumes = [   {   'name': 'volume-{username}{servername}',
        'persistentVolumeClaim': {'claimName': 'claim-{username}{servername}'}}]
NotebookApp
  .allow_remote_access = 1
Spawner
  .args = ['--NotebookApp.allow_origin=https://local.flywheel.io']
  .cmd = ['fw-notebook-start.sh']
  .consecutive_failure_limit = 5
  .delete_pvc = True

Which version of the Z2JH chart are you using? Can you share your Z2JH configuration file(s)?

I was using the latest versions, but I found a somewhat different solution that accomplishes the same thing. I’ve turned off authentication through the UI using the NullAuthenticator.