Back to Blog
Tutorial9 min read2026-07-04

How to Deploy a Streamlit Data App

Streamlit turns Python scripts into interactive data apps, but it runs a stateful WebSocket server that needs the right deployment setup. This guide containerizes Streamlit and connects it to a database.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Streamlit is the fastest way to turn a data script into a shareable web app — dashboards, model demos, internal tools. The catch when deploying is that Streamlit isn't a static site or a stateless API: it's a stateful server that holds a WebSocket connection per user session. Get the deployment details right and it just works.

This guide containerizes a Streamlit app, wires it to a managed database, and deploys it.

What makes Streamlit different to deploy

  • Stateful sessions. Each browser tab is a session held over WebSocket. You can't blindly load-balance across replicas without sticky sessions.
  • Single long-lived server. It runs streamlit run, not a build-and-serve-static flow.
  • Re-runs top to bottom. Every interaction re-executes the script, so expensive work must be cached.

Because of session state, the simplest robust deployment is a single replica (or sticky-session routing if you scale out). For most internal data apps, one well-sized replica is plenty.

Step 1: Cache expensive work

Before deploying, make sure your app caches data loads and model inits — otherwise every widget click re-queries your database.

import streamlit as st
import pandas as pd
import os
from sqlalchemy import create_engine

@st.cache_resource
def get_engine():
    return create_engine(os.environ["DATABASE_URL"], pool_pre_ping=True)

@st.cache_data(ttl=300)
def load_sales():
    return pd.read_sql("SELECT * FROM sales", get_engine())

df = load_sales()
st.title("Sales Dashboard")
st.metric("Total", f"${df.revenue.sum():,.0f}")
st.bar_chart(df.groupby("region").revenue.sum())

@st.cache_resource for connections/models; @st.cache_data for serializable data with a TTL.

Step 2: Containerize

FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8501
CMD ["streamlit", "run", "app.py", \
     "--server.port=8501", \
     "--server.address=0.0.0.0", \
     "--server.headless=true", \
     "--browser.gatherUsageStats=false"]
# requirements.txt
streamlit==1.39.0
pandas==2.2.3
sqlalchemy==2.0.35
psycopg2-binary==2.9.9

The critical flags:

  • --server.address=0.0.0.0 so the platform ingress can reach it.
  • --server.headless=true so it doesn't try to open a browser.
  • --server.port=8501 (Streamlit's default).

Step 3: Provision a managed database

Most data apps read from a database. Create a managed PostgreSQL (14.x or 16.x) on [PandaStack](https://dashboard.pandastack.io) and link it to the app — DATABASE_URL is injected automatically, which the SQLAlchemy engine above reads directly. No connection-string juggling.

Use pool_pre_ping=True (shown earlier) so stale connections after idle periods are recycled cleanly.

Step 4: Deploy

  1. 1Push the repo to GitHub.
  2. 2Create a container app and connect the repo — BuildKit builds the Dockerfile and Helm deploys it.
  3. 3Link the managed PostgreSQL database.
  4. 4Expose port 8501.
  5. 5Set the health check to /_stcore/health (Streamlit's built-in health endpoint).
  6. 6Add a custom domain; SSL is automatic.

Step 5: Handle WebSockets and scaling

Streamlit needs WebSocket upgrades to pass through the ingress — PandaStack's Kong ingress handles WebSocket connections, so live widgets work out of the box.

On scaling:

StrategyWhen
Single replicaDefault for internal data apps — simplest, no session issues
Multiple replicas + sticky sessionsHigh-traffic public dashboards
Scale-to-zero (free tier)Low-traffic internal tools where a cold start is acceptable

For a rarely used internal dashboard, free-tier scale-to-zero is a great fit — it costs nothing when idle and spins up on the next visit (with a short cold start). For an always-watched dashboard, keep one warm replica.

Step 6: Add authentication

Streamlit has no built-in auth for arbitrary deployments. Protect internal apps with:

  • PandaStack team/org access controls and SSO.
  • A reverse-proxy auth layer (e.g. Cloudflare Access) on the custom domain.
  • Streamlit's native authentication features for OIDC providers if you wire them up.

Don't leave a dashboard that queries production data open to the internet.

Common pitfalls

  • Forgetting caching — leads to a database query storm on every interaction.
  • Local file writes — containers are ephemeral; write to object storage or the database.
  • Wrong health check — use /_stcore/health, not /.
  • Blocking the main thread — long computations freeze the UI; cache or offload them.

References

  • [Streamlit documentation](https://docs.streamlit.io/)
  • [Streamlit caching](https://docs.streamlit.io/develop/concepts/architecture/caching)
  • [Streamlit Docker deployment](https://docs.streamlit.io/deploy/tutorials/docker)
  • [Streamlit configuration options](https://docs.streamlit.io/develop/api-reference/configuration/config.toml)

---

Streamlit is a joy once the stateful-server details are handled — caching, WebSockets, and a single sized replica. PandaStack auto-wires a managed PostgreSQL via DATABASE_URL and its Kong ingress passes WebSockets through cleanly. Deploy your first data app free at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also