# Realtime for Laravel: Reverb in your own Scaleway account

How Brewless runs Laravel Reverb on a serverless container in your own Scaleway account, the one-hour limit, and how to reconnect and refetch with Echo.

## In short

- Realtime runs Laravel Reverb in a Serverless Container of its own in your Scaleway project, and browsers connect over wss:// straight to that container, without Brewless or a CDN in between.
- A WebSocket on a serverless container lives at most as long as the container's timeout, an hour at most, and a deploy replaces the container, so every client has to be ready to reconnect.
- Events sent while a browser was disconnected are not replayed, so a page should load the current state again each time Echo connects after the first time.
- Reverb checks the cache for its restart signal every five seconds, so unless your app's cache is Redis Brewless gives the Reverb container an array cache, and a sleeping Serverless SQL Database stays asleep.
- With min 0 the Reverb container costs nothing while nobody is connected; always on, 140 mvCPU and 256 MB come to about €5 a month at Scaleway's list prices.

Realtime is new and in beta: it works and is measured on Scaleway, but its limits and defaults may still change.


Laravel broadcasting needs a WebSocket server, and a WebSocket server is the one thing serverless hosting is least built for: it holds connections open, while a serverless container is made to answer a request and go back to sleep.

Brewless now has Realtime: Laravel Reverb in a container of its own, in your own Scaleway account. This post explains how that works, where the limits are and why they are there, and the two things your frontend should do to live with them.

## Can Laravel Reverb run on a serverless container?

Yes, within limits. Scaleway Serverless Containers accept WebSocket connections, and each open connection counts as a request in flight until it closes. So a connection lives at most as long as the container's timeout, which is an hour at most, and one container holds as many connections as its concurrency allows, at most 80.

Those two numbers shape everything below. Within them, Reverb runs as it does anywhere else.

## What Brewless sets up

You switch it on per environment:

```yaml brewless.yml
environments:
  production:
    realtime: true
```

On the next deploy Brewless does four things:

1. It makes a Serverless Container named `realtime` in the environment's namespace, next to `web` and `worker`, with a timeout of an hour and at most one instance.
2. It makes the Reverb app id, key and secret once, in a secret in your own Secret Manager. Brewless keeps the reference, never the values.
3. It gives your web, release and worker containers `REVERB_APP_ID`, `REVERB_APP_KEY`, `REVERB_APP_SECRET`, `REVERB_HOST`, `REVERB_PORT=443` and `REVERB_SCHEME=https`, and `BROADCAST_CONNECTION=reverb` when your app sets no connection of its own.
4. It makes a DNS record for `ws.` in front of your app's own host and attaches that hostname to the container, which gets its own certificate. Until that address is ready, the container's own `scw.cloud` address is used.

All settings, with their defaults:

```yaml brewless.yml
environments:
  staging:
    realtime:
      image: app        # or brewless (closed beta)
      memory: 512       # MB; 512 on your app's image, 256 on the Brewless image
      min: 0            # 0 sleeps while nobody is connected, 1 is always on
      timeout: 3600     # seconds a connection may stay open, 10 to 3600
      host: true        # ws.<your host>, a hostname of your own, or false
```

### Straight to the container

Browsers connect over `wss://` to the Reverb container itself. Brewless is never a hop for your visitors, and Realtime does not change that.

There is no bunny.net pull zone in front of it either. A CDN earns its place by keeping responses at the edge, and a WebSocket has nothing to keep. A pull zone would only add a charge per connection minute and an idle timeout of its own.

What goes through the WebSocket stays between your app, your container and the browser. No hosted WebSocket service of another company is in that chain.

### Two images to start from

With `image: app`, the default, the Reverb container runs your app's own release image, started as `php artisan reverb:start`. It gets all of your app's variables plus the Reverb ones, and follows every deploy and every rollback. It needs `laravel/reverb` in your `composer.json`.

> [!WARNING]
> 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. Otherwise the container starts the web server instead of Reverb.

With `image: brewless`, the container runs a small image Brewless maintains: a bare Laravel skeleton with `laravel/reverb`. It gets only the Reverb keys and the origins that may connect, and starts in 256 MB. This one is in closed beta; you request it in the console, on the **Realtime** page of an environment.

## The cache that would keep your database awake

`php artisan reverb:restart` works by putting a timestamp in the cache. A running Reverb server looks for it every five seconds, and restarts when it finds a newer one.

That is fine with Redis. With the cache in the database it is a query every five seconds, all day. A Scaleway Serverless SQL Database falls asleep five minutes after its last query, so a Reverb container with the database cache would keep it awake, and billed, all month.

So unless your app's cache store is Redis, Brewless starts the Reverb container on your app's image with `CACHE_STORE=array` (a cache in Object Storage would be asked just as often). The restart signal then never reaches it, and does not need to: every deploy replaces the container anyway. The Brewless image does not touch your database at all.

## One hour, then reconnect

A connection is closed when the container's timeout is up, an hour at most, and when a deploy replaces the container. Neither is a failure. pusher-js, which Laravel Echo uses for Reverb, connects again by itself, usually within a second.

What does not come back is what happened in between. Reverb sends an event to whoever is connected at that moment and keeps nothing for later. A browser that was reconnecting missed it.

The fix is the same as for a laptop that wakes from sleep: when the connection comes back, ask your app for the current state.

```js 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'],
});

// After every reconnect, not the first connect: load what may have been missed.
let connectedBefore = false;

window.Echo.connector.pusher.connection.bind('connected', () => {
    if (connectedBefore) {
        window.dispatchEvent(new Event('echo:reconnected'));
    }

    connectedBefore = true;
});
```

A page then listens for that event and fetches what it shows:

```js resources/js/orders.js
window.Echo.private('orders').listen('OrderShipped', (event) => {
    showOrder(event.order);
});

window.addEventListener('echo:reconnected', async () => {
    const response = await fetch('/orders/latest', { headers: { Accept: 'application/json' } });

    showOrders(await response.json());
});
```

The first half of that file is what `php artisan install:broadcasting` writes. Brewless gives the build `VITE_REVERB_APP_KEY`, `VITE_REVERB_HOST`, `VITE_REVERB_PORT` and `VITE_REVERB_SCHEME`, so it works without changes. Because those values differ per environment, they are part of what marks a built image: an image built with the keys of staging is never reused for production.

> [!TIP]
> Private and presence channels are authorised by your own app at `/broadcasting/auth`, an ordinary request to your web container. Nothing about that changes on Brewless.

## A Content-Security-Policy

If your app sends a Content-Security-Policy, the browser refuses a WebSocket to an address that `connect-src` does not name. Add the Reverb address:

```text
Content-Security-Policy: connect-src 'self' wss://ws.example.com
```

Check it in a browser, not with curl: curl ignores the header, so a page can look fine there and still have no live updates for every visitor.

## What it costs, and when it sleeps

The Reverb container is billed by Scaleway like your other containers, per second for processor and memory, on your own Scaleway bill. At Scaleway's list prices in fr-par before VAT (November 2026), €0.0000100 per vCPU-second and €0.0000020 per GB-second:

| Size | Always on | Sleeping when idle |
| --- | --- | --- |
| 140 mvCPU, 256 MB | about €5.00 a month | only while connected |
| 280 mvCPU, 512 MB | about €10.00 a month | only while connected |

Always on is `min: 1`; sleeping when idle is `min: 0`.

With `min: 0`, the default, the container scales to zero after the last browser disconnects. The first visitor after a quiet spell waits for it to start; pusher-js keeps trying until it is up.

One thing to keep in mind: an open tab is an open connection. While somebody leaves your dashboard open, the container runs and is billed, also when nothing is being sent.

## The limits

| Limit | Why |
| --- | --- |
| One Reverb container per environment | Spreading connections over several needs Reverb's Redis scaling, which Realtime does not use |
| A few hundred open connections | Measured: one container held 400; memory runs out first, about a third of a megabyte per connection |
| At most an hour per connection | The container's timeout; Echo reconnects |
| A deploy reconnects everyone | The container is replaced; for about 30 seconds the old one keeps its browsers and they miss what is sent |
| No replay of missed events | Reverb sends to whoever is connected; refetch after reconnecting |

For live notifications, a dashboard that updates, or a status page for a team, that is plenty. For thousands of open connections per app, it is not, and it is better to know that now.

The settings are in the [brewless.yml reference](https://docs.brewless.eu/reference/brewless-yml), and the overview is on the [Realtime page](/realtime). Why other things keep a Laravel container awake is in [Why your Laravel container never scales to zero](/blog/laravel-container-never-sleeps).

## Frequently asked questions

### Can I run Laravel Reverb on serverless containers?

Yes, within limits. Scaleway Serverless Containers accept WebSocket connections, and a connection stays open until the container's timeout, at most an hour. Brewless runs Reverb on one such container per environment, which held 400 open connections when we measured it.

### Does WebSocket traffic go through Brewless or bunny.net?

No. Browsers connect over wss:// straight to the Reverb container in your own Scaleway project. Brewless is never a hop for visitor traffic, and there is no CDN in front of the container because WebSockets have nothing to cache.

### What happens to open connections when I deploy?

A deploy replaces the Reverb container, so every open connection is closed and Laravel Echo connects again by itself, usually within a second. Load the current state of the page after that reconnect, because events sent in between are not sent again.

### Why does the Reverb container not use my database cache?

Reverb looks in the cache for its restart signal every five seconds. With the cache in the database that is a query every five seconds, which would keep a Serverless SQL Database awake all month. Brewless therefore gives the Reverb container an array cache when your app's cache store is the database.

### What does Realtime cost at Scaleway?

The Reverb container is billed like your other containers, per second for processor and memory. Always on, 140 mvCPU and 256 MB come to about €5 a month at Scaleway's list prices in fr-par before VAT. With min 0 it costs nothing while nobody is connected.

Source: https://brewless.eu/blog/realtime-laravel-reverb-scaleway
