Hello, I made this project for fun.
The server is TypeScript on Node/Bun. The client is Go. Operators talk to the server through a web panel or the Electron desktop app, and agents connect over encrypted WebSockets.
Docker is the easiest way to run it.
- Quick Start (Docker)
- No Docker (.bat / .sh)
- Production Package Scripts
- WebRTC Streaming
- Docker Notes (TLS, reverse proxy, cache)
Pick your OS below. Each section is self-contained: install Docker, get the project, start it.
Windows and macOS use
docker-compose.windows.yml. Linux uses the defaultdocker-compose.yml(host networking).
After the first start, open https://localhost:5173. Default login is admin / admin unless you set OVERLORD_USER / OVERLORD_PASS. First startup writes generated secrets to data/save.json (inside the container: /app/data/save.json) — keep that file private and back it up.
Step-by-step: Windows
1. Install Docker Desktop
Either from the website:
Or with winget:
winget install -e --id Docker.DockerDesktopStart Docker Desktop once, then verify:
docker --version
docker compose version2. Get the project
git clone https://github.com/vxaboveground/Overlord.git
cd Overlord3. Start it
docker compose -f docker-compose.windows.yml up -d4. Open the panel
https://localhost:5173
5. Update later
docker compose -f docker-compose.windows.yml down
docker compose -f docker-compose.windows.yml pull
docker compose -f docker-compose.windows.yml up -d6. Stop
docker compose -f docker-compose.windows.yml downStep-by-step: Linux (Debian / Ubuntu / Kali)
1. Install Docker
Official docs: https://docs.docker.com/engine/install/debian/
Set up Docker's apt repository:
# Add Docker's official GPG key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt updateOn a derivative distro (e.g. Kali), replace the codename expansion with the matching Debian codename, e.g. bookworm.
Install Docker:
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginMake sure the daemon is running:
sudo systemctl status docker2. Grab the compose file
Make a folder for it, drop in the file, and you're done:
mkdir overlord && cd overlord
wget https://raw.githubusercontent.com/vxaboveground/Overlord/refs/heads/main/docker-compose.ymlNo wget? Use curl:
mkdir overlord && cd overlord
curl -O https://raw.githubusercontent.com/vxaboveground/Overlord/refs/heads/main/docker-compose.yml3. Start it
docker compose up -dThe image is pulled automatically from ghcr.io/vxaboveground/overlord:latest on first run.
4. Open the panel
https://localhost:5173
or
https://IP:5173
5. Update later
From the same folder:
docker compose down
docker compose pull
docker compose up -d6. Stop
docker compose downStep-by-step: macOS
1. Install Docker Desktop
Either from the website:
Or with Homebrew:
brew install --cask dockerStart Docker Desktop once, then verify:
docker --version
docker compose version2. Get the project
git clone https://github.com/vxaboveground/Overlord.git
cd Overlord3. Start it
macOS uses the same compose file as Windows:
docker compose -f docker-compose.windows.yml up -d4. Open the panel
https://localhost:5173
5. Update later
docker compose -f docker-compose.windows.yml down
docker compose -f docker-compose.windows.yml pull
docker compose -f docker-compose.windows.yml up -d6. Stop
docker compose -f docker-compose.windows.yml downIf you don't want Docker, use the included scripts.
Prerequisites:
- Bun in PATH
- Go 1.21+ in PATH
Development mode (starts server + client):
start-dev.batProduction mode (build + run server executable):
start-prod.batBuild client binaries (adds client builds to the build queue):
build-clients.batMake scripts executable once:
chmod +x start-dev.sh start-dev-server.sh start-dev-client.sh start-prod.sh build-prod-package.shDevelopment mode (server in background, client in foreground):
./start-dev.shOnly server, or only client:
./start-dev.sh server
./start-dev.sh clientProduction mode:
./start-prod.shBuild a production-ready package where the server can still build client binaries at runtime.
Windows:
build-prod-package.batOutput: release/
Linux / macOS:
./build-prod-package.shOutput: release/prod-package/
The remote desktop viewer has a Transport dropdown with three modes:
- Canvas (default): H.264 / JPEG / block frames over the existing WebSocket, decoded into a
<canvas>. Highest latency, works anywhere the WS does. - WebRTC P2P: browser ↔ agent direct. The server only relays SDP and ICE candidates over the existing WS — MediaMTX is not involved. Lowest latency. Fails when both sides are behind aggressive symmetric NAT.
- WebRTC Relayed: agent publishes to a MediaMTX sidecar via WHIP, browser plays via WHEP. The server proxies signaling so the existing JWT auth + per-client RBAC still apply. Lowest-effort fallback when P2P can't punch through.
WebRTC is opt-in per agent build. In the builder UI, tick the WebRTC checkbox before clicking Build. Without it, the Pion stack (~6 MB of Go modules) is not compiled in — any WebRTC start attempt from the operator returns webrtc support not compiled in and the viewer falls back to Canvas. The Canvas path always works regardless of the build setting.
The build tag (overlord_webrtc) is also available if you build agents outside the UI:
go build -tags overlord_webrtc ./cmd/agentCompose starts an overlord-mediamtx service for Relayed mode. It needs:
- Port
8189/udpand8189/tcpreachable from operators (WebRTC ICE traffic). The Windows / macOS compose publishes these; Linux uses host networking and shares the host's interfaces directly. - No auth config — the Overlord server proxies every WHIP/WHEP request through
/api/webrtc/...and enforces the existing operator JWT + RBAC there.
If you only ever want P2P, you can comment out the mediamtx: service in your compose file — only Relayed mode depends on it.
By default MediaMTX advertises 127.0.0.1 as an ICE candidate, which is enough for operators running their browser on the same machine as Docker.
For viewers on a different machine:
-
Linux (host networking): nothing to configure — MediaMTX sees the host's eth0/wlan0 and gathers those candidates automatically.
-
Windows / macOS (bridge networking): set
OVERLORD_WEBRTC_ADDITIONAL_HOSTSto a comma-separated list including the server's reachable LAN or public IP. Either export it in your shell, or add it to a.envfile next to the compose file:OVERLORD_WEBRTC_ADDITIONAL_HOSTS=127.0.0.1,192.168.1.42Then
docker compose -f docker-compose.windows.yml up -d mediamtxto apply.
The compose file passes a minimal set of MTX_* environment variables to MediaMTX — enough for Overlord to work. To change anything else (codecs, paths, ICE servers, etc.), either:
-
Add more
MTX_*variables to themediamtxservice'senvironment:block (every option in MediaMTX's docs is supported as an env var), or -
Provide a full
mediamtx.ymlvia a bind mount:mediamtx: # ...existing config... volumes: - ./mediamtx.yml:/mediamtx.yml:ro
Make sure the file exists on the host before the container starts — Docker will otherwise auto-create an empty directory at that path and fail with "not a directory".
Notes on configs and workarounds.
docker-compose.yml ships with build.cache_from and build.cache_to pointing at .docker-cache/buildx. Local builds reuse it automatically — no extra setup.
The compose setup uses a persistent volume for runtime client builds:
- Volume:
overlord-client-build-cache - Mount:
/app/client-build-cache - Env:
OVERLORD_CLIENT_BUILD_CACHE_DIR(default/app/client-build-cache)
To use Let's Encrypt certificates in production Docker:
- Set
OVERLORD_TLS_CERTBOT_ENABLED=true - Set
OVERLORD_TLS_CERTBOT_DOMAIN=your-domain.com - Mount letsencrypt into the container read-only, e.g.
/etc/letsencrypt:/etc/letsencrypt:ro
Default cert paths:
cert: /etc/letsencrypt/live/<domain>/fullchain.pem
key: /etc/letsencrypt/live/<domain>/privkey.pem
ca: /etc/letsencrypt/live/<domain>/chain.pem
Override with:
OVERLORD_TLS_CERTBOT_LIVE_PATHOVERLORD_TLS_CERTBOT_CERT_FILEOVERLORD_TLS_CERTBOT_KEY_FILEOVERLORD_TLS_CERTBOT_CA_FILE
If your platform terminates TLS before traffic reaches Overlord (Render, Caddy, nginx, etc.), set:
OVERLORD_TLS_OFFLOAD=true
OVERLORD_HEALTHCHECK_URL=http://localhost:5173/health
OVERLORD_PUBLISH_HOST=127.0.0.1
When enabled:
- Container serves internal HTTP on
0.0.0.0:$PORT - External URL stays
https://...through your platform proxy - Health checks should use
http://localhost:$PORT/healthinside the container - Don't expose the internal container HTTP port directly to the internet
If the dashboard, audit log, or IP bans show all agents as 172.x.x.x (or some other proxy/bridge IP), something between the agent and Bun is rewriting the source IP. Two independent flags govern this:
OVERLORD_TLS_OFFLOAD— TLS terminates at the proxy; Overlord runs plain HTTP internally.OVERLORD_TRUST_PROXY— honorX-Forwarded-For/X-Real-IP/CF-Connecting-IPso dashboard/audit/IP-bans see the real client. Auto-enabled whenTLS_OFFLOAD=true.
Common shapes:
| Setup | TLS_OFFLOAD |
TRUST_PROXY |
|---|---|---|
| Domain, no proxy (Linux host networking; certbot inside Overlord; Cloudflare DNS-only) | false |
false |
| Domain, proxy does TLS (Render, nginx terminating TLS → http upstream) | true |
auto (true) |
Domain, proxy in front but Overlord still does TLS (Cloudflare orange-cloud Full Strict; nginx with proxy_pass https://) |
false |
true |
Only enable OVERLORD_TRUST_PROXY when a trusted reverse proxy is in front. If Overlord is directly exposed and you enable it, agents can spoof their source IP by sending their own X-Forwarded-For header, breaking IP bans and audit accuracy. The upstream proxy also has to be configured to inject the header (Cloudflare does by default; nginx/Caddy/Traefik need explicit directives).
If you're using docker-compose.windows.yml or docker-compose.quickstart.yml (Docker Desktop bridge networking) with no reverse proxy, Docker itself rewrites source IPs to the bridge gateway and there is no header to recover from — TRUST_PROXY cannot help. Either switch to Linux host networking or put a real reverse proxy in front.
- Keep
HOST=0.0.0.0inside the container. Limit exposure withOVERLORD_PUBLISH_HOST, not the bind host. - If your
.envsecret/password contains$, escape it as$$to avoid Docker Compose variable-expansion warnings.
