Skip to content

Self-hosting

Run it locally

Serve a checkout of Pooled from your own machine, with an optional local signaling server.

Pooled is a static site: plain HTML and JavaScript modules, with no build step. Any static server works, as long as it applies two rewrites.

git clone https://github.com/Nehanth/pooled && cd pooled
npx -y serve -l 8080 .

Open http://localhost:8080/room. npm run serve runs the same command.

serve reads serve.json for the rewrites:

serve.json
{
"rewrites": [
{ "source": "/room", "destination": "/p2p.html" },
{ "source": "/r/:code", "destination": "/p2p.html" }
]
}

Production uses the same two rewrites in vercel.json, so keep the two files in step.

If you use nginx, Caddy or something else, map /room and /r/<code> to /p2p.html. For example, with Caddy:

Caddyfile
:8080 {
root * /path/to/pooled
rewrite /room /p2p.html
rewrite /r/* /p2p.html
file_server
}

A local checkout uses the public PeerJS server by default, the same as pooled.run. That works from localhost. To keep everything on your machine, run a local PeerServer and point the room at it:

npx peer --port 9000 --path /
http://localhost:8080/room?signal=127.0.0.1:9000

On an http page a bare host:port uses ws://. ?signal= opens the room in dev mode; add &dev=0 if you don’t want that. Every device must use the same server. See Signaling servers.

The page still loads PeerJS from cdn.jsdelivr.net, model files from Hugging Face (unless a device in the room has them), and STUN from Google’s public servers. Browser caches are per site, so weights cached on pooled.run download again on your origin (and localhost and 127.0.0.1 are different sites).

WebGPU needs a secure context. http://localhost counts; http://192.168.1.20:8080 on another device does not, so that device gets no GPU and can only ask.

Serve the checkout over https with a certificate the devices trust, for example Caddy with a real domain name. Then run the signaling server behind TLS too: an https page can only open wss://. See Put it behind TLS.

Previews run on a second site so a runaway app can’t freeze the room. Locally that is the other loopback name (localhost ↔ 127.0.0.1), with nothing to set up. On a LAN address there is no second site, so previews run in the room’s tab and run_js is off.