The docs/deploy/ tree entry point. T1/T2 process+service tables, the grace-exceeds-drain rule, the honest T3 statement (paper only, no copy-paste deploy exists), the not-supported list, and the shared ports/volumes reference. Acceptance bar: copy-paste paths delegate to quickstart-docker.md; T3 states plainly what cannot be done today. Signed-off-by: Aaron D. Lee <himself@adlee.work>
106 lines
5.8 KiB
Markdown
106 lines
5.8 KiB
Markdown
# Deployment topologies — one binary, three blessed shapes
|
|
|
|
The FOB never decomposes; topology is a configuration property, not a code property. The same
|
|
binary runs in every shape below; only `RUTSTER_*` env and what is deployed alongside it
|
|
differ. Ratified (Proposed) in [ADR-0011](../adr/0011-deployment-topology.md).
|
|
|
|
## Decision guide
|
|
|
|
| You are… | Use | Doc |
|
|
|---|---|---|
|
|
| Trying rutster for the first time; one box, one domain, a public IP | **T1 — Solo** | [quickstart-docker.md](quickstart-docker.md) |
|
|
| Running production and want independently upgradable parts | **T2 — Modular** (the reference deployment) | [quickstart-docker.md](quickstart-docker.md#t2--modular-compose-stack) |
|
|
| Behind CGNAT / no public IP (homelab) | T1/T2 + tunnel (dev, **PSTN-only**) or VPS forwarder (production) | [homelab.md](homelab.md) |
|
|
| Already running nginx / HAProxy / Traefik | T2 minus the `caddy` service | [reverse-proxies.md](reverse-proxies.md) |
|
|
| On EC2 and want ALB + ACM to terminate TLS | T2/T3 behind an ALB | [aws.md](aws.md) |
|
|
| Outgrowing one node | **T3 — Fleet** — **paper only today** | [ADR-0011](../adr/0011-deployment-topology.md) |
|
|
|
|
## T1 — Solo (all-in-one container)
|
|
|
|
One image, `rutster-allinone`, with s6-overlay supervising four processes:
|
|
|
|
| Process | Role | Binding |
|
|
|---|---|---|
|
|
| `caddy` | Edge: ACME issuance/renewal, TLS termination, WS proxy | `:443` / `:80` public |
|
|
| `rutster` (the FOB) | Engine — signaling, trunk webhook, media, reflexes | `127.0.0.1:8080` behind Caddy; **media UDP direct** |
|
|
| `rutster-brain-realtime` | Brain | `127.0.0.1:8082` loopback tap |
|
|
| `valkey-server` | Event stream (`EventSink`); ledger/directory later | `127.0.0.1:6379` |
|
|
|
|
Two volumes, **both non-negotiable**:
|
|
|
|
- `/data` — Caddy's cert/ACME state. Losing it can lock your domain out of Let's Encrypt for
|
|
up to a week (duplicate-certificate limit) — a **total inbound outage**. See
|
|
[certificates.md](certificates.md).
|
|
- `/var/lib/valkey` — stream/state persistence.
|
|
|
|
WebRTC media (UDP) goes **direct to the FOB, never through Caddy**: run with host networking,
|
|
or publish `RUTSTER_MEDIA_PORT_RANGE` and set `RUTSTER_MEDIA_ADVERTISED_IP` to your public IP
|
|
(NAT 1:1 model — no STUN).
|
|
|
|
Copy-paste path from zero to first call: [quickstart-docker.md](quickstart-docker.md).
|
|
|
|
## T2 — Modular (compose stack) — the reference deployment
|
|
|
|
Four services in `deploy/compose.yaml`:
|
|
|
|
| Service | Image | Notes |
|
|
|---|---|---|
|
|
| `caddy` | `rutster-edge` | Same custom Caddy build + same Caddyfile as T1 |
|
|
| `engine` | `rutster-engine` | The FOB; plaintext `:8080` on the compose network; media UDP published |
|
|
| `brain` | `rutster-brain` | `network_mode: "service:engine"` — shares the engine's netns so the loopback-only tap posture survives unchanged |
|
|
| `valkey` | `valkey/valkey` (upstream) | Own service; network-near |
|
|
|
|
Operational contract:
|
|
|
|
- `stop_grace_period: 660s` paired with `RUTSTER_DRAIN_DEADLINE_SECS=600` — grace **must**
|
|
exceed drain, or the orchestrator kills calls the engine was gracefully draining.
|
|
- Bring-your-own-proxy: disable the `caddy` service; the engine keeps its plaintext `:8080`
|
|
mode. Tuned-timeout configs: [reverse-proxies.md](reverse-proxies.md).
|
|
- Upgrades: `docker compose pull && docker compose up -d` — the drain window lets in-flight
|
|
calls finish (calls are non-migratable; a killed engine kills its calls, honestly stated).
|
|
|
|
Copy-paste path from zero to first call:
|
|
[quickstart-docker.md](quickstart-docker.md#t2--modular-compose-stack).
|
|
|
|
## T3 — Fleet (paper only — no shipped artifacts in this epoch)
|
|
|
|
**Honest statement: you cannot deploy T3 today by copy-paste; there are no fleet artifacts
|
|
yet.** The design is ratified on paper in
|
|
[ADR-0011](../adr/0011-deployment-topology.md) so single-node deployments grow toward it, not
|
|
away from it:
|
|
|
|
- N symmetric nodes (the T1 process set minus valkey) + a shared green zone: one Valkey
|
|
(presence + directory + spend ledger), object storage.
|
|
- Wildcard DNS `*.pbx.domain`; per-node certs — the traps live in
|
|
[certificates.md](certificates.md).
|
|
- Answer-time placement via Valkey presence; node-addressed `wss://node-K.pbx.domain/...`
|
|
Stream URLs; no router tier.
|
|
- Per-node admission cap = the benchmark number (`RUTSTER_MAX_SESSIONS`, default 64, is a
|
|
placeholder until then).
|
|
- Scale-in is drain-then-terminate only. No spot instances for the engine tier.
|
|
|
|
## What is deliberately not supported
|
|
|
|
- **Splitting the FOB into services** — permanent rejection, not a roadmap item.
|
|
- **Serverless** — non-migratable multi-hour calls, per-session UDP sockets, in-process media
|
|
state. Categorically unfit.
|
|
- **Tunnels in production** — plaintext audio at the tunnel vendor's edge + documented
|
|
mid-call WS kills. Dev/demo only; the whole story is in [homelab.md](homelab.md).
|
|
- **NLB TLS listeners** — see [aws.md](aws.md) for the 350-second reason.
|
|
- **k8s manifests/Helm** — compose-first. If you run k8s anyway: set
|
|
`terminationGracePeriodSeconds` above `RUTSTER_DRAIN_DEADLINE_SECS`, same grace-exceeds-drain
|
|
rule as compose.
|
|
|
|
## Ports & volumes reference (all shapes)
|
|
|
|
| Surface | Where | Notes |
|
|
|---|---|---|
|
|
| `:80` / `:443` TCP | Caddy, public | ACME HTTP-01 + TLS + all HTTPS/WSS traffic |
|
|
| `:8080` TCP | FOB, behind the edge | Signaling + `/v1/trunk/webhook` + `/twilio/media-stream` WS — one listener (`RUTSTER_HTTP_BIND`) |
|
|
| `RUTSTER_MEDIA_PORT_RANGE` UDP | FOB, **public, direct** | WebRTC media; never proxied |
|
|
| `:8082` TCP | Brain, loopback only | The tap; non-loopback rejected until step 6 |
|
|
| `:6379` TCP | Valkey, private | Loopback (T1) / compose network (T2) / green zone (T3) |
|
|
| `:9090` TCP | FOB `/metrics`, internal | `RUTSTER_METRICS_BIND`; never routed through Caddy |
|
|
| `/data` volume | Caddy | Cert/ACME state — loss class: total inbound outage |
|
|
| `/var/lib/valkey` volume | Valkey | Event stream persistence |
|