Co-authored-by: Aaron D. Lee <himself@adlee.work> Co-committed-by: Aaron D. Lee <himself@adlee.work>
5.1 KiB
Docker quickstart — zero to first call
Two shapes, one binary (topologies.md). T1 is one docker run; T2 is the
reference compose up. Both end the same way: a browser call at your domain, and (with Twilio
credentials) a real phone call.
Artifact names note: images are published to the
git.adlee.work/alee/registry namespace by the release CI (plan B / slice F settled this). If your Gitea registry uses a different owner segment, substitute accordingly — checkdeploy/compose.yamlin your checkout, which is authoritative.
Prerequisites (both shapes)
- A Linux host with Docker, a public IPv4, and inbound
80/tcp,443/tcp, and your chosen media UDP range open. (No public IP? → homelab.md.) - A domain with an A record pointing at that host, e.g.
pbx.example.com. Certificates are issued automatically via ACME — no self-signed path exists, the CPaaS refuses it (certificates.md). - For the phone call: a Twilio account + a Voice-capable number (trial works).
T1 — Solo (all-in-one)
docker run -d --name rutster --restart unless-stopped \
--network host \
-v rutster-caddy-data:/data \
-v rutster-valkey:/var/lib/valkey \
-e RUTSTER_DOMAIN=pbx.example.com \
-e RUTSTER_ACME_EMAIL=you@example.com \
-e RUTSTER_MEDIA_ADVERTISED_IP=203.0.113.7 \
-e RUTSTER_MEDIA_PORT_RANGE=49152-49407 \
-e RUTSTER_TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
-e RUTSTER_TWILIO_AUTH_TOKEN=your_auth_token \
-e RUTSTER_TWILIO_MEDIA_BIND=0.0.0.0:8081 \
-e RUTSTER_TWILIO_WEBHOOK_BASE=https://pbx.example.com \
git.adlee.work/alee/rutster-allinone:latest
Notes:
--network hostis the recommended mode: WebRTC media UDP goes direct to the engine, never through Caddy. If you can't use host networking, publish-p 80:80 -p 443:443 -p 49152-49407:49152-49407/udpinstead and keepRUTSTER_MEDIA_ADVERTISED_IP= the host's public IP (NAT 1:1, no STUN).- The two volumes are non-negotiable.
/datain particular: recreating the container without it re-requests certificates and can hit Let's Encrypt's duplicate-certificate limit — a week-long total inbound outage (certificates.md). - The four
RUTSTER_TWILIO_*vars are all-or-none — the engine refuses partial Twilio config at startup. Omit all four to run WebRTC-only. RUTSTER_DOMAIN/RUTSTER_ACME_EMAILfeed the bundled Caddyfile. Domains that can't do HTTP-01 (no port 80, wildcard needs) use DNS-01 — certificates.md.
Verify it's up:
curl -fsS https://pbx.example.com/healthz && echo OK # liveness
curl -fsS https://pbx.example.com/readyz && echo READY # can accept a new call
First call (browser, WebRTC)
Open https://pbx.example.com/, click Start call, grant the microphone. You're on a call
with the engine.
First call (phone, PSTN)
- Twilio Console → Phone Numbers → your number → A call comes in → Webhook,
POST https://pbx.example.com/v1/trunk/webhook. - Dial your Twilio number from any phone. Twilio hits the webhook, the engine answers with
TwiML pointing Twilio's Media Streams at
wss://pbx.example.com/twilio/media-stream, and the call's audio enters the same reflex loop a WebRTC call uses.
T2 — Modular (compose stack)
git clone https://git.adlee.work/alee/rutster.git
cd rutster/deploy
cp .env.example .env
$EDITOR .env # set DOMAIN, ACME email, RUTSTER_MEDIA_ADVERTISED_IP, RUTSTER_TWILIO_*
docker compose up -d
What comes up: caddy (edge, rutster-edge), engine (rutster-engine), brain
(rutster-brain, sharing the engine's network namespace), valkey (upstream image). The
same two first-call paths as T1 apply verbatim.
Operational notes:
.env.exampleshipsRUTSTER_DRAIN_DEADLINE_SECS=600and the stack setsstop_grace_period: 660s— grace exceeds drain, sodocker compose downlets in-flight calls finish (up to 10 minutes) instead of killing them.- Upgrade:
docker compose pull && docker compose up -d. - Bring your own proxy instead of Caddy: reverse-proxies.md.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Twilio error 31910 on calls | Your cert isn't publicly trusted or expired — check /data volume survived, see certificates.md (twilio.com/docs/api/errors/31910) |
| Webhook signature validation fails | The edge isn't forwarding X-Forwarded-Proto/Host honestly, or RUTSTER_TRUSTED_PROXIES doesn't include your proxy — the engine ignores forwarded headers from unlisted sources |
| Browser call connects, no audio | Media UDP blocked or RUTSTER_MEDIA_ADVERTISED_IP wrong — the SDP advertises that IP; callers send UDP straight to it |
curl /readyz returns 503 |
Node is draining or at the admission cap (RUTSTER_MAX_SESSIONS) — liveness (/healthz) stays 200 |
| Let's Encrypt rate-limit errors in Caddy logs | You recreated the container without the /data volume — certificates.md |