Architecture & operations reference¶
Look-up material for a running deployment. The system diagram lives in the top-level README; per-service internals live in each service's doc (linked from there).
Services & ports¶
Public (exposed on master):
| Service | Port | Purpose |
|---|---|---|
| Traefik | 80 / 443 | Public HTTPS front door (k3s ingress) — Let's Encrypt TLS; routes platform.<domain> → Platform, app.<domain> → Web UI |
| Platform | 8100 | API entrypoint — every SDK call goes here, downstream services proxied through it |
| Web UI | 3000 | Dashboard (served via Traefik once HTTPS is set up) |
| kuberay-service | 8800 | Worker join-node.sh calls /v1/nodes/validate pre-tunnel |
| K3s API | 6443 | Worker k3s-agent |
| WireGuard | 51820/udp | Encrypted tunnel endpoint |
Internal-only (not publicly exposed):
| Service | Port | Purpose |
|---|---|---|
| Job Gateway | 8200 | Job submission & VRAM-aware scheduling (stateless — job/endpoint state lives in CRDs) |
| Metering | 8300 | Usage tracking & billing calc |
| Credit | 8400 | Balances, mint/deduct, transfers |
| Faucet | 8500 | Email verify, onboarding, credit refill |
| Postgres | 5432 | One instance; per-service databases: platform, credits, metering, faucet, chain |
| CometBFT | 26657 | Append-only audit-chain RPC (chain_service; active when CHAIN_ENABLED=true) |
| MinIO | 9000 / 9001 | S3 API / console (reached over WG from workers) |
| Registry | 30500 | Platform image registry (NodePort, reached over WG — see Image registry TLS) |
| Ray Head | 30001 / 30002 / 30003 | Ray client / dashboard / Serve (NodePort, reached over WG) |
| VictoriaLogs / Prometheus | 9428 / 9090 | Logs & metrics behind the admin Diagnostics console |
Networking¶
The master needs these ports reachable publicly:
| Port | Used by | Why |
|---|---|---|
6443/tcp |
Worker K3s agent | K3s API |
8800/tcp |
Worker join | /v1/nodes/validate is called before the WG tunnel exists, so it can't go through WG |
51820/udp |
WireGuard | Tunnel endpoint |
3000/tcp |
Users | WebUI |
8100/tcp |
Users / SDK | Platform API (every SDK call goes here; job-gateway, metering, credit, faucet, and MinIO are proxied through it) |
GPU workers need no inbound ports.
deploy.sh opens the OS firewall (ufw) on every box automatically and, at the end, prints the exact host:port list to allow in your cloud firewall / security-group. In a multi-VM deploy, a public service placed on an extra machine (e.g. the dashboard) is reachable on that machine's IP — the summary tells you which.
deploy.sh brings WireGuard up on the master; join-node.sh brings it up on each worker, using the master's pubkey + the VPN IP returned by /v1/nodes/validate. K3s pod-to-pod traffic (flannel/VXLAN) is pinned to wg0 via flannel-iface.
Expected times¶
deploy.sh takes ~10–15 minutes on a fresh master:
| Step | Time |
|---|---|
| K3s install + start | ~1 min |
| Docker services build + start | ~3 min |
| WireGuard setup | ~1 min |
| Helm + KubeRay + device plugins | ~2 min |
Ray head image build (gridweave-ray-head:latest) |
~3–5 min |
GPU worker join takes ~30–40 minutes per node on a fresh install (disk-bound):
| Step | NVIDIA | AMD |
|---|---|---|
| Prereqs, WG tunnel, K3s agent | ~2 min | ~2 min |
Worker image build (gridweave-worker:latest) |
~10 min (torch CUDA + HF libs, ~8 GB export) | ~15–20 min (torch ROCm, slower disk) |
vLLM image build (gridweave-ray-vllm-{nvidia,amd}:latest) |
~15 min (~10 min pip install + ~5 min layer export) | ~10–15 min (experimental, may fail) |
| Register with cluster | ~30s | ~30s |
Re-running join-node.sh is idempotent — it skips any image already present in containerd (REBUILD=1 to force). A re-join preserves buildkit cache, so re-builds are fast (~3–5 min). AMD vLLM support is experimental (upstream vLLM ROCm is newer than CUDA).
Storage¶
GPU worker images are large: ~8 GB for gridweave-worker:latest (CUDA/ROCm + torch + HF libs), plus ~10 GB for gridweave-ray-vllm-{nvidia,amd}:latest. The BuildKit cache persists across runs (capped at 50 GB by BuildKit's own GC) so re-joins after image eviction rehydrate layers locally instead of re-downloading ~4 GB of torch. Set CLEAN_BUILDKIT=1 on a join to wipe the cache. If your GPU node has a secondary disk, mount it at /mnt/data before running join-node.sh:
sudo mkdir -p /mnt/data
sudo mount /dev/sdb1 /mnt/data
echo '/dev/sdb1 /mnt/data ext4 defaults 0 0' | sudo tee -a /etc/fstab
When /mnt/data is present and >100 GB, join-node.sh:
- Adds
data-dir: /mnt/data/k3sto the K3s agent config (final image storage lives there). - Uses
/mnt/data/buildkitfor the BuildKit cache (persistent, GC-capped at 50 GB). - Creates the model cache at
/mnt/data/model-cacheand symlinks/data/model-cacheto it (endpoint pods hostPath-mount/data/model-cache; without a data disk it's created directly). No manual setup needed — the script handles both layouts.
The K3s data-dir choice is a one-time decision — if K3s is already installed at /var/lib/rancher/k3s, it stays there.
Bring your own S3¶
By default Gridweave runs its own MinIO for object storage. To use an external S3 instead (AWS S3, Cloudflare R2, …), set S3_ENDPOINT, S3_ACCESS_KEY, and S3_SECRET_KEY in deploy/.env before deploying — the system uses yours and skips MinIO.
Image registry TLS¶
Custom-endpoint images (spec.image) live in the platform's own container
registry (see docs/image_registry). It's exposed
on NodePort 30500 and reached by nodes over the WireGuard mesh at
MASTER_VPN_IP:30500 — no public exposure needed for pulls. Users push to the
same address over the mesh, or to a public registry.<domain> in prod.
If you put
registry.<domain>behind Cloudflare, the DNS record MUST be DNS-only (grey cloud), not proxied (orange cloud). Docker pushes each image layer as one large monolithicPATCHbody; Cloudflare's proxied path caps request bodies (~100 MB) and GPU image layers routinely exceed that, so a proxied record fails the push mid-layer with a413/reset. Terminate TLS at the master (Caddy / Let's Encrypt / Traefik) on a grey-cloud record instead. This does not apply to the mesh push path (plain HTTP over WireGuard), which is already encrypted.
Tuning knobs in .env (all optional): REGISTRY_QUOTA_GB (per-user cap,
default 20), REGISTRY_GC_INTERVAL_HOURS (default weekly), REGISTRY_NODEPORT,
REGISTRY_S3_BUCKET. The quota is also editable live in the web UI
(Configuration → Image Registry) when it isn't pinned in .env.
Files¶
core/
deploy/
deploy.sh # Master setup + multi-VM fan-out (wizard) — recreates a clean slate every run
update.sh # Keep-everything re-run: pull new code + rebuild (no data loss)
join-node.sh # GPU node join (single key, auto-everything)
setup-https.sh # Traefik + Let's Encrypt routes for app/platform domains
.env.example # Config template
docker/
deploy/docker-compose.yml # All services
kuberay_service/ # KubeRay API
ray-worker/Dockerfile # Worker image (built on each node)
kuberay_service/
routers/nodes.py # Join key + validate + register endpoints
Developer shortcut — one-container rebuild¶
For Python-only changes to job_gateway/ or platform_service/, skip the full
update: copy the files and rebuild the one container.
scp job_gateway/*.py root@<master>:/tmp/core/job_gateway/
ssh root@<master> 'cd /tmp/core/docker/deploy && docker compose --env-file /tmp/core/deploy/.env up -d --build --no-deps job-gateway'
Removing Gridweave¶
Only for permanently removing Gridweave from a machine (decommissioning,
repurposing). For retrying a failed join, just re-run join-node.sh — it's
idempotent.
Worker — use the script. leave-node.sh deregisters the node from the
master (WireGuard peer + Ray worker group), stops the k3s agent, and brings
wg0 down. By default it keeps the built images so a re-join is fast;
--uninstall also removes k3s, containerd, and all images:
sudo /opt/gridweave/deploy/leave-node.sh --uninstall
# BuildKit + nerdctl live outside k3s, so the uninstaller doesn't touch them.
# Scrub them too when decommissioning for good:
sudo rm -rf /var/lib/buildkit /mnt/data/buildkit /etc/buildkit /run/buildkit
sudo rm -rf /usr/local/bin/nerdctl /usr/local/bin/buildkitd /usr/local/bin/buildctl
Master. There is no master uninstall script — deploy.sh clean-slates the
machine on every run, so re-deploying never needs one. To remove Gridweave from
a master for good:
sudo helm uninstall kuberay-operator -n gridweave
sudo kubectl delete namespace gridweave
sudo /usr/local/bin/k3s-uninstall.sh