Jupyterhub + Streamlit

I just managed to get streamlit working inside of JupyterHub.

Prereqs: Make sure to have https://github.com/jupyterhub/jupyter-server-proxy installed + enabled.

streamlit hello --browser.serverAddress 0.0.0.0 --server.enableCORS False

optional: --server.port 8501 (in case you want to run it on a different port, like one opened in the container)

Env:
Running Jupyterhub with Docker, each user gets their own container with one of several pre-built environments they can choose from a drop-down list.

Issues I ran into:

  1. Finding documentation about command-line arguments was difficult. I didn’t have access to edit ~/.streamlit/config.toml because of restricted access, hence the command-line.
  2. Installing serverproxy in a container that didn’t already have it pre-installed doesn’t seem to work. No explanation for why. Using an image that came with serverproxy was fine. Intuition is that it shouldn’t matter, experiments have shown otherwise.
  3. CORS must be disabled. Stuck “loading” if not. I take care of all SSL at the nginx-level, and proxy-pass to the jupyterhub instance, so the safety wasn’t a concern for me (hub is running on http). Let me know if I’m wrong (I’m not a web-dev).
  4. Had to visit user/michael/proxy/5000/ (and the trailing slash does matter)
  5. Technically, the user-image that worked for me used the following install instructions, not pip install:
 git clone --depth 1 https://github.com/jupyterhub/jupyter-server-proxy && \
    cd jupyter-server-proxy/jupyterlab-server-proxy && \
    npm install && npm run build && jupyter labextension link . && \
    npm run build && jupyter lab build

I ran that in a container that didn’t have the proxy installed, didn’t fix lack of access.

At PyDataLA right now, so I don’t quite have the time to dig in and build a minimally-working Dockerfile, but that’s definitely a next step for debugging.

A bloated image (with R/Julia/Octave/Python) that does allow the proxy-pass can be found on docker hub at mathematicalmichael/math-user:latest

Got a minimal working example! Reproduction steps:

Dockerfile:

FROM jupyter/minimal-notebook:latest
RUN pip install streamlit jupyter-server-proxy jupyter-rsession-proxy
ENV JUPYTER_ENABLE_LAB=yes

So, apparently all that was missing was the jupyter-rsession-proxy, which I happened to be using to get RStudio working in JupyterHub.
Anyway, make that Dockerfile and then run:

docker built -t streamlit-image .
docker run --rm -p 8888:8888 streamlit-image jupyter lab --NotebookApp.token=''
  1. Visit port 8888 on localhost (or the IP of your remote machine, as in my case).
  2. Open Terminal and run streamlit hello --browser.serverAddress 0.0.0.0 --server.enableCORS False
  3. In your address bar, swap /lab with /proxy/8501/ (trailing slash important!)
  4. Enjoy the wonderful goodness that is the streamlit demo

This should make app-demos fairly easy. Swap jupyter lab ... for the streamlit command, map 8888:8501 instead and use nginx to proxy-pass to 127.0.0.1:8888/

In practice… That hasn’t worked for me. I suspect it’s some problem in my nginx config (headers?). I’m able to hit the app but see “Please wait”. Web console says something about an unspported state transition.
Would love help with that…
With my above plan, if I visit the IP of my remote machine at port 8888 instead of the web address that I was attempting to proxy-pass, the app runs (on http)! That’s promising. Basically says that we have a docker-run command now:

docker run --rm -p 8501:8501 streamlit-image streamlit hello --browser.serverAddress 0.0.0.0 (CORS seems to only be an issue for the proxy-via-jupyterlab approach).

4 Likes

This is so amazing. Now I have somewhere to point people to when this comes up. Thanks for coming to the talk today!!

docker build* not built

also not really having a whole lot of success with this in an already-running JLab. It seems like if it exists upon startup, this solution works. But now I’m on someone else’s jupyter deployment so I don’t have the ability to do anything except pip install from JLab’s terminal. Hitting 404 errors.

quick update on using with docker-machine (I didn’t install the desktop edition on my new mac, figuring out the kinks/differences as I go, finally got around to testing streamlit).

since docker won’t bind localhost to the docker-machine IP, you’ll have to disable CORS for local development.

@Adrien_Treuille @mathematicalmichael I just came across this older thread, and wanted to link to one I posted recently, below.

I’ve been working on running Streamlit in JupyterHub but without using Docker. I also have experience doing this with Docker too, so if I get a chance I’ll go through your notes and see if I can write something up based on our ideas combined.

Thanks for this example @mathematicalmichael! I took this and wrapped it a bit further by adding streamlit to the jupyter config so that you just need to go to /streamlit to access the server.

I’ve got an example on renkulab.io that anyone can fork and extend if they wish to play around with it: https://renkulab.io/projects/rok.roskar/streamlit-demo

The key piece is the jupyter_notebook_config.py file that defines streamlit as a server extension so if you want to run your own piece of code you would need to amend that file (or add a second spec).

1 Like

nice!
I’m actually having some trouble getting around some of the new security. does yours still work with latest streamlit?

updated Dockerfile that I hope can help people figure out the associated instructions

# base image
FROM python:3.7

# streamlit-specific commands
RUN mkdir -p /root/.streamlit
RUN bash -c 'echo -e "\
[general]\n\
email = \"\"\n\
" > /root/.streamlit/credentials.toml'
RUN bash -c 'echo -e "\
[server]\n\
enableCORS = false\n\
enableXsrfProtection = false\n\
" > /root/.streamlit/config.toml'

# exposing default port for streamlit
EXPOSE 8501

# copy over and install packages
COPY requirements.txt ./requirements.txt
RUN pip3 install -r requirements.txt

# copying everything over
#COPY . .
WORKDIR /st
RUN useradd appuser && chown -R appuser /st
USER appuser

# run app
CMD streamlit run
>cat ~/bin/streamlit 
#!/bin/sh
IMAGE_NAME=streamlit:latest
COMMAND=streamlit
docker run --rm --name streamlit -d -p 8501:8501 -v $(pwd):/tmp -w /tmp $IMAGE_NAME $COMMAND $@ --browser.serverAddress 0.0.0.0 --server.enableCORS False --server.enableXsrfProtection False

so from within jupyter terminal this would be: streamlit run app.py --browser.serverAddress 0.0.0.0 --server.enableCORS False --server.enableXsrfProtection False

1 Like

I have extended my work on JupyterHub + Streamlit and released a full JupyterHub extension that easily allows deployment of any dashboarding framework within JupyterHub (e.g. Streamlit, Voila, Plotly Dash etc).

This takes care of configuration for you - just specify the Streamlit file (or a Git repo URL) in a page under the new ‘Dashboard’ menu and it will automatically start a new server running only Streamlit. You can allow access to any authenticated JupyterHub user, or specify individual users who should have access.

It also means users can keep using their regular Jupyter servers just as before, and easily spin up a new ‘dashboard’ for any new Streamlit apps. Really, it’s just leveraging JupyterHub’s security and deployment model.

I’d be really pleased if you get to try it out and let me know how it goes! The extension is ContainDS Dashboards.

1 Like

whoaaa that I need to try

Please do try it out! Let me know how it goes, or if you need any help.

1 Like

@danlester loving the ContainsDS extension - hitting an issue where I get a Error: Request failed with status code 403 when trying to upload a file to a Streamlit app running in a dashboard - thinking it is XRSF related but not sure what to do from there. Any thoughts here? Thanks!

@chrishumphries Great to hear you like ContainDS Dashboards! Yes, I think there is a problem with XRSF (which @mathematicalmichael also alluded to).

I am working on fixing this ‘properly’ but in the meantime you can try adding this to your jupyterhub_config:

c.VariableMixin.extra_presentation_launchers = {
    'streamlit': {
        'args': ['--destport=0', 'streamlit', 'run', '{presentation_path}', 
                '{--}server.port={port}',
                '{--}server.headless=True',
                '{--}server.enableXsrfProtection=false'],
    }
}

You’ll need to restart the dashboard.

The problem is that Streamlit only sets the XRSF cookie once, and that is during the ‘websocket handshake’ - which is perhaps not enough really, and is missed through ContainDS’ proxy. Maybe Streamlit would be better off with a fix to make this more robust, but hopefully I can work around it in ContainDS Dashboards anyway. I’ll take a look. For now, turning off XSRF is fine for a lot of JupyterHubs which are behind firewalls etc.

I’ll update here when a fix is available, but any other problems or feedback about the extension please just get in touch wherever makes the most sense!

Thanks Dan - if I am using TLJH, would I still make a jupyterhub_config file in /opt/tljh/config?

exactly the kinds of things I have no idea how to fix. do ping this thread when they’re resolved.

@chrishumphries yes you would just add the snippet I included earlier at the end of the /opt/tljh/config/jupyterhub_config.d/cdsdashboards_config.py file that you would have created during ContainDS Dashboards setup for TLJH.

@mathematicalmichael Definitely - will let you know!

@chrishumphries @mathematicalmichael I have released a new version of ContainDS Dashboards that should solve this problem. It’s version 0.3.2 - upgrade instructions here.

You would no longer need the config snippet provided earlier. I’d really appreciate if you can try it out.