Skip to content
Guides · Recipes

How to Run Storybook in a Sandbox With a Shareable URL

Updated October 27, 2026 .md

Short answer: Run storybook dev on port 6006 in a sandbox, add .box.sandboxer.com to core.allowedHosts, set Vite's hmr.clientPort to 443, then run sbx host 6006 --localhost. Keep that dev URL private. For a link anyone can open, run storybook build, serve storybook-static and host that port with --public.

What we ran#

Everything below ran on prod on 2026-10-27, on one small sandbox (2 vCPU, 4 GB), with Storybook 10.6.4, Vite 8.3.5 and React 19 on the sandbox's Node.js 22. Tokens are replaced with <token>. For how hosted URLs, tokens and cookies work in general, see Give a sandbox port an HTTPS URL. This page covers what Storybook needs on top of that.

Step Result
Vite scaffold and npm install 10.3 s
storybook init 25.7 s
Dev server start (systemd restart to first answer) 1.6 to 2.4 s
Dev URL, default config 502, then 403 Invalid host
Dev URL with allowedHosts 200, 11 stories in index.json, about 0.19 s per request
Hot reload through the URL Broken until hmr.clientPort: 443; then the edit showed in 176 ms, no page reload
storybook build 3.6 s, 8.5 MB, 63 files
Static build on a public URL 200, Button story drawn in 4.5 s in headless Chromium
After sbx stop --wait and sbx resume memoryRestored: true, same PIDs, both URLs answered at once

Dev server or static build?#

Dev server (storybook dev) Static build (storybook build)
Who it's for You, or a reviewer pairing with you Designers, PMs, anyone with the link
Hot reload Yes, over WebSockets No, rebuild to update
What a visitor can do Storybook warns that the dev server lets clients write files in your project through its WebSocket channel Read the built HTML, JS and CSS
Hosted access Private only Public is fine if the components aren't secret
Server Node.js, about 2 s to start Any static file server

1. Install Storybook#

Storybook's install docs add it to an existing project. We made a Vite React app first, then ran the initializer without prompts:

sbx new --name storybook --type small
sbx exec sb_dt3tuszvq5 --timeout 600 '
  npm create vite@latest sb-demo -- --template react-ts --no-interactive
  cd sb-demo && npm install
  npx -y storybook@latest init --yes --no-dev --features docs --disable-telemetry --no-agent'
added 28 packages in 9s
…
◇  Storybook was successfully installed in your project!

--no-dev stops init from starting a server inside your exec. --features docs skips the test addon, which would also pull in Vitest and browser binaries. The default --timeout of sbx exec is 30 s, and init took 25.7 s, so raise it.

2. Run the dev server as a service#

npm run storybook runs storybook dev -p 6006, which listens on 127.0.0.1 only. A systemd unit keeps it running and starts it again after a cold boot. Run this inside the sandbox (over sbx ssh, or save it as a script and pass it to sbx exec). It writes the unit with your user, home and npm path filled in:

NPM=$(command -v npm)
sudo tee /etc/systemd/system/storybook-dev.service >/dev/null <<UNIT
[Unit]
Description=Storybook dev server
After=network-online.target
[Service]
User=$USER
WorkingDirectory=$HOME/sb-demo
Environment=PATH=$(dirname $NPM):/usr/bin:/bin
Environment=STORYBOOK_DISABLE_TELEMETRY=1
ExecStart=$NPM run storybook -- --no-open --ci
Restart=always
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload && sudo systemctl enable --now storybook-dev
ss -ltn | grep 6006
LISTEN 0      511        127.0.0.1:6006      0.0.0.0:*

It answered on localhost 2.4 s after the start.

3. Host it, and the two errors you will hit#

A plain sbx host can't reach a server bound to 127.0.0.1:

sbx host sb_dt3tuszvq5 6006 --title "Storybook dev"
https://direct-noble-squid-6006.box.sandboxer.com?_token=<token>

Without the token, the URL returns 401. With it, you get the 30-day cookie and then 502 with the page "Nothing is answering". Host the same port again with --localhost so the sandbox relays to the loopback address. The URL and token stay the same:

sbx host sb_dt3tuszvq5 6006 --localhost --title "Storybook dev"

Now Storybook itself answers, and refuses the request:

/ with cookie: 403  Invalid host
/index.json with cookie: 403  Invalid host

Storybook checks the Host header, and direct-noble-squid-6006.box.sandboxer.com isn't on its list. Add the hosted domain to core.allowedHosts. A leading dot allows every subdomain, so the same file works in any sandbox:

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
import { mergeConfig } from 'vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: ['@storybook/addon-docs'],
  framework: '@storybook/react-vite',
  // Accept requests for the hosted URL (*.box.sandboxer.com).
  core: { allowedHosts: ['.box.sandboxer.com'] },
  // The browser reaches the sandbox on 443, not on 6006.
  viteFinal: (config) => mergeConfig(config, { server: { hmr: { clientPort: 443 } } }),
};
export default config;

After sudo systemctl restart storybook-dev (1.6 s to the first answer):

/ with cookie: 200 in 0.195 s
/index.json with cookie: 200 in 0.191 s
11 entries: configure-your-project--docs, example-button--docs, example-button--large, example-button--primary, …

A request without the token still gets 401 from the proxy, so Storybook never sees it.

Storybook offers another route: --host 0.0.0.0 and a plain sbx host. We tried that only on a local machine, not on prod. There, Storybook 10.6.4 printed a warning that it was listening on all interfaces, that anyone who can reach it can read your stories and source files and write files in your project through the WebSocket channel and the MCP server, and to set core.allowedHosts to keep host validation on. The --localhost route above keeps validation on.

4. Fix hot reload behind the URL#

allowedHosts alone gets you a working Storybook, but edits don't show up. We opened the token link in headless Chromium (Playwright), edited Button.tsx in the sandbox, and waited for the change. It never came. The browser log showed why:

[vite] connecting...
WebSocket connection to 'wss://direct-noble-squid-6006.box.sandboxer.com:6006/?token=<token>' failed: net::ERR_CONNECTION_REFUSED
[vite] failed to connect to websocket (Error: WebSocket closed without opened.).

Vite's client in the preview iframe connects to the dev server's port, 6006, on the hosted name. The hosted URL only serves 443. Storybook's own channel (/storybook-server-channel) used 443 and connected. The viteFinal line in the file above sets Vite's server.hmr.clientPort to 443. With it, the same test passed:

first response: 200
[vite] connecting...
[vite] connected.
Primary story rendered after 4113 ms, button text: Button
[vite] hot updated: /src/stories/Button.tsx
edited Button.tsx in the sandbox: new class showed up after 176 ms
page reloaded: false

The browser had only the cookie from the token link. The WebSocket handshakes carried that cookie, so hot reload works on a private URL without extra steps.

5. Publish a static build on a public URL#

For a link you can paste in a PR or a chat, build Storybook and serve the files. The publishing docs describe the build; any static server works, and Python is already in the sandbox:

sbx exec sb_dt3tuszvq5 --timeout 600 "cd ~/sb-demo && npm run build-storybook && du -sh storybook-static"
◇  Output directory: /home/user/sb-demo/storybook-static
8.5M	storybook-static

The build took 3.6 s. A second unit serves it on port 6007, bound to all interfaces:

# /etc/systemd/system/storybook-static.service
[Unit]
Description=Static Storybook
After=network-online.target

[Service]
User=user
ExecStart=/usr/bin/python3 -m http.server 6007 --directory /home/user/sb-demo/storybook-static
Restart=always

[Install]
WantedBy=multi-user.target
sbx exec sb_dt3tuszvq5 "sudo systemctl daemon-reload && sudo systemctl enable --now storybook-static"
sbx host sb_dt3tuszvq5 6007 --public --title "Storybook static"
/: 200 in 0.197 s
/index.json: 200 in 0.192 s
/iframe.html?id=example-button--primary&viewMode=story: 200 in 0.291 s
/storybook-server-channel: 404 in 0.192 s

There is no write channel to reach: /storybook-server-channel is a plain 404. Headless Chromium drew the Button story from https://direct-noble-squid-6007.box.sandboxer.com/?path=/story/example-button--primary in 4.5 s, with no WebSockets opened. To update the link, run npm run build-storybook again. The server picks up the new files without a restart.

6. Stop and resume#

sbx stop sb_dt3tuszvq5 --wait
sbx resume sb_dt3tuszvq5

While stopped, both URLs returned 404. The resume took 1.3 s and reported "memoryRestored": true. Both servers kept their PIDs, the public URL answered 200 on the first try, and the dev URL answered 200 with the cookie set before the stop.

Memory comes back only when the resume lands on a machine with the same CPU type. Otherwise the sandbox boots from disk: files, hosted routes and tokens are kept, and processes start fresh. Both units are enabled, so systemd starts them at boot. We didn't force a cold boot on this sandbox; Run code-server in the browser shows the same systemd pattern surviving one, tested with a fork.

FAQ#

Why does my hosted Storybook say "Invalid host"?#

Storybook rejects Host headers it doesn't know. Add '.box.sandboxer.com' to core.allowedHosts in .storybook/main.ts and restart the dev server.

Storybook loads but my edits don't appear. Why?#

Vite's hot reload client tries to connect on port 6006, which the hosted URL doesn't serve. Set server.hmr.clientPort to 443 in viteFinal. The browser console shows [vite] connected. when it works.

Can I make the dev server URL public?#

Don't. Storybook's own warning says clients of the dev server can read source files and write files in your project. Keep the dev port private and share a static build with --public instead.

No. Anyone with the full link gets a 30-day cookie and can use Storybook, hot reload included. Treat the link like a password. Switching the port to public and back to private makes a new token and cuts off the old link.

More guides

Top AI sandbox platforms for code execution in 2026, ranked

For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.

Read the guide
Best sandboxes for running coding agents (Claude Code, Codex, Gemini CLI) in 2026, ranked

For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.

Read the guide
Best Daytona sandbox alternatives in 2026, ranked

The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.

Read the guide
Best E2B alternatives in 2026, ranked

If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.

Read the guide