You start a server in a workspace terminal:
streamlit run app.pyand then you want to look at it. A preview gives that server a URL you can open in your browser. There's no SSH tunnel to set up and nothing to forward from your own machine.
Where it lives
Open Workspaces, find the row, and click the … button at the end of it. Ports… sits in that menu, beside Files….
It opens a dialog listing every port currently listening inside the workspace, with the name of the process holding each one. The list refreshes every few seconds while the dialog is open, so you can leave it up and start your server afterwards. Each row has an Open button and a button that copies the link.
Both give you a URL of this shape:
https://privatemind.com/w/<org>/<workspace>-<nonce>/preview/<port>/You never type it yourself, though a couple of servers need to be handed it. It doesn't change while the workspace exists, restarts included, so you can keep one in a script.
Who can open it
A preview link sits behind the same gate as every other workspace surface, and it follows the workspace's visibility. While a workspace is private, only you and your org's compute admins can open its previews. Flip it to shared and anyone in your org can, the same as JupyterLab or the terminal.
It is never a public link. Someone outside your org, or someone who isn't signed in, gets a refusal instead of your page. See Sharing with your team.
Most servers need nothing
Requests arrive at your server with the /w/<org>/<workspace>-<nonce>/preview/<port> prefix removed, so it sees the paths it expects: /, /static/app.css. Any server that links to its own pages with relative URLs works with nothing configured. Streamlit, Gradio, FastAPI and python -m http.server all work with no flags.
Which address your server binds doesn't matter. Localhost is fine, and so is binding every interface.
Websockets pass through, so streaming responses work and Streamlit's own socket connects unaided.
To see it working:
cd ~/projects/my-app
python -m http.server 8000Open the … menu, then Ports…, find 8000, click Open.
Servers that build their own URLs
Some servers write absolute URLs into the pages they serve. FastAPI's /docs page is the common one: its routes work with no flags, but the page it serves fetches /openapi.json, which starts at the root of the domain and never reaches your server.
The fix is that server's own root-path setting. It changes the URLs the server builds and leaves the paths it accepts alone.
| Server | Setting |
|---|---|
| FastAPI / uvicorn | --root-path |
| Gradio | root_path= |
The value is that port's preview link, which is what the copy button gives you. Leave off the trailing slash.
uvicorn main:app --port 8000 \
--root-path https://privatemind.com/w/<org>/<workspace>-<nonce>/preview/8000demo.launch(
server_port=7860,
root_path="https://privatemind.com/w/<org>/<workspace>-<nonce>/preview/7860",
)There's an awkward loop here: you need the link before you can pass it, and the link only shows up once the port is listening. Start the server, open Ports…, copy the link, stop the server, start it again with the root path set. The link is stable afterwards, so it's once per port and not once per session.
Streamlit needs nothing here.
Vite and Next dev servers
The Vite and Next dev servers don't work behind a preview, and there's no setting that fixes it. Both emit their asset URLs from the root (/@vite/client, /src/main.js), so those requests never reach your server. Setting their base option makes the dev server redirect to that base, the redirect comes back through the proxy, and the browser ends up in a loop.
Building the app and serving the output does work, as long as the build uses relative asset paths. By default Vite writes them from the root (/assets/…), which a preview never receives, so the page comes up blank. Pass --base ./:
npm run build -- --base ./
python -m http.server 8000 -d distNext's production server, next start, hasn't been tested behind a preview.
Which ports you can use
Ports 1024 to 65535, minus the ones the workspace's own surfaces hold: 2222, 7681 to 7685, 7691, 7692, 7694, 8443, 8444 and 8888. Those never appear in the Ports… list, and opening one directly is refused with a page naming the surface that holds it. 8888 is the one people run into, because it's JupyterLab's port here and the default for a fair few other things.
The usual defaults are free: 8000 for uvicorn and python -m http.server, 8501 for Streamlit, 7860 for Gradio.
Redirects
When your server redirects the browser, it writes the redirect in its own terms. Location: /login means "this port's /login", and the preview puts the prefix back on so you stay inside it rather than landing at the root of the site. Relative redirects, and redirects that already carry the prefix, are left alone. So is a redirect to another host, which takes you there.
Troubleshooting
"Nothing is listening on port 8000." A short page saying that means the request reached the workspace and found no server on the port. Usually the process exited, or it's on a different port from the one you opened. Look at the terminal it's running in, read the port off its startup line, and reopen Ports… to see what's listening now.
Your port isn't in the list. The list covers 1024 and up, and it hides the ports the workspace's own surfaces hold, so a server on 8888 is invisible there and can't be previewed. Move it to another port. If it's already on a free port, check that it started.
The page loads with no styling. The HTML came through and the CSS and JavaScript didn't. That's the root-path case above: the page is asking for its assets from the root of the domain, and they are not there. Set the server's root-path option to that port's preview link and start it again. If it's a Vite or Next dev server, that's the case with no fix, so build it with relative asset paths and serve the output instead (see above).
A link took you somewhere unexpected. If you land on the workspace list or a sign-in page, your server sent the browser to an absolute URL on another host, and previews pass those through unchanged.
Where next
- Workspaces: the environment itself, its surfaces, sharing, and SSH access from your own machine.
- Deploy a model: if what you want is a long-lived endpoint on the API rather than a look at something you're building.