How to host a Streamlit app from a sandbox and share the link
Install Streamlit in the sandbox, start streamlit run app.py on port 8501 with spawn, and share that port as an HTTPS preview.
On Runtime a public Streamlit demo can call a paid API without the key being in the sandbox. Store the key as a Runtime secret: the app sees a placeholder, and the host's proxy puts the real key into requests to the API's own host. A 1 vCPU, 2 GiB sandbox that waits for visitors costs $0.01625 an hour, $11.70 for a 30-day month kept running, because Runtime bills the CPU the app uses. Streamlit 1.64.0 was current on 25 September 2026.
Start the app and share it
TypeScriptimport { Sandbox } from "withruntime";const sbx = await Sandbox.create({ timeoutSeconds: 3600 }); // runs for its lease, then pausesawait sbx.exec("pip install -q streamlit==1.64.0", { check: true, timeoutMs: 600_000 });await sbx.files.upload("./dashboard", "/workspace/dashboard");await sbx.exec("cd dashboard && pip install -q -r requirements.txt", { check: true, timeoutMs: 600_000,});await sbx.spawn( "streamlit run app.py --server.port 8501 --server.headless true --browser.gatherUsageStats false", { cwd: "/workspace/dashboard" },);await sbx.exec("npx wait-on@9.1.0 http://127.0.0.1:8501", { check: true, timeoutMs: 120_000 });const preview = await sbx.previews.create(8501);console.log(preview.urlWithToken); // private: a one-time link for your browserPythonfrom withruntime import Sandboxsbx = Sandbox.create(timeout_seconds=3600) # runs for its lease, then pausessbx.exec("pip install -q streamlit==1.64.0", check=True, timeout_ms=600_000)sbx.files.upload("./dashboard", "/workspace/dashboard")sbx.exec("cd dashboard && pip install -q -r requirements.txt", check=True, timeout_ms=600_000)sbx.spawn("streamlit run app.py --server.port 8501 --server.headless true --browser.gatherUsageStats false", cwd="/workspace/dashboard")sbx.exec("npx wait-on@9.1.0 http://127.0.0.1:8501", check=True, timeout_ms=120_000)print(sbx.previews.create(8501)["urlWithToken"])Streamlit's browser page talks to its server over a WebSocket, and previews
carry WebSockets, so widgets and reruns work through the link. In a test on
25 September 2026, Streamlit 1.64.0 answered a page request and accepted the
WebSocket upgrade for a host under runtimehost.com with its default
settings, so it needs no CORS or host changes.
The flags that matter in a sandbox
| Flag | Why |
|---|---|
--server.port 8501 |
Streamlit's default; any port can take a preview |
--server.headless true |
Do not try to open a browser or prompt for an email address |
--browser.gatherUsageStats false |
Streamlit sends usage statistics unless told not to |
--server.maxUploadSize 200 |
The file_uploader limit in megabytes; 200 is the default |
--server.runOnSave true |
Rerun when a file changes, for an agent editing the app live |
Every setting can also go in .streamlit/config.toml in the app's folder.
Call a paid API without handing over the key
A dashboard that answers questions with a model needs an API key. Store it as
a secret, and every sandbox of the account gets OPENAI_API_KEY holding a
placeholder that is worthless anywhere but api.openai.com:
Terminalprintf %s "$OPENAI_API_KEY" | runtime secrets set OPENAI_API_KEY --host api.openai.comThe app's code does not change: openai.OpenAI() reads OPENAI_API_KEY
from the environment and sends it in its Authorization header, where the
proxy swaps in the real value. A visitor who finds a way to make the app print
its environment sees rtsec_..., and so does anything the page sends
elsewhere (secrets).
Share it publicly and keep it up
For a link anyone can open, make the preview public. To keep the demo running
past an hour, a paid sandbox can be persistent: its lease renews itself while
the account has credit, and maxTotalCostMicros sets the most it may cost over
its life:
TypeScriptimport { Sandbox } from "withruntime";const demo = await Sandbox.getOrCreate("sales-dashboard", { funding: "paid", persistent: true, vcpu: 1, memoryMiB: 2048, maxTotalCostMicros: 20_000_000, // at most $20 over its life});if (!demo.info.reused) { await demo.exec("pip install -q streamlit==1.64.0", { check: true, timeoutMs: 600_000 }); await demo.files.upload("./dashboard", "/workspace/dashboard"); await demo.spawn("streamlit run app.py --server.port 8501 --server.headless true", { cwd: "/workspace/dashboard", });}const link = await demo.previews.create(8501, { visibility: "public" });console.log(link.url);| Setting | Effect |
|---|---|
visibility: "public" |
Anyone with the address can open it; no token |
previews.rotate(8501) |
Private previews only: every token issued so far stops working |
previews.delete(8501) |
Stop sharing the port |
persistent: true |
Paid: the lease renews itself while credit lasts |
maxTotalCostMicros |
The most the sandbox may cost over its whole life |
| A custom domain | Paid: serve it at your own hostname (networking) |
getOrCreate answers the same sandbox on the next run of this script, so the
app is installed once and the link stays the same.
Start with Streamlit installed
A custom image with Streamlit and the app's packages saves the install on every new sandbox:
Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="streamlit", recipe={"pip": ["streamlit==1.64.0", "plotly", "openai"]})sbx = runtime.sandboxes.create(image="streamlit")sbx.files.upload("./dashboard", "/workspace/dashboard")sbx.spawn("streamlit run app.py --server.port 8501 --server.headless true", cwd="/workspace/dashboard")Related
- Run a Gradio demo in a sandbox
- Preview apps an AI agent builds
- A data analysis agent that runs Python safely
- Share a port
Sources
Checked 25 September 2026.
- Streamlit config.toml reference:
server.port(8501),server.headless,browser.gatherUsageStats(default true),server.maxUploadSize(200),server.runOnSave,server.allowedHosts(empty accepts any host) - streamlit on PyPI: version 1.64.0, Python 3.10 or later
- A local test of Streamlit 1.64.0 on 25 September 2026: HTTP 200 and a
WebSocket upgrade (101) on
/_stcore/streamfor the host8501-abc.runtimehost.comwith default settings
Facts on this page were checked on 25 September 2026.