dockerspawner.SystemUserSpawner requries --allow-root in TLJH


I am working on a small Jupyterhub deployment for my university group. I try to spawn the singleuser servers in docker containers, while mounting the home directory of the user into the container. Therefore I want to use the SystemUserSpawner as it seems to provide the correct functionality. However, after spawning the container the server is run by root instead of the user, which is not as intended.

The image is build by repo2docker and pushed to a local registry from where it is pulled by DockerSpawner:

jupyter-repo2docker --image-name localhost:5000/test_tools:latest --ref 0.1.0 --target-repo-dir /home/test/test_tools --user-name jovyan --no-run --push test_tools

Configuration of the TLJH is:

from jupyter_client.localinterfaces import public_ips

c.JupyterHub.spawner_class = 'dockerspawner.SystemUserSpawner'
c.SystemUserSpawner.host_homedir_format_string = '/home/jupyter-{username}'
c.DockerSpawner.image = 'localhost:5000/test_tools:latest'

c.JupyterHub.hub_ip = public_ips()[0]
c.JupyterHub.cleanup_servers = False
c.DockerSpawner.cmd = ["jupyterhub-singleuser"]
c.DockerSpawner.remove = True

The handling of the user ids, to fix the file persmissions of the host-home-directory and the hub-home-directory is done by a start script at the entrypoint of the image, which was adapted from the docker-stacks repository ( for which the SystemUserSpawner seems to be optimized:


if [ $(id -u) == 0 ] ; then

    # Only attempt to change the jovyan username if it exists
    if id jovyan &> /dev/null ; then
        echo "Set username to: $NB_USER"
        usermod -d /home/$NB_USER -l $NB_USER jovyan

    # handle home and working directory if the username changed
    if [[ "$NB_USER" != "jovyan" ]]; then
        # changing username, make sure homedir exists
        # (it could be mounted, and we shouldn't create it if it already exists)
        if [[ ! -e "/home/$NB_USER" ]]; then
            echo "Relocating home dir to /home/$NB_USER"
            mv /home/jovyan "/home/$NB_USER"
        # if workdir is in /home/jovyan, cd to /home/$NB_USER
        if [[ "$PWD/" == "/home/jovyan/"* ]]; then
            echo "Setting CWD to $newcwd"
            cd "$newcwd"

    # Change UID of NB_USER to NB_UID if it does not match
    if [ "$NB_UID" != $(id -u $NB_USER) ] ; then
        echo "Set $NB_USER UID to: $NB_UID"
        usermod -u $NB_UID $NB_USER

    # Set NB_USER primary gid to NB_GID (after making the group).  Set
    # supplementary gids to NB_GID and 100.
    if [ "$NB_GID" != $(id -g $NB_USER) ] ; then
        echo "Add $NB_USER to group: $NB_GID"
        groupadd -g $NB_GID -o ${NB_GROUP:-${NB_USER}}
        usermod  -g $NB_GID -aG 100 $NB_USER


exec "$@"

When spinning up a server with this configuration the startup fails with a “Bad Gateway” message on the hub, while the logs of the container show

Running as root is not recommended. Use --allow-root to bypass.

However, if I change the Dockerspawner command to

c.DockerSpawner.cmd = ["jupyterhub-singleuser", "--allow-root"]

the server starts up fine, but it is now executed by root instead of the user.

I hope someone has an idea. I think I am missing only something small, but it can not figure out where it is. When I crawled the web I found similar questions but without solutions, so solving this might also be interesting for others.



I finally solved it myself. The repo2docker images require the hub to start the singleuser-server in the configuration (

c.DockerSpawner.cmd = ['jupyterhub-singleuser']

But as the SingleUserSpawner is written for the jupyter-stacks images, which do not require this, starting the server over the configuration creates the problem. The solution is to discard repo2docker, create a custom Dockerfile from a docker-stacks image, install the repository to this image and delete the above line from the jupyterhub configuration.