App shapes

Publish a Streamlit app

Write your Streamlit app the way you always would. Swap `st.session_state` for persistence with `ellivate.kv`. Run `ellivate publish` and you have a hosted Streamlit app on a shareable URL.

A minimal Streamlit app

Save as app.py:

import streamlit as st
import ellivate

st.title("Reading tracker")

books = ellivate.kv.get("shared:books") or []

with st.form("add", clear_on_submit=True):
    title = st.text_input("Book")
    submitted = st.form_submit_button("Add")
    if submitted and title:
        books.append(title)
        ellivate.kv.set("shared:books", books)
        st.rerun()

for book in books:
    st.write(f"• {book}")

And a requirements.txt:

streamlit>=1.30

Run ellivate publish. The classifier recognizes Streamlit from requirements.txt and emits the right Procfile — streamlit run app.py --server.address=0.0.0.0 --server.port=$PORT.

Per-viewer vs app-wide data on Streamlit

Use Streamlit when: the app's data is legitimately shared across all viewers — a household dashboard, a team retrospective, a reading log for a book club.

Don't use Streamlit when: each user needs their own state — private notes, personal settings, user-specific drafts.

Identity on Streamlit

Because Streamlit apps are server-lane, your Streamlit code doesn't need to read the viewer. If you want to show who is signed in on the shell (for display only), you can read from the env var:

import os
# Available when Ellivate injects a server-lane viewer token
signed_in = os.environ.get("ELLIVATE_VIEWER_TOKEN") is not None
if signed_in:
    st.caption("You're signed in through Ellivate.")

Public-vs-private

Streamlit apps default to private + auth-required, the same as any other shape. Flip to public in the dashboard or at publish time:

  • Private (default). Only people you invite see the app. Great for internal tools.
  • Public + gated. Listed on your profile, but users must sign in. Good for apps that are discoverable but not open.
  • Public + open. Anyone can open it, no sign-in. Good for demos or always-shared dashboards.

Caching and reruns

Streamlit's rerun model works as-is. A few tips:

  • Wrap expensive KV reads in @st.cache_data(ttl=60) to avoid hitting the backend on every interaction.
  • Call st.rerun() after a write so the next render sees the fresh data.
  • st.session_state is fine for ephemeral in-session UI state (open/closed panels, multi-step forms). It's per-session in memory — doesn't persist across page refreshes.

Things that fail

  • Hardcoded file paths. Streamlit apps often load CSVs via pd.read_csv("data.csv"). That works if the file is committed to your project directory. It doesn't work if you're writing to a file — containers are ephemeral.
  • st.experimental_* APIs that Streamlit has deprecated. Pin streamlit>=1.30 and use the stable APIs; older experimental ones may not resolve in the classifier's version pin.
  • streamlit-authenticator or any auth library. Identity is the shell's job.

What's next

  • Persistence & scopes — the server-lane model and when to reach for Flask instead.
  • Flask — for apps that need per-viewer data.