Customizing JupyterHub on Kubernetes

I think that’s why JupyteHub cant find your templates, can you tried to set c.JupyterHub.template_paths with the complete path to your templates folder:

    extraConfig:
      templates: |
        c.JupyterHub.template_paths = ['/etc/jupyterhub/custom/jupyterhub/templates']

@bitnik thanks for the eye opener. Even though my templates are not in the root directory,
All of git path is not being cloned, only the template directory is clone as you can see:

jovyan@hub-7465dbb7c9-rntb7:/srv/jupyterhub$ ls /etc/jupyterhub/custom/templates/
404.html    error.html  login.html   not_running.html  page.html   spawn_pending.html  token.html
admin.html  home.html   logout.html  oauth.html        spawn.html  stop_pending.html
jovyan@hub-7465dbb7c9-rntb7:/srv/jupyterhub$ 

Thank you very much, I got it working. Have a blessed day

Hi @bitnik,

I’ve followed the above tips and here’s my relevant config.yaml

jupyterhub:
  hub:
    # clone custom JupyterHub templates into a volume
    initContainers:
      - name: git-clone-templates
        image: alpine/git
        args:
          - clone
          - --single-branch
          - --branch=master
          - --depth=1
          - --
          - https://github.com/datainpoint/custom-jhub-templates.git
          - /etc/jupyterhub/custom
        securityContext:
          runAsUser: 0
        volumeMounts:
          - name: custom-templates
            mountPath: /etc/jupyterhub/custom
    extraVolumes:
      - name: custom-templates
        emptyDir: {}
    extraVolumeMounts:
      - name: custom-templates
        mountPath: /etc/jupyterhub/custom
    extraConfig:
      templates: |
        c.JupyterHub.template_paths = ['/etc/jupyterhub/custom']

However, my login page remained the same.
Is there something wrong with my config.yaml or my git repository for custom login.html?
Btw, the git repository is a real one: https://github.com/datainpoint/custom-jhub-templates.git

hi @yaojenkuo, your repo’s main branch is main but in the config you pass --branch=master to git clone. Can you change it with --branch=main and try again? You could also check logs of the git-clone-templates container (kubectl logs <hub-pod-name> -c git-clone-templates -n <namespace>) and see if there are other errors.

1 Like

Hi @bitnik, tons of thanks! I really did not notice that GitHub’s new branch naming would cause me this much trouble setting up a custom login page. I’ve also noticed that the jupyterhub key at the top might also affect initContainers. Now my login.html is functioning well and below is my revised config.yaml:

hub:
# clone custom JupyterHub templates into a volume
  initContainers:
    - name: git-clone-templates
      image: alpine/git
      args:
        - clone
        - --single-branch
        - --branch=main
        - --depth=1
        - --
        - https://github.com/datainpoint/custom-jhub-templates.git
        - /etc/jupyterhub/custom
      securityContext:
        runAsUser: 0
      volumeMounts:
        - name: custom-templates
          mountPath: /etc/jupyterhub/custom
  extraVolumes:
    - name: custom-templates
      emptyDir: {}
  extraVolumeMounts:
    - name: custom-templates
      mountPath: /etc/jupyterhub/custom
  extraConfig:
    templates: |
      c.JupyterHub.template_paths = ['/etc/jupyterhub/custom']

Thanks for sharing this Erik. In case it affects other people too, my version of helm didn’t find the files with

data:
  {{- (.Files.Glob "files/etc/jupyterhub/templates/*").AsConfig | nindent 2 }}

I would get an empty configmap. If I changed the globstring to files/etc/jupyterhub/templates/*.html (adding the .html) it would find the HTML files in that folder.

I believe I’m using Helm 3.5.2.

Hi @consideRatio

I have done the steps you mentioned and created hub-external.yaml and hub-templates.yaml files to my jupyterhub helm chart template folder…
image

image

also helm upgrade with

jupyter hub pod renders html files in mounted volumes but when doing login page.html line 125 which wants to inject admin:user and cannot render the page due to error…
before these configuration I could not see the admin pannel either…

this is Error from running kubectl logs -f :slight_smile:

{% if ‘admin:users’ in parsed_scopes and ‘admin:servers’ in parsed_scopes %}
jinja2.exceptions.UndefinedError: ‘parsed_scopes’ is undefined

How can I customize branding and logo and html files with this way or other method like extra cofigFile?

tnx;

It sounds like your templates are invalid! Maybe there’s a mismatch in the version of JupyterHub and your templates? Or maybe there’s a genuine error in them?

Hey I am also getting this same error:
{% if ‘admin:users’ in parsed_scopes and ‘admin:servers’ in parsed_scopes %}
jinja2.exceptions.UndefinedError: ‘parsed_scopes’ is undefined

I don’t know where parsed_scopes is supposed to be defined. Did you find a solution to this issue by any chance?

Make sure to start from the templates for the version of JupyterHub that you have. This error would occur if you have the templates from JupyterHub 2.x, but are running JupyterHub 1.x.

Hi everyone

I have Jupyterhub running with Kubernetes on a cloud machine. Is there a way to directly mount a templates folder onto the pod rather than going the Github route explained here in the comments. The Configmap method seems very complicated to me.

Rather than pulling from Github at start cant I just mount an existing folder with my templates. Is that possible or are these the only two ways to apply custom UI templates to Jupyterhub. I just need some clarification and explanation in this matter.

Thanks

Ultimately, templates can be any folder, so any way you can create a folder in the container will work. So yes, you can always create and mount a volume with templates via hub.extraVolumeMounts. If you go that route, you will have to create a volume with that folder somehow, which would have to be done outside the chart.

In case it helps, I am also using initContainers to pull static images.

    initContainers:
      - name: git-clone-templates
        image: alpine/git
        args:
          - clone
          - --single-branch
          - --branch=main
          - --depth=1
          - --
          - https://github.com/<your-repo>.git
          - /etc/jupyterhub/custom
        volumeMounts:
          - name: custom-templates
            mountPath: /etc/jupyterhub/custom
      - name: git-clone-static
        image: alpine/git
        args:
          - clone
          - --single-branch
          - --branch=pp-aup
          - --depth=1
          - --
          - https://github.com/<your-repo>.git
          - /usr/local/share/jupyterhub/static/custom
        volumeMounts:
          - name: custom-static
            mountPath: /usr/local/share/jupyterhub/static/custom
    extraVolumes:
      - name: custom-templates
        emptyDir: {}
      - name: custom-static
        emptyDir: {}
    extraVolumeMounts:
      - name: custom-templates
        mountPath: /etc/jupyterhub/custom
      - name: custom-static
        mountPath: /usr/local/share/jupyterhub/static/custom
    extraConfig:
      templates: |
        c.JupyterHub.template_paths = ['/etc/jupyterhub/custom/templates']

k8s is easy… they say :smile: