Why Streamlit hosting is its own problem
Streamlit makes it absurdly easy to turn a Python script into a data app. The hard part shows up *after* the demo works: you want it private, you want it to talk to a database, you want it to not fall asleep, and you want secrets that aren't pasted into a public repo.
Streamlit runs as a long-lived stateful WebSocket server. That single fact rules out a lot of "serverless" platforms and shapes every recommendation below. A Streamlit app holds a persistent connection per browser session and keeps server-side session state in memory — so you want a host that runs a real container or VM, not one that spins up a fresh function per request.
What actually matters for a Streamlit host
- Persistent process / WebSockets. Streamlit needs a running server, not request/response functions.
- Private by default. Most internal dashboards should not be on a public URL with no auth.
- Secrets management. API keys and DB URLs as environment variables, not
secrets.tomlin git. - A managed database nearby. Most useful Streamlit apps read from Postgres, MySQL, or a warehouse.
- Sane cost when idle. Internal tools are used a few hours a day. Scale-to-zero or a cheap floor matters.
- Custom domain + SSL.
dashboard.yourcompany.com, not a random subdomain.
The options in 2026
Streamlit Community Cloud
The official free host. Unbeatable for public demos and sharing notebooks-as-apps. The catch: it's built for public, lightweight apps. Private apps and serious resource limits push you elsewhere, and you're tied to GitHub-based deploys. Great first stop, rarely the final one.
Hugging Face Spaces
Excellent if your app is ML-adjacent and you want community visibility. Spaces supports Streamlit and Gradio natively and has GPU options. It's less of a fit for a private internal BI tool wired to your production Postgres.
A raw VPS (Hetzner, DigitalOcean, etc.)
Maximum control, lowest sticker price. You run streamlit run, set up a reverse proxy (nginx/Caddy), a systemd unit, TLS, and auth yourself. Fine if you enjoy that; a recurring chore if you don't. No managed database unless you add one.
General PaaS (containers)
Any container platform that runs a long-lived process works. You write a Dockerfile or lean on buildpacks, expose the port, and you're live. The differentiator becomes: does it also give you a managed database, private networking, and scale-to-zero so an idle dashboard costs little?
PandaStack
PandaStack runs Streamlit as a container app: connect the repo, it auto-detects Python, builds with rootless BuildKit, and deploys. You get a managed PostgreSQL/MySQL alongside it with DATABASE_URL auto-injected, env-var secrets, custom domains with automatic SSL, and free-tier apps that scale to zero (with a cold start) so an idle internal tool isn't burning money.
Comparison
| Platform | Persistent server | Private apps | Managed DB nearby | Idle cost | Best for |
|---|---|---|---|---|---|
| Streamlit Community Cloud | Yes | Limited | No | Free | Public demos |
| Hugging Face Spaces | Yes | Paid tiers | No | Free/low | ML demos |
| Raw VPS | Yes (DIY) | DIY | DIY | Fixed monthly | Full control |
| General PaaS | Yes | Yes | Varies | Varies | Production apps |
| PandaStack | Yes | Yes | Yes (auto-wired) | Scale-to-zero (free tier) | Internal data apps + DB |
Deploying Streamlit on a container platform
The key is binding to the platform's port and 0.0.0.0, and disabling CORS/XSRF gymnastics for the proxy. A minimal Dockerfile:
FROM python:3.12-slim
WORKDIR /app
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"]If you use buildpacks instead of a Dockerfile, set the start command explicitly:
streamlit run app.py --server.port=$PORT --server.address=0.0.0.0 --server.headless=trueRead secrets from the environment rather than secrets.toml:
import os
import streamlit as st
import psycopg
@st.cache_resource
def get_conn():
return psycopg.connect(os.environ["DATABASE_URL"])
conn = get_conn()
with conn.cursor() as cur:
cur.execute("select count(*) from events")
st.metric("Events", cur.fetchone()[0])Note the @st.cache_resource on the connection — Streamlit re-runs your whole script on every interaction, so you do *not* want a fresh DB connection per rerun.
Gotchas that bite people
- Cold starts with scale-to-zero. Convenient for cost, but the first request after idle waits for the container to wake. Fine for internal tools, less so for a customer-facing dashboard — keep a warm instance there.
- Session state is per-connection and in-memory. If the platform runs multiple replicas, a user can land on different instances. Keep Streamlit to a single replica or externalize state (e.g., Redis).
- WebSocket-hostile proxies. Some CDNs/proxies buffer or drop long-lived connections. Make sure your host explicitly supports WebSockets (Kong-style ingress does).
- Long-running callbacks block the rerun. Push heavy jobs to a background worker or cronjob and poll results.
My recommendation
For a quick public demo, start on Streamlit Community Cloud — it's free and frictionless. For an internal tool that reads from a real database, needs to be private, and shouldn't cost much while idle, use a container platform with an attached managed DB. That's exactly the shape PandaStack targets: push the repo, get a running Streamlit container, a managed Postgres with DATABASE_URL already injected, SSL, and scale-to-zero on the free tier.
References
- Streamlit deployment docs: https://docs.streamlit.io/deploy
- Streamlit Community Cloud: https://streamlit.io/cloud
- Hugging Face Spaces: https://huggingface.co/docs/hub/spaces
- Streamlit configuration reference: https://docs.streamlit.io/develop/api-reference/configuration/config.toml
- psycopg 3 docs: https://www.psycopg.org/psycopg3/docs/
---
Want to host a private Streamlit dashboard with a managed Postgres wired in automatically? PandaStack's free tier gives you containers + a database to try it. Start at https://dashboard.pandastack.io