Make a container suitable for JupyterHub

The documentation of Zero to Jupyterhub explains how to make a container for KubeSpawner starting from one in docker-stacks: Customizing User Environment — Zero to JupyterHub with Kubernetes documentation

However, I need to start from an existing CUDA container.
My attempt has been to just install the jupyterhub package:

FROM nvcr.io/nvidia/tensorflow:22.04-tf2-py3
RUN pip install --no-cache-dir jupyterhub==1.5.0

However, I get some complaints from pip about running as root, which I don’t think is a problem.
But then when I try to use this into JupyterHub I get:

Traceback (most recent call last):        
  File "/usr/local/lib/python3.8/dist-packages/traitlets/traitlets.py", line 537, in 
get                                                                                  
    value = obj._trait_values[self.name]  
KeyError: 'runtime_dir'                                                              
                                                                                     
During handling of the above exception, another exception occurred:                  
                                                                                     
Traceback (most recent call last):                                                   
  File "/usr/local/bin/jupyterhub-singleuser", line 8, in <module>                   
    sys.exit(main())                      
  File "/usr/local/lib/python3.8/dist-packages/jupyter_core/application.py", line 264
, in launch_instance                      
    return super(JupyterApp, cls).launch_instance(argv=argv, **kwargs)               
  File "/usr/local/lib/python3.8/dist-packages/traitlets/config/application.py", line
 845, in launch_instance                                                             
    app.initialize(argv)                                                             
  File "/usr/local/lib/python3.8/dist-packages/jupyterhub/singleuser/mixins.py", line
 852, in initialize                                                                  
    result = super().initialize(*args, **kwargs)                                     
  File "/usr/local/lib/python3.8/dist-packages/jupyterhub/singleuser/mixins.py", line
 573, in initialize
    return super().initialize(argv)                                                  
  File "/usr/local/lib/python3.8/dist-packages/traitlets/config/application.py", line 88, in inner                                                                            return method(app, *args, **kwargs)
  File "/usr/local/lib/python3.8/dist-packages/notebook/notebookapp.py", line 2121, i
n initialize                                                                             self.init_configurables()                                                          File "/usr/local/lib/python3.8/dist-packages/notebook/notebookapp.py", line 1650, in init_configurables
    connection_dir=self.runtime_dir,                                                   File "/usr/local/lib/python3.8/dist-packages/traitlets/traitlets.py", line 577, in __get__                                                                              
    return self.get(obj, cls)                                                          File "/usr/local/lib/python3.8/dist-packages/traitlets/traitlets.py", line 540, in get    
    default = obj.trait_defaults(self.name)                                            File "/usr/local/lib/python3.8/dist-packages/traitlets/traitlets.py", line 1580, in trait_defaults                                                                          return self._get_trait_default_generator(names[0])(self)                           File "/usr/local/lib/python3.8/dist-packages/jupyter_core/application.py", line 95,
 in _runtime_dir_default
    ensure_dir_exists(rd, mode=0o700)
  File "/usr/local/lib/python3.8/dist-packages/jupyter_core/utils/__init__.py", line 
11, in ensure_dir_exists
    os.makedirs(path, mode=mode)
  File "/usr/lib/python3.8/os.py", line 213, in makedirs
    makedirs(head, exist_ok=exist_ok)
  File "/usr/lib/python3.8/os.py", line 213, in makedirs
    makedirs(head, exist_ok=exist_ok)
  File "/usr/lib/python3.8/os.py", line 213, in makedirs
    makedirs(head, exist_ok=exist_ok)
  File "/usr/lib/python3.8/os.py", line 223, in makedirs
    mkdir(name, mode) 
PermissionError: [Errno 13] Permission denied: '/.local'

Would you have suggestions on how to debug this further? Thanks

Does your container work if you run it locally (without JupyterHub) and launch JupyterLab/notebook? If it doesn’t then focus on getting that to work, then move on to working out the JupyterHub configuration.

1 Like

Judging from the error (permission error on /.local, which is $HOME/.local), I think what’s missing is a user. If you create a user with a home directory in the image and set $HOME, I bet that’s the main thing.

1 Like

Thanks @minrk, it was it!

I got the user configuration from docker-stacks/Dockerfile at bb1c56b5c73ea791cc7555da232ae0c87f18c992 · jupyter/docker-stacks · GitHub

into my Dockerfile:

Here is the relevant section:

ARG NB_USER="jovyan"
ARG NB_UID="1000"
ARG NB_GID="100"

# Fix: https://github.com/hadolint/hadolint/wiki/DL4006
# Fix: https://github.com/koalaman/shellcheck/wiki/SC3014
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

USER root

COPY fix-permissions /usr/local/bin/fix-permissions
RUN chmod a+rx /usr/local/bin/fix-permissions

ENV HOME="/home/${NB_USER}"

# Enable prompt color in the skeleton .bashrc before creating the default NB_USER
# hadolint ignore=SC2016
RUN sed -i 's/^#force_color_prompt=yes/force_color_prompt=yes/' /etc/skel/.bashrc

# Create NB_USER with name jovyan user with UID=1000 and in the 'users' group
# and make sure these dirs are writable by the `users` group.
RUN echo "auth requisite pam_deny.so" >> /etc/pam.d/su && \
    useradd -l -m -s /bin/bash -N -u "${NB_UID}" "${NB_USER}" && \
    chmod g+w /etc/passwd && \
    fix-permissions "${HOME}"

USER ${NB_UID}

WORKDIR "${HOME}"