Self-host Weave
Everything you need to run your own instance — on a Raspberry Pi, a spare PC, or any Docker-capable server. Roughly 30–60 minutes end to end.
Quick-start checklist
Use this as your progress tracker. Each item maps to a step below.
- Git installed
- Docker installed and running
- Router UDP ports 10000–10100 forwarded to this server's LAN IP
- Public hostname or IP decided (
ANNOUNCED_IP) - Repository cloned
.envfile created and filled inlogs/directory created- (Optional) Cloudflare Tunnel token obtained and set
docker compose up -dran successfully- First admin user created via bootstrap script
- Logged in and confirmed working
Step 1 — Install prerequisites
Git
Windows: Download and run the installer from git-scm.com/downloads. Accept all defaults.
Linux / Raspberry Pi:
sudo apt update && sudo apt install -y git
Verify: git --version
Docker
Windows / Mac: Install Docker Desktop. Start it and confirm the whale icon appears in your system tray before proceeding.
Linux / Raspberry Pi: Use the official install script. Do not use apt install docker.io — that package is often severely outdated.
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
Then install the Compose plugin:
sudo apt install -y docker-compose-plugin
Verify both are working:
docker --version
docker compose version
Weave has been tested on a Raspberry Pi 5 running Raspberry Pi OS (64-bit). Docker Engine 24+ and Compose v2+ are required.
Step 2 — Forward router ports (UDP)
mediasoup handles all WebRTC media server-side. When users speak or share audio, their browser sends UDP packets directly to your server — this does not go through any proxy. Without port forwarding, remote users won't hear anyone.
| Protocol | External port range | Internal port range | Destination |
|---|---|---|---|
| UDP | 10000–10100 | 10000–10100 | Your server's LAN IP |
Find your server's LAN IP — Linux / Raspberry Pi: hostname -I | awk '{print $1}' — Windows: ipconfig, look for IPv4 Address.
Every router admin panel differs — search portforward.com for your router model and follow the UDP port-range guide.
If you changeRTC_MIN_PORT/RTC_MAX_PORTin.env, update the router rule to match.
Step 3 — Set up a public hostname
mediasoup needs to tell clients where to send WebRTC packets — configured as ANNOUNCED_IP in .env. It must be reachable from the public internet.
Option A — Static public IP
ANNOUNCED_IP=89.x.x.x
Option B — Dynamic IP (most home connections)
Use a free dynamic DNS service like DuckDNS: create an account, create a subdomain, and set up a cron job / scheduled task to keep it pointed at your current IP.
ANNOUNCED_IP=my-weave.duckdns.org
Step 4 — Clone the repository
git clone https://github.com/CodeineZA/Weave.git
cd Weave
Step 5 — Configure .env
Copy the template:
# Linux / Raspberry Pi / Mac
cp .env.example .env
# Windows (PowerShell)
Copy-Item .env.example .env
| Variable | Required | Description |
|---|---|---|
ANNOUNCED_IP | Yes | Your public hostname/IP for WebRTC ICE candidates |
LOCAL_ANNOUNCED_IP | No | LAN IP, advertised as a 2nd ICE candidate for same-network clients |
RTC_MIN_PORT | Yes | Start of UDP range — must match router rule. Default 10000 |
RTC_MAX_PORT | Yes | End of UDP range. Default 10100 |
HTTP_PORT | Yes | Host port for the web server. Default 3000 |
WEAVE_TUNNEL_TOKEN | Tunnel only | Cloudflare tunnel connector token |
Create the logs directory:
mkdir -p logs
Step 6 — Optional: Cloudflare Tunnel
A Cloudflare Tunnel lets your server reach the internet without opening any inbound TCP port on your router — Cloudflare handles HTTPS/WSS termination.
Even with the tunnel, WebRTC UDP ports (10000–10100) still need router forwarding — the tunnel only carries HTTP/WebSocket signalling; media bypasses it entirely.
- Create a free Cloudflare account and add your domain
- Zero Trust dashboard → Networks → Tunnels → Create a tunnel → Cloudflared connector
- Name it, save, and copy the tunnel token into
.envasWEAVE_TUNNEL_TOKEN - Add a Public Hostname route: Service =
http://weave-voice:3000
Start with the tunnel: docker compose -f docker-compose.yml -f docker-compose.tunnel.yml up -d
Start without it: docker compose up -d — Weave is then reachable at http://<server-ip>:3000.
Step 7 — Start Weave
First startup builds the Docker image from source (2–5 minutes).
docker compose up -d
docker ps
docker logs weave-voice --tail 50 -f # wait for SFU_READY
Access it at http://localhost:3000, your LAN IP, or your configured public hostname.
Step 8 — Create the first admin user
Registration is invite-only, and invite codes are generated by logged-in users — so the very first user has to be bootstrapped directly.
8a — Create bootstrap_admin.mjs in the repo directory:
import { createUser } from '/app/db.js';
import Database from 'better-sqlite3';
const r = createUser({
full_name: 'YourFullName',
nick_name: 'YourNick',
password: 'YourPassword',
profile_picture: '🎯',
secret_phrase: 'A phrase only you know'
});
if (!r.ok) { console.error('Failed:', r.error); process.exit(1); }
const raw = new Database('/app/data/weave.db');
raw.prepare('UPDATE weave_users SET is_admin=1 WHERE id=?').run(r.id);
raw.close();
console.log('Admin user created. ID:', r.id);
8b — Run it inside the container:
docker cp bootstrap_admin.mjs weave-voice:/tmp/bootstrap_admin.mjs
docker exec -it weave-voice node /tmp/bootstrap_admin.mjs
rm bootstrap_admin.mjs
8c — Log in with your full_name and password, then use Invite a Friend to generate invite codes for everyone else.
Updating Weave
git pull
docker compose build
docker compose down
docker compose up -d
User data, messages and uploads live in a named Docker volume (weave_data) and persist across rebuilds. docker compose down does not delete volumes — only docker compose down -v does.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No audio in voice channels | UDP ports not forwarded | Confirm router forwards UDP 10000–10100 to the server's LAN IP |
| Voice connects but drops / ICE failure | ANNOUNCED_IP unreachable | Verify it resolves to your public IP and the UDP range is open |
| Page won't load at all | TCP 3000 blocked, or tunnel misconfigured | Check TCP 3000 reachability, or docker logs weave-cloudflared |
| Container exits immediately | Config error or port conflict | docker logs weave-voice — read the error |
| "port already in use" | Another process on 3000 / RTC range | Change HTTP_PORT, or stop the conflicting service |
| "Name already taken" on rejoin | Reconnect race condition | Wait a few seconds and rejoin — stale sessions clear automatically |
| First login fails after restart | Data volume may be missing | Check docker volume ls for weave_data |
Stuck? Get in touch or open an issue on GitHub.