backend / web frameworks / streamlit / 06_deployment_and_secrets.md

Streamlit Deployment and Secrets - Interview Questions

3 interview angles 5 min read source

Streamlit Deployment and Secrets - Interview Questions

1. How do you deploy a Streamlit app?

Five common paths, ordered by operational overhead:

Option When to pick
Streamlit Community Cloud Public demos, side projects; free; one-click deploy from GitHub
Snowflake / Streamlit in Snowflake Already on Snowflake; data stays in-warehouse
Hugging Face Spaces ML demos, open access, free with limits
Docker on a VM / ECS / Cloud Run / App Service Internal tools at companies; full control; behind your own auth
Kubernetes You already run K8s; Streamlit is one more Deployment + Service + Ingress

For internal company use, the typical choice is Docker behind an OAuth proxy (oauth2-proxy, Cloudflare Access, AWS ALB OIDC) — Streamlit has no built-in auth.


2. What does a Streamlit Dockerfile look like?

FROM python:3.14-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8501

HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1

CMD ["streamlit", "run", "app.py", \
     "--server.port=8501", \
     "--server.address=0.0.0.0", \
     "--server.headless=true", \
     "--server.enableXsrfProtection=true"]

Key flags:

  • --server.headless=true — don’t try to open a browser.
  • --server.address=0.0.0.0 — listen on all interfaces (required in containers).
  • --server.enableCORS=false --server.enableXsrfProtection=true is the safer default behind a proxy that strips origin headers.

/_stcore/health is the built-in liveness endpoint.


3. How do you manage secrets in Streamlit?

Two approaches, often combined:

st.secrets (TOML file):

.streamlit/secrets.toml
OPENAI_API_KEY = "sk-..."

[database]
host = "db.example.com"
port = 5432
user = "app"
password = "..."
key = st.secrets["OPENAI_API_KEY"]
db_host = st.secrets["database"]["host"]

The file is git-ignored. On Streamlit Community Cloud you paste secrets into the dashboard — same TOML schema.

Environment variables — read with os.environ or via pydantic-settings. Standard 12-factor approach; works with K8s secrets, AWS Parameter Store, Vault.

import os
key = os.environ["OPENAI_API_KEY"]

For real apps: env vars + a secret manager (AWS Secrets Manager, Vault, GCP Secret Manager). secrets.toml is fine for local dev and Community Cloud demos.


4. How do you add authentication?

Streamlit has no native auth. Options:

1. Front with an auth proxy (recommended for production). Put oauth2-proxy / Cloudflare Access / AWS ALB OIDC in front. Streamlit reads the user from a header:

user = st.context.headers.get("X-Forwarded-User")
if not user:
    st.error("Unauthorized")
    st.stop()

2. streamlit-authenticator — community library, hashed credentials in YAML. Fine for internal tools with a small user list:

import streamlit_authenticator as stauth

auth = stauth.Authenticate(config["credentials"], "cookie", "key", 30)
name, status, username = auth.login("Login", "main")
if status:
    st.write(f"Welcome {name}")
elif status is False:
    st.error("Bad credentials")

3. st.user (Community Cloud) — Google sign-in built into Streamlit Community Cloud. Access via st.user.email.

4. Roll your own — login form → call your auth service → set st.session_state["user"]st.rerun(). Don’t store passwords; only tokens.

For anything internet-facing, prefer option 1. Don’t put a hand-rolled login on the public internet.


5. How do you scale Streamlit horizontally?

Each user holds an open WebSocket to a specific Streamlit process. To scale beyond one process:

  1. Run multiple replicasstreamlit run app.py is single-process; deploy N pods.
  2. Sticky sessions — required. The load balancer must route a user back to the same pod for the lifetime of their session. Use cookie-based stickiness or session-affinity in the LB.
  3. Don’t share in-memory statest.session_state is per-process. Anything needing cross-pod consistency goes to Redis / DB.
  4. Cache shared resources@st.cache_resource is per-process; each pod loads its own copy of the model. Plan for memory accordingly.

Typical sizing: 1 vCPU + 1–2 GB RAM per pod handles tens of concurrent users for a light app, far fewer for a chat app holding LLM streams.


6. What configuration knobs matter in production?

.streamlit/config.toml:

[server]
headless = true
port = 8501
address = "0.0.0.0"
maxUploadSize = 200          # MB
enableCORS = false
enableXsrfProtection = true

[browser]
gatherUsageStats = false      # opt out of telemetry

[theme]
base = "light"
primaryColor = "#1f77b4"

[runner]
fastReruns = true             # cancel previous run on new interaction (default true)

Override per-run with --server.port=... flags or STREAMLIT_* env vars (e.g., STREAMLIT_SERVER_PORT=8501).

For internal tools, also set [client] showErrorDetails = false to avoid leaking tracebacks to end users.


7. How do you set up logging and observability?

Streamlit’s own logger writes to stdout; standard Python logging works:

import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)

log.info("user %s opened dashboard", user_id)

In containers, log to stdout/stderr and let the platform (CloudWatch / Cloud Logging / Loki) collect.

For metrics, instrument with Prometheus (custom counters around expensive operations) or push to OpenTelemetry. There’s no built-in metrics endpoint — /_stcore/health is for liveness only.

For error tracking, Sentry’s Python SDK works as in any web app:

import sentry_sdk
sentry_sdk.init(dsn=st.secrets["SENTRY_DSN"], environment="prod")

8. How do you handle CSRF, XSS, and other web vulnerabilities?

  • XSRF — enabled by default (enableXsrfProtection = true). Don’t disable.
  • XSSst.markdown doesn’t render HTML by default. unsafe_allow_html=True lets it render — only use with content you control. Never pass user input through unsafe_allow_html without sanitization.
  • CORS — disabled by default in production setups; the WebSocket only accepts same-origin connections. Behind a reverse proxy this just works.
  • Secrets in URLs — don’t put tokens in st.query_params; they end up in browser history and proxy logs.
  • Code injection via uploads — never eval or exec uploaded content. Treat uploaded files as untrusted.
  • Resource exhaustion via uploads — set maxUploadSize, validate file type, scan for size before parsing.
  • Pickle deserialization — never load pickle files from users; use safer formats (parquet, CSV, JSON).

Same OWASP-Top-10 hygiene as any other web app — see ../../25_security/.


9. How do you do zero-downtime deploys?

Streamlit holds long-lived WebSockets, so rolling restarts disconnect users mid-session. Options:

  • Accept the disconnect. For internal tools, a brief “connection lost — reconnecting” flash is fine.
  • Blue/green — spin up the new version alongside, drain the old. Long-running sessions on the old version finish before it’s killed.
  • Rolling with sticky sessions — new connections go to new pods; existing ones stay on old pods until those drain.

For high-stakes apps with long-running computations: write intermediate state to a DB so a reconnect can resume; otherwise the user starts over.


Interview angle

  • “How do you handle secrets in Streamlit?” - st.secrets reading from a TOML file locally and platform-provided secrets in deployment. Never hardcode, and keep the secrets file out of version control.
  • “How do you deploy it?” - a container behind a reverse proxy with WebSocket support, or Streamlit Community Cloud for internal use. WebSocket proxying is the configuration people miss.
  • “How do you scale it?” - horizontally with sticky sessions, because session state is in-process. Anything needing to survive a restart belongs in an external store.