Reverb realtime
Reverb is Laravel's first-party WebSocket server, written in PHP. It speaks the Pusher protocol to Laravel Echo — the server is ours, not pusher.com. It runs in its own container from the same app image (php artisan reverb:start), not Node, not in-process.
The container
Reverb runs as a self-hosted compose/k8s sidecar next to app (VPS or your own k8s). Same image, distinct container, distinct port. Dev compose exposes it on :8081; the app stays on :8090. One long-running CLI process — PHP max_execution_time on CLI must be -1 — and a supervisor keeps it alive. Reverb is not a Magic Containers / Cloud Run / Workers sidecar in our recipes (ADR 0009): those platforms scale HTTP and idle sleep, which is the wrong host for a long-lived daemon.
php artisan reverb:start
# default bind 0.0.0.0:8080; dev compose overrides to 8081
On a CaaS HTTP-only deploy (Magic Containers, Cloud Run, Cloudflare Containers/Workers), pick one:
- Run Reverb on a self-hosted host (VPS / k8s) and point
REVERB_HOST/REVERB_PORTat it. - Set
BROADCAST_CONNECTION=pusherand use Pusher Cloud (clone option, see Laravel broadcasting docs). - Set
BROADCAST_CONNECTION=log(broadcasts off).
Fan-out: in-memory by default
Default fan-out is in-memory — one Reverb process, no Redis, no database. Laravel has no database persister for Reverb scale-out. Horizontal replicas need Redis pub/sub, enabled only when the operator sets a Redis URL and REVERB_SCALING_ENABLED=true (typical when replica count > 1).
REDIS_URL=redis://…
REVERB_SCALING_ENABLED=true
Leave those empty and omit the --profile redis compose profile to keep the in-memory server. The broadcast queue may stay on QUEUE_CONNECTION=database; pointing queues at Redis is a separate, optional decision.
Client libraries
Echo (laravel-echo + pusher-js) and the PHP broadcaster (pusher/pusher-php-server) are the Pusher protocol clients Reverb requires. They point at REVERB_HOST / REVERB_PORT, not at Pusher Cloud. Pusher Cloud (hosted at pusher.com) is not in this template — do not wire PUSHER_APP_CLUSTER or the hosted endpoints.
flowchart LR
Browser[Browser — Echo / pusher-js] -->|wss| CDN[CDN WebSockets — Bunny, optional]
CDN --> Reverb[Reverb container — PHP]
Browser -.without CDN.-> Reverb
Reverb -->|/apps HTTP publish| App[App — broadcast events]
App -->|queued| Worker[Queue worker]
Reverb -.REVERB_SCALING_ENABLED.-> Redis[(Redis — fan-out)]
Redis -.-> Reverb2[Reverb replica]
CDN WebSockets
Bunny CDN WebSockets are a pull-zone feature, off until enabled. Default cap is 500 concurrent connections per zone. For production scale, enable WebSockets on the pull zone in front of Reverb and expose Reverb's port through a second endpoint. Without that toggle, Echo's wss:// through the CDN hostname will not upgrade.
Broadcasts are queued — a queue worker must run, or events never leave the app. SIGTERM on scale-down or rolling deploy drops in-memory sockets; clients reconnect when the process returns.