REBRANDING NOTICE: EzDeploy is now Roptal. Platform launching on roptal.com. Docs: docs.roptal.com.REBRANDING NOTICE: EzDeploy is now Roptal. Platform launching on roptal.com. Docs: docs.roptal.com.REBRANDING NOTICE: EzDeploy is now Roptal. Platform launching on roptal.com. Docs: docs.roptal.com.REBRANDING NOTICE: EzDeploy is now Roptal. Platform launching on roptal.com. Docs: docs.roptal.com.
Oryvo
← All articles

Deploying Streamlit ML Apps to Production: What Breaks and How to Fix It

Streamlit is fast for demos. Deploying to production reveals auth, concurrency, and state issues. Here is how to fix them.

Deploying Streamlit ML Apps to Production: What Breaks and How to Fix It

Streamlit is the fastest way to build an ML demo. 50 lines of Python, you have an interactive app. But deploying it to production reveals problems that don't show up on localhost.

Problem 1: Streamlit Is Single-User by Design

Streamlit runs one session per process. When two users open your app simultaneously, they share the same Python process. Widget state leaks between users. If user A uploads a file and user B clicks Submit before their own file is ready, user B gets user A's results.

Fix: Session state isolation.

Assign each browser tab a unique session ID and prefix all session state keys:

import streamlit as st
from uuid import uuid4

if "session_id" not in st.session_state:
    st.session_state.session_id = str(uuid4())
    
sid = st.session_state.session_id
st.session_state[f"{sid}_uploaded_file"] = None

This works for a few concurrent users. Beyond that, you need multiple Streamlit processes behind a load balancer with sticky sessions.

Problem 2: No Built-in Authentication

Streamlit has no auth. Anyone with the URL can use your app. In development, fine. In production, you're exposing your inference API and GPU to the internet.

Fix: Reverse proxy with auth.

Put nginx or Cloudflare Access in front:

server {
    listen 443 ssl;
    
    location / {
        auth_basic "Restricted";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://localhost:8501;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

For multi-user apps, use OAuth with Cloudflare Access or an auth proxy like oauth2-proxy.

Problem 3: No Production-Grade Server

streamlit run app.py uses Tornado, which is fine for development but not built for production traffic. No worker processes, no graceful restarts, no request queuing.

Fix: Multiple workers with gunicorn.

Streamlit doesn't support gunicorn, but you can run multiple instances behind nginx:

streamlit run app.py --server.port 8501 &
streamlit run app.py --server.port 8502 &
streamlit run app.py --server.port 8503 &

And configure nginx to load balance across them. Each instance handles a subset of users.

Problem 4: State Is In-Memory Only

Streamlit session state lives in RAM. Restart the server, lose all state. Scale to multiple instances, state isn't shared.

Fix: Externalize state to Redis.

import redis
import pickle

r = redis.Redis(host='localhost', port=6379)

def save_state(user_id, data):
    r.set(f"user:{user_id}", pickle.dumps(data), ex=3600)

def load_state(user_id):
    raw = r.get(f"user:{user_id}")
    return pickle.loads(raw) if raw else {}

All instances share the same Redis. If an instance dies, the user's session continues on another instance.

Problem 5: No Health Checks

Streamlit has no health endpoint. Your load balancer can't tell if an instance is healthy.

Fix: Add a health endpoint via Flask sidecar.

Run a tiny Flask app on a separate port:

from flask import Flask
import threading

health_app = Flask(__name__)

@health_app.route('/health')
def health():
    return {"status": "ok"}, 200

def run_health():
    health_app.run(port=8500)

threading.Thread(target=run_health, daemon=True).start()

The load balancer checks port 8500. If Flask returns 200, the instance is healthy.

When Streamlit Is the Wrong Choice

If you need:

  • Multi-user isolation (< 5 concurrent users)
  • Sub-100ms latency (Streamlit re-runs the entire script on each interaction)
  • REST API access (Streamlit is UI-only)
  • Custom frontend (Streamlit controls the entire layout)

Then you should build a FastAPI + custom frontend instead. Streamlit is great for internal tools, dashboards, and demos. Not for public-facing products.

When Streamlit Is the Right Choice

  • Internal dashboards and data exploration tools
  • Prototypes and demos (fastest path from idea to working UI)
  • Single-user analysis tools
  • Anything where time-to-deploy matters more than scalability

Roptal detects Streamlit apps during repository analysis and configures deployments accordingly — health checks, multiple workers behind a reverse proxy, and Redis-backed session state when needed.

Deploying Streamlit ML Apps to Production: What Breaks and How to Fix It — Oryvo AI Blog