Runtime

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 browser
Pythonfrom 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.com

The 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")

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/stream for the host 8501-abc.runtimehost.com with default settings

Facts on this page were checked on 25 September 2026.