# Docker quickstart — zero to first call Two shapes, one binary ([topologies.md](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 — check `deploy/compose.yaml` in your > checkout, which is authoritative. ## Prerequisites (both shapes) 1. 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](homelab.md).) 2. 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](certificates.md)). 3. For the phone call: a [Twilio](https://www.twilio.com/) account + a Voice-capable number (trial works). ## T1 — Solo (all-in-one) ```bash 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 host` is 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/udp` instead and keep `RUTSTER_MEDIA_ADVERTISED_IP` = the host's public IP (NAT 1:1, no STUN). - The two volumes are **non-negotiable**. `/data` in 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](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_EMAIL` feed the bundled Caddyfile. Domains that can't do HTTP-01 (no port 80, wildcard needs) use DNS-01 — [certificates.md](certificates.md). Verify it's up: ```bash 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) 1. Twilio Console → Phone Numbers → your number → **A call comes in** → Webhook, `POST https://pbx.example.com/v1/trunk/webhook`. 2. 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) ```bash 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.example` ships `RUTSTER_DRAIN_DEADLINE_SECS=600` and the stack sets `stop_grace_period: 660s` — grace exceeds drain, so `docker compose down` lets 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](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](certificates.md) ([twilio.com/docs/api/errors/31910](https://www.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](certificates.md) |