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.

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.

ProtocolExternal port rangeInternal port rangeDestination
UDP10000–1010010000–10100Your 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 change RTC_MIN_PORT / RTC_MAX_PORT in .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
VariableRequiredDescription
ANNOUNCED_IPYesYour public hostname/IP for WebRTC ICE candidates
LOCAL_ANNOUNCED_IPNoLAN IP, advertised as a 2nd ICE candidate for same-network clients
RTC_MIN_PORTYesStart of UDP range — must match router rule. Default 10000
RTC_MAX_PORTYesEnd of UDP range. Default 10100
HTTP_PORTYesHost port for the web server. Default 3000
WEAVE_TUNNEL_TOKENTunnel onlyCloudflare 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.
  1. Create a free Cloudflare account and add your domain
  2. Zero Trust dashboard → Networks → Tunnels → Create a tunnel → Cloudflared connector
  3. Name it, save, and copy the tunnel token into .env as WEAVE_TUNNEL_TOKEN
  4. 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

SymptomLikely causeFix
No audio in voice channelsUDP ports not forwardedConfirm router forwards UDP 10000–10100 to the server's LAN IP
Voice connects but drops / ICE failureANNOUNCED_IP unreachableVerify it resolves to your public IP and the UDP range is open
Page won't load at allTCP 3000 blocked, or tunnel misconfiguredCheck TCP 3000 reachability, or docker logs weave-cloudflared
Container exits immediatelyConfig error or port conflictdocker logs weave-voice — read the error
"port already in use"Another process on 3000 / RTC rangeChange HTTP_PORT, or stop the conflicting service
"Name already taken" on rejoinReconnect race conditionWait a few seconds and rejoin — stale sessions clear automatically
First login fails after restartData volume may be missingCheck docker volume ls for weave_data

Stuck? Get in touch or open an issue on GitHub.