# 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 ```ts check import { Sandbox } from "withruntime"; const sbx = await Sandbox.create({ timeoutSeconds: 3600 }); // runs for its lease, then pauses await 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 ``` ```python check from withruntime import Sandbox sbx = Sandbox.create(timeout_seconds=3600) # runs for its lease, then pauses sbx.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`: ```bash no-run printf %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](/docs/security#secrets-sandboxes-never-see)). ## 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: ```ts check import { 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](/docs/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](/docs/images) with Streamlit and the app's packages saves the install on every new sandbox: ```python check from withruntime import Runtime runtime = 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](/integrations/gradio) - [Preview apps an AI agent builds](/use-cases/preview-agent-built-apps) - [A data analysis agent that runs Python safely](/use-cases/data-analysis-agent) - [Share a port](/docs/javascript#share-a-port) ## Sources Checked 25 September 2026. - [Streamlit config.toml reference](https://docs.streamlit.io/develop/api-reference/configuration/config.toml): `server.port` (8501), `server.headless`, `browser.gatherUsageStats` (default true), `server.maxUploadSize` (200), `server.runOnSave`, `server.allowedHosts` (empty accepts any host) - [streamlit on PyPI](https://pypi.org/project/streamlit/): 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.