Realtime for your Laravel app. In your own account.
WebSockets with Laravel Reverb, in a container of its own in your Scaleway project. Browsers connect straight to that container: Brewless is not in between, and neither is any other company.
Realtime is new and in beta: it works, and it is measured on Scaleway, but its limits and defaults may still change. Tell us what you run into.
How it works
One line in brewless.yml, and the next deploy sets up everything Laravel broadcasting needs.
A container of its own
Reverb runs in a Serverless Container named realtime, next to your web and worker containers, in the region of the environment.
Straight to your container
Browsers connect over wss:// to that container. Brewless never carries the traffic, and there is no CDN in the path: WebSockets have nothing to cache.
Keys in your Secret Manager
The Reverb app id, key and secret are made once, in your own Secret Manager. Brewless keeps the reference, never the secret.
An address on your domain
ws. in front of your app's own host, or a hostname you pick. Brewless makes the DNS record and attaches it to the container, with its own certificate.
An event goes from your app to your Reverb container and from there to the browser. No hosted WebSocket service of another company sits in between, so what you broadcast stays with Scaleway, a European company, in the region you chose.
Switch it on
Add realtime to an environment in brewless.yml and deploy. That is all for most apps.
environments:
production:
realtime: true
Or with its settings spelled out:
environments:
staging:
realtime:
image: app
memory: 512
min: 0
timeout: 3600
host: true
| Setting | Default | What it does |
|---|---|---|
image |
app | app runs Reverb on your app's own release image; brewless runs it on a small image Brewless maintains (closed beta). |
memory |
512 or 256 | Megabytes: 512 on your app's image, 256 on the Brewless image. The processor follows the memory, as for your other containers. |
min |
0 | 0 lets the container sleep while nobody is connected; 1 keeps it on. |
timeout |
3600 | Seconds a connection may stay open, from 10 to 3600. |
host |
true | true gives ws. plus your app's host; a hostname such as realtime.example.com gives that; false keeps only the container's own scw.cloud address. |
Your web, release and worker containers get REVERB_APP_ID, REVERB_APP_KEY, REVERB_APP_SECRET (as a secret), REVERB_HOST, REVERB_PORT and REVERB_SCHEME, and BROADCAST_CONNECTION=reverb when your app sets no connection of its own.
Two ways to run it
Both run Laravel Reverb in your own account. They differ in what the container starts from.
| What | On your app's image Available | On the Brewless image Closed beta |
|---|---|---|
| Image | The release image of your app, started as php artisan reverb:start. | A small image Brewless maintains: a bare Laravel skeleton with laravel/reverb. |
| Variables | All of your app's variables, plus the Reverb ones. | Only the Reverb keys and the origins that may connect. |
| Follows | Every deploy and every rollback. | Every deploy, to the image version Brewless pins. |
| Needs | laravel/reverb in your composer.json. | Access to the beta, which you request in the console on the Realtime page of an environment. |
| Memory | 512 MB by default. | 256 MB by default. |
If your app has its own docker/entrypoint.sh, it has to hand other arguments on to php artisan, as the Brewless base entrypoint does. That is how the image starts Reverb instead of the web server.
Connecting with Laravel Echo
Your frontend needs the key and the address at build time. Brewless gives the build VITE_REVERB_APP_KEY, VITE_REVERB_HOST, VITE_REVERB_PORT and VITE_REVERB_SCHEME, so the file that php artisan install:broadcasting writes works as it is.
resources/js/echo.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
One build per environment
The keys differ per environment, so they are part of what marks an image. An image built with the keys of staging is never reused for production.
A Content-Security-Policy
If your app sends one, allow the address in connect-src, for example connect-src 'self' wss://ws.example.com.
What it costs at Scaleway
The Reverb container is billed by Scaleway, on your own bill, like your other containers: per second it runs, for its processor and its memory.
| Size | Always on (min: 1) | Sleeps (min: 0) |
|---|---|---|
| 140 mvCPU, 256 MB | about €5.00 a month | Only while a connection is open |
| 280 mvCPU, 512 MB | about €10.00 a month | Only while a connection is open |
With min: 0 the container scales to zero when the last browser disconnects and costs nothing until the next one connects. The first visitor after a quiet spell waits for the container to start; Echo keeps trying until it is up.
Scaleway's list prices for Serverless Containers in fr-par, before VAT, as of November 2026: €0.0000100 per vCPU-second and €0.0000020 per GB-second, over a month of 730 hours. Your own Scaleway prices are what count.
The limits, honestly
Realtime is a container that holds connections open, on a platform built for requests. That works well within these bounds.
One container
Realtime runs one Reverb container. Measured on Scaleway, one container held 400 open connections; memory runs out first, so give it more for more.
At most an hour per connection
Scaleway closes a connection after the timeout, an hour at most. Echo connects again by itself, usually within a second.
A deploy reconnects everyone
A deploy replaces the container. For about 30 seconds the old one keeps its browsers, then they connect again, to the new release.
Fetch state after reconnecting
Events sent while a browser was disconnected are not sent again. Load the current state when Echo is connected again.
An open tab keeps it awake
While a tab holds a connection, the container runs and is billed, also when nothing is being sent.
A start after a quiet spell
With min: 0 the first browser after a quiet spell waits for the container to start. Set min: 1 when that wait matters more than the cost.
Read more
Realtime for Laravel, under the hood brewless.yml reference Laravel Reverb documentation Why your Laravel container never scales to zero