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=trueis 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:
- Run multiple replicas —
streamlit run app.pyis single-process; deploy N pods. - 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.
- Don’t share in-memory state —
st.session_stateis per-process. Anything needing cross-pod consistency goes to Redis / DB. - Cache shared resources —
@st.cache_resourceis 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. - XSS —
st.markdowndoesn’t render HTML by default.unsafe_allow_html=Truelets it render — only use with content you control. Never pass user input throughunsafe_allow_htmlwithout 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
evalorexecuploaded content. Treat uploaded files as untrusted. - Resource exhaustion via uploads — set
maxUploadSize, validate file type, scan for size before parsing. - Pickle deserialization — never load
picklefiles 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.
10. Cross-links
- Secrets/config patterns: ../../../system_design/04_secrets_config/
- Observability: ../../15_observability/
- Docker basics: ../../16_docker/
- Security: ../../25_security/
- Authentication: ../../11_authentication/
Interview angle
- “How do you handle secrets in Streamlit?” -
st.secretsreading 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.