Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Taxuspt/heroku_streamlit_nginx
Example project showing how to host multiple streamlit apps on Heroku behind a nginx proxy with authentication
https://github.com/Taxuspt/heroku_streamlit_nginx
Last synced: 3 months ago
JSON representation
Example project showing how to host multiple streamlit apps on Heroku behind a nginx proxy with authentication
- Host: GitHub
- URL: https://github.com/Taxuspt/heroku_streamlit_nginx
- Owner: Taxuspt
- Created: 2019-12-31T12:00:52.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2022-12-08T03:22:19.000Z (almost 2 years ago)
- Last Synced: 2024-06-23T06:39:12.191Z (5 months ago)
- Language: HTML
- Size: 15.6 KB
- Stars: 75
- Watchers: 3
- Forks: 16
- Open Issues: 5
-
Metadata Files:
- Readme: Readme.md
Awesome Lists containing this project
- awesome-streamlit - Host Streamlit on Heroku with Nginx basic authentication
README
# Host Streamlit on Heroku with Nginx basic authentication
This project shows an example of how to:
- Host a streamlit app on Heroku.
- Setup nginx on Heroku and serve the streamlit app via nginx.
- Host multiple streamlit apps under the same process (a single tornado server).
- Add basic user authentication with Nginx to restrict user access to your apps.# Streamlit app hosted on Heroku
## Setup your environment
`mkdir my_project`
`cd my_project`
Since we're hosting on Heroku it's recommended that we create a dedicated virtual environment
`virtualenv .venv`
`source .venv/bin/activate`
At this stage the only dependency is streamlit itself
`pip install streamlit`
Create a streamlit test app. And save it on app.py
# Contents of app.py
import streamlit as st
st.write('This is a streamlit app')
Test your streamlit application locally
`streamlit run app.py`
If all goes well you should see a page with a message
## Setup the Heroku app
Create a Procfile that will launch your app don't forget to set the Port and disable CORs (cross origin requests).
# Contents of Procfile
web: streamlit run --server.enableCORS false --server.port $PORT app.pyFreeze your requirements, Heroku needs this to know how to initialise the environment.
`pip freeze > requirements.txt`
Now, create the heroku app, add it to git and push everything
heroku create
git init
git add .
git commit -m "Repo Init"git push heroku master
After the deployment finished successfully you can visit your app with `heroku open`.
# Setting up the nginx proxy
## Nginx buildpack
Start by adding the nginx buildpack to your Heroku app
`heroku buildpacks:add [https://github.com/heroku/heroku-buildpack-nginx](https://github.com/heroku/heroku-buildpack-nginx)`
You should see
Buildpack added. Next release on will use:
1. heroku/python
2. https://github.com/heroku/heroku-buildpack-nginx
Run git push heroku master to create a new release using these buildpacks.## Changes to the Procfile
The `Procfile` needs a couple of changes:
- Start the nginx server instead of streamlit:
`bin/start-nginx streamlit run --server.enableCORS false --server.port 8501 [app.py](http://app.py/)`
The port on streamlit is set to the default (8501), this port will not be exposed by Heroku but will be accessible by nginx.
- By default, nginx assumes that the "private" webserver is ready when `/tmp/app-initialized` is created. Streamlit uses Tornado but (afaik) there's no way yo capture it's ready state from Streamlit. A (possible flawled) workaround is to wait a few seconds and then manually create this file. e.g. `sleep 10 && touch '/tmp/app-initialized'`The Procfile becomes
# Content of Procfile
web: sleep 10 && touch '/tmp/app-initialized' & bin/start-nginx streamlit run --server.enableCORS false --server.port 8501 app.py## Nginx configuration
A custom configuration can be passed to nginx by creating a `nginx.conf.erb` inside the `/config` folder.
The content of this file is:
# Content of /config/nginx.conf.erb
daemon off;
#Heroku dynos have at least 4 cores.
worker_processes <%= ENV['NGINX_WORKERS'] || 4 %>;events {
use epoll;
accept_mutex on;
worker_connections <%= ENV['NGINX_WORKER_CONNECTIONS'] || 1024 %>;
}http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}gzip on;
gzip_comp_level 2;
gzip_min_length 512;server_tokens off;
log_format l2met 'measure#nginx.service=$request_time request_id=$http_x_request_id';
access_log <%= ENV['NGINX_ACCESS_LOG_PATH'] || 'logs/nginx/access.log' %> l2met;
error_log <%= ENV['NGINX_ERROR_LOG_PATH'] || 'logs/nginx/error.log' %>;include mime.types;
default_type application/octet-stream;
sendfile on;#Must read the body in 5 seconds.
client_body_timeout 5;server {
listen <%= ENV["PORT"] %>;
server_name localhost;
keepalive_timeout 5;location / {
proxy_pass http://127.0.0.1:8501/;
}
location ^~ /static {
proxy_pass http://127.0.0.1:8501/static/;
}
location ^~ /healthz {
proxy_pass http://127.0.0.1:8501/healthz;
}
location ^~ /vendor {
proxy_pass http://127.0.0.1:8501/vendor;
}
location /stream {
proxy_pass http://127.0.0.1:8501/stream;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
}# Adding support to multiple streamlit apps
I considered two different approaches to host multiple streamlit apps on the same server:
- Start a single process (`streamlit run app1`) for each app. This implies starting multiple tornado servers and dealing with the routing with nginx.
- Start a main streamlit app and import other apps as modules from within the main app.There are probably a couple of caveats but I'll go with option 2.
The main app will list all modules from `apps.json` that have a `run` method *(this is potentially unsafe)* and show an option to run each script/app.
# Contents of app.py
import json
import streamlit as stdef import_module(name):
mod = __import__(name)
components = name.split('.')
for comp in components[1:]:
mod = getattr(mod, comp)
return modst.sidebar.markdown('Select an app from the dropdown list')
with open('apps.json','r') as f:
apps = {a['name']:a['module'] for a in json.load(f)}app_names = []
for name, module in apps.items():
if hasattr(import_module(module), 'run'):
app_names.append(name)run_app = st.sidebar.selectbox("Select the app", app_names)
submit = st.sidebar.button('Run selected app')
if submit:
import_module(apps[run_app]).run()The apps are normal streamlit scripts that run via a `run` method.
# Example of streamlit app, stored under scripts/app_something.py
import streamlit as stdef run():
st.write('I am a secondary streamlit app')if __name__ == '__main__':
run()# Adding authentication to nginx
Create a file with your encrypted password with:
`htpasswd -c .htpasswd `
Add the resulting file to the root of the project and edit the `/config/nginx.conf.erb` file blocking the locations that you want to protect. E.g.
# The path to .htpasswd needs to be absolute (assume Heroku's filesystem)
location / {
auth_basic "closed site";
auth_basic_user_file /app/.htpasswd;
proxy_pass http://127.0.0.1:8501/;
}