> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the docs for Sevalla, a platform for deploying applications, databases, static sites, object storage and load balancers. The dashboard is at https://app.sevalla.com.
> Bold text in steps is an exact dashboard label.
> To act on a Sevalla account, prefer the Sevalla MCP server (https://mcp.sevalla.com/mcp, see https://docs.sevalla.com/ai-agents/overview.md), then the Sevalla CLI (https://docs.sevalla.com/developer-tools/cli.md), then the REST API (https://api-docs.sevalla.com, OpenAPI spec at https://api.sevalla.com/v3/openapi.json).
> Every page index is at https://docs.sevalla.com/llms.txt.

# Run Laravel Reverb

> Run Laravel Reverb WebSockets on Sevalla in the web process or its own application, broadcast to it over the private network, and reach every instance with Redis.

Laravel Reverb is Laravel's WebSocket server. Browsers connect to it with Laravel Echo and subscribe to channels, and the application broadcasts events to them by sending each event to Reverb's HTTP API. When `laravel/reverb` is installed, [Ingot](https://docs.sevalla.com/applications/builds/ingot.md) runs Reverb inside the web process, next to FrankenPHP, and FrankenPHP passes WebSocket connections on the application's own domain to it, so Reverb needs no process, domain or port of its own.

## At a glance

What this guide adds to the application from [Deploy a Laravel application](https://docs.sevalla.com/guides/php/laravel/deploy.md#at-a-glance):

| | |
| - | - |
| Processes | The web process runs Reverb on `127.0.0.1:6001` next to FrankenPHP. Or a second application runs Reverb, as in [Run Reverb in its own application](#run-reverb-in-its-own-application) |
| Port | The `PORT` environment variable, `8080` by default: FrankenPHP passes WebSocket connections to `/app/...` and Reverb's API on `/apps/...` to Reverb |
| Services | With more than one web instance: a Redis or Valkey database over an internal connection |
| Size | **H1** or larger for the web process |

### Environment variables

| Variable | Required | Available during | Secret | Value |
| - | - | - | - | - |
| `BROADCAST_CONNECTION` | Yes | Runtime | No | `reverb` |
| `REVERB_APP_ID` | Yes | Runtime | No | A number you choose, for example `621330` |
| `REVERB_APP_KEY` | Yes | Runtime | No | A random string: `openssl rand -hex 10`. Browsers see it. |
| `REVERB_APP_SECRET` | Yes | Runtime | Yes | A random string: `openssl rand -hex 20` |
| `REVERB_HOST` | Yes | Runtime | No | Where the application sends events: the web process's internal hostname, for example `storefront-x7k2m-web.storefront-x7k2m.svc.cluster.local` |
| `REVERB_PORT` | Yes | Runtime | No | `8080` |
| `REVERB_SCHEME` | Yes | Runtime | No | `http` |
| `VITE_REVERB_APP_KEY` | Yes | Build | No | The value of `REVERB_APP_KEY` |
| `VITE_REVERB_HOST` | Yes | Build | No | Where browsers connect: the application's domain, for example `storefront-x7k2m.sevalla.app` |
| `VITE_REVERB_PORT` | Yes | Build | No | `443` |
| `VITE_REVERB_SCHEME` | Yes | Build | No | `https` |
| `REVERB_SCALING_ENABLED` | With more than one instance | Runtime | No | `true` |
| `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD`, `REDIS_URL` | With more than one instance | Runtime | `REDIS_PASSWORD` and `REDIS_URL` | Added by the internal connection to the Redis database |

The application's other variables are in [Deploy a Laravel application](https://docs.sevalla.com/guides/php/laravel/deploy.md#environment-variables).

## Before you begin

* A Laravel application deployed on Sevalla, as in [Deploy Laravel](https://docs.sevalla.com/guides/php/laravel/deploy.md).

## Install Reverb

Install Reverb 1 and Laravel's broadcasting files, then the two packages Echo needs in the browser:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
php artisan install:broadcasting --reverb --without-node
npm install --save-dev laravel-echo pusher-js
```

`install:broadcasting` installs `laravel/reverb`, writes `config/broadcasting.php`, `config/reverb.php` and `routes/channels.php`, registers the channels file in `bootstrap/app.php`, and adds `resources/js/echo.js`, which `resources/js/app.js` imports. It also writes Reverb's settings and credentials for local development to `.env`, which Sevalla doesn't use. `--without-node` skips its own `npm install` step, which needs an interactive terminal and otherwise fails with `TTY mode requires /dev/tty to be read/writable.`; the second command installs the same packages.

`resources/js/echo.js` reads the `VITE_REVERB_*` variables. Vite writes their values into the JavaScript it builds, so on Sevalla they must be available during the build.

### Broadcast an event

Create an event that Laravel broadcasts on the public `orders` channel. `ShouldBroadcastNow` sends it to Reverb at once; `ShouldBroadcast` queues it for a [queue worker](https://docs.sevalla.com/guides/php/laravel/queues.md) instead:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
php artisan make:event OrderShipped
```

```php app/Events/OrderShipped.php theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;

class OrderShipped implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets;

    public function __construct(public int $orderId)
    {
    }

    /**
     * @return array<int, Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new Channel('orders'),
        ];
    }
}
```

Add a page that listens on the channel and lists each shipped order:

```blade resources/views/orders.blade.php theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Shipped orders</title>
    @vite('resources/js/app.js')
</head>
<body>
    <h1>Shipped orders</h1>
    <ul id="orders"></ul>
    <script type="module">
        window.Echo.channel('orders').listen('OrderShipped', (event) => {
            const item = document.createElement('li');
            item.textContent = `Order ${event.orderId} shipped`;
            document.getElementById('orders').append(item);
        });
    </script>
</body>
</html>
```

```php routes/web.php theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
Route::view('/orders', 'orders');
```

Commit and push the changes, `composer.lock` and `package-lock.json` included. A private channel works the same way on Sevalla; it also needs a signed-in user and a rule in `routes/channels.php`.

## Run Reverb on Sevalla

These steps use Ingot, which runs Reverb in the web process. With your own Dockerfile, see [Deploy with a Dockerfile](#deploy-with-a-dockerfile).

**1. Find the internal hostname**

On the application's **Networking** tab, copy the **Web** process's hostname from **Internal hostnames**, for example `storefront-x7k2m-web.storefront-x7k2m.svc.cluster.local`.

**2. Add the runtime variables**

Click the **Environment variables** tab, then click **Add environment variables**. Add these variables, select only **Available during runtime**, and click **Add environment variables**:

| Variable | Value |
| - | - |
| `BROADCAST_CONNECTION` | `reverb` |
| `REVERB_APP_ID` | A number, for example `621330` |
| `REVERB_APP_KEY` | The output of `openssl rand -hex 10` |
| `REVERB_APP_SECRET` | The output of `openssl rand -hex 20` |
| `REVERB_HOST` | The internal hostname from the first step |
| `REVERB_PORT` | `8080` |
| `REVERB_SCHEME` | `http` |

**3. Add the build variables**

Click **Add environment variables** again. Add these variables, select only **Available during buildtime**, and click **Add environment variables**:

| Variable | Value |
| - | - |
| `VITE_REVERB_APP_KEY` | The same value as `REVERB_APP_KEY` |
| `VITE_REVERB_HOST` | The application's domain, for example `storefront-x7k2m.sevalla.app` |
| `VITE_REVERB_PORT` | `443` |
| `VITE_REVERB_SCHEME` | `https` |

**4. Deploy**

Click the **Overview** tab, then click **Deploy now** under **Recent deployments**. Check the **Branch** and click **Deploy**. The build must run, so that Vite reads the new variables.

### How Ingot runs Reverb

Ingot finds `laravel/reverb` in `composer.lock`, and its build plan has a **Websockets** row:

```text Build log theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
│ Websockets           Reverb, served on PORT next to the application    composer.lock laravel/reverb       │
```

The web process's start script then runs `php artisan reverb:start --host=127.0.0.1 --port=6001` beside FrankenPHP and starts Reverb again if it stops. FrankenPHP passes requests that upgrade to a WebSocket under `/app/` to Reverb, and requests to Reverb's API under `/apps/<id>/` (`events`, `batch_events`, `connections`, `channels`, `users`), so both share the application's domain and the `PORT` with Laravel. Every other request goes to Laravel. When the web process stops, the script stops Reverb with it, and Reverb logs `Gracefully terminating connections.`

Two settings say where Reverb is, one for each side:

* **`REVERB_HOST`, `REVERB_PORT` and `REVERB_SCHEME`** are where Laravel sends events. The web process's internal hostname on port `8080` keeps them on Sevalla's private network, and reaches Reverb from every process of the application, so a queue worker can broadcast too. The application's domain on port `443` with `https` works as well, through Sevalla's proxy and Cloudflare.
* **`VITE_REVERB_*`** are where browsers connect: the application's domain on port `443`, over `wss://`.

The deployment's build log also shows Ingot's warnings `With BROADCAST_CONNECTION=reverb, Laravel neither builds nor starts without REVERB_APP_ID, REVERB_APP_KEY and REVERB_APP_SECRET` and `Reverb runs in each web instance`. Both are covered by the variables in these steps and in [Run more than one instance](#run-more-than-one-instance).

## Verify

1. When the deployment's status is **Deployed**, click the **Logs** tab. The web process's runtime logs show Reverb's start next to FrankenPHP's:

   ```text Runtime logs theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
      INFO  Starting server on 127.0.0.1:6001 (storefront-x7k2m-web.storefront-x7k2m.svc.cluster.local).
   ```

2. Open `/orders` on the application's domain, for example `https://storefront-x7k2m.sevalla.app/orders`. The page shows **Shipped orders**.

3. Click the **Web terminal** tab, and in the shell that opens in the **Web** process, broadcast an event:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   php artisan tinker --execute 'App\Events\OrderShipped::dispatch(42);'
   ```

   The open page adds `Order 42 shipped` without a reload.

### Check from a terminal

These checks need no dashboard and no browser. `API`, `H`, `APP_ID`, `WEB_ID` and `REVERB_APP_KEY` are set as in [Deploy with the API](#deploy-with-the-api), and the checks need Node.js 22 or later, which has a built-in `WebSocket`.

1. Connect to Reverb as a browser does, with your application's domain, subscribe to `orders` and wait for one event, in the background:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   node -e '
   const ws = new WebSocket(`wss://${process.argv[1]}/app/${process.argv[2]}?protocol=7`);
   ws.onmessage = ({ data }) => {
     const { event, data: payload } = JSON.parse(data);
     console.log(event, event.startsWith("pusher") ? "" : payload);
     if (event === "pusher:connection_established") ws.send(JSON.stringify({ event: "pusher:subscribe", data: { channel: "orders" } }));
     if (event === "App\\Events\\OrderShipped") ws.close();
   };
   setTimeout(() => process.exit(1), 30000);' storefront-x7k2m.sevalla.app "$REVERB_APP_KEY" &
   sleep 3
   ```

2. Broadcast an event from the web process with [Run a command in a process](https://api-docs.sevalla.com/v3/applications/run-a-command-in-a-process) (`execProcessCommandV3`), then wait for the listener:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s -X POST "$API/applications/$APP_ID/processes/$WEB_ID/exec" -H "$H" -H "Content-Type: application/json" \
     -d '{"command": ["php", "artisan", "tinker", "--execute", "App\\Events\\OrderShipped::dispatch(42);"], "timeout": 30}' \
     | jq -r .exit_code
   wait
   ```

   ```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   pusher:connection_established 
   pusher_internal:subscription_succeeded 
   App\Events\OrderShipped {"orderId":42}
   0
   ```

   The listener prints each message it gets: the connection, the subscription, then the event with its data. The `0` is the exit code of the command in the web process.

## Where Reverb can run on Sevalla

Browsers on an `https://` page connect to Reverb over `wss://`, so Reverb must be behind a domain that Sevalla serves over HTTPS: the domain of a web process.

| Where | How browsers connect | How the application broadcasts | Use it when |
| - | - | - | - |
| The web process, with Ingot | The application's domain | The web process's internal hostname, port `8080` | Your application builds with Ingot. Reverb grows with the web process. |
| The web process of its own application | That application's domain | An internal connection to that application, its internal hostname, port `8080` | Your application builds from a Dockerfile, or Reverb needs a size or instance count of its own. |
| A background worker | Not from a browser: a background worker has no domain, and a [TCP proxy](https://docs.sevalla.com/applications/networking.md#tcp-proxies) forwards plain TCP, so the address is `ws://`. Browsers refuse it from an HTTPS page with `SecurityError: Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.` | The worker's internal hostname, port `8080` | Clients other than browsers that accept unencrypted `ws://`. |

## Run Reverb in its own application

A second application from the same repository can run Reverb in its web process, with its own domain, size and instances. The main application keeps serving Laravel and broadcasts to it over the private network.

**1. Create the Reverb application**

In the sidebar, click **Applications**, then click **Create** > **Application**. Under **Source**, click **Private repository**, then choose the main application's **Git provider**, **Repository** and **Branch**. Under **Location**, choose the same **Data center**. Enter `storefront-reverb` as the **Name**, keep the **Size** at **H1**, and click **Create application**.

**2. Start Reverb in its web process**

On the new application's **Processes** tab, click **⋯** on the **Web** process, then click **Update process**. Enter `exec php artisan reverb:start --host=0.0.0.0 --port=$PORT` as the **Custom start command**. Under **Health checks**, turn on **Readiness probe** with the **Path** `/up`, Reverb's health route, and click **Update process**.

**3. Add its environment variables**

On its **Environment variables** tab, add `REVERB_APP_ID`, `REVERB_APP_KEY` and `REVERB_APP_SECRET` with the main application's values, and `CACHE_STORE` with the value `file`, all with **Available during runtime** selected. Reverb reads the cache when it starts, to see whether `php artisan reverb:restart` asked it to restart, and Laravel's default cache store is the database, which this application doesn't have: without `CACHE_STORE`, the runtime logs show `Database file at path [/app/database/database.sqlite] does not exist.` at every start.

**4. Deploy it**

Click the **Overview** tab, then click **Deploy now**. The runtime logs show `INFO  Starting server on 0.0.0.0:8080.`

**5. Connect the main application**

In the main application, click the **Networking** tab. Under **Connected services**, click **Add internal connection**, choose `storefront-reverb` as the **Service**, and click **Add internal connection**.

**6. Point the main application at Reverb**

On the main application's **Environment variables** tab, set `REVERB_HOST` to the Reverb application's **Web** internal hostname, for example `storefront-reverb-a1b2c-web.storefront-reverb-a1b2c.svc.cluster.local`, and `VITE_REVERB_HOST` to its domain, for example `storefront-reverb-a1b2c.sevalla.app`. Then click **Deploy now** on the **Overview** tab.

The `/orders` page now connects to the Reverb application's domain, and the main application sends events to it over the internal connection. The main application's web process still runs Ingot's Reverb next to FrankenPHP, unused, because `laravel/reverb` is installed.

## Run more than one instance

Sevalla spreads WebSocket connections across a web process's instances, and each instance runs its own Reverb, which knows only the browsers connected to it. An event that the application sends reaches one instance, so only the browsers connected to that instance get it. With 2 instances and 8 connected clients, one event reached 3 of them, and another 5.

Reverb's scaling mode passes each event to every instance through Redis:

**1. Connect a Redis database**

Create a Redis or Valkey database in the same location and add an internal connection to it with **Add environment variables to the application** turned on, as in [Cache Laravel with Redis](https://docs.sevalla.com/guides/php/laravel/caching.md#create-a-redis-database). Sevalla adds `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` and `REDIS_URL`, which Reverb reads.

**2. Turn on scaling**

Add the environment variable `REVERB_SCALING_ENABLED` with the value `true`, with **Available during runtime** selected, then deploy.

With scaling on, the same test reached 8 of 8 clients on 2 instances. Each Reverb instance knows only its own connections wherever it runs, so a [Reverb application](#run-reverb-in-its-own-application) with more than one instance needs the same two steps.

## Deploy with a Dockerfile

Ingot's Reverb is part of the image Ingot builds. The [Dockerfile](https://docs.sevalla.com/guides/php/laravel/deploy.md#deploy-with-a-dockerfile) in the deploy guide starts FrankenPHP only, so run Reverb in [its own application](#run-reverb-in-its-own-application) from the same Dockerfile: its start command replaces the image's `CMD`. Make two changes to the Dockerfile:

1. Install the `pcntl` extension, with which Reverb handles `SIGTERM` and closes its connections before the instance stops. Change the first `RUN` line:

   ```dockerfile Dockerfile theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   RUN install-php-extensions pdo_pgsql pcntl \
   ```

2. Pass the `VITE_REVERB_*` variables to the stage that builds the assets. Sevalla passes every variable available during the build as a build argument, and a stage sees only the arguments it declares:

   ```dockerfile Dockerfile theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   FROM node:24-trixie-slim AS assets
   ARG VITE_REVERB_APP_KEY VITE_REVERB_HOST VITE_REVERB_PORT VITE_REVERB_SCHEME
   WORKDIR /app
   ```

Without the `ARG` line, Vite builds `echo.js` without a key or host, and the page never connects. Switch both applications to the Dockerfile build strategy as in [Deploy with a Dockerfile](https://docs.sevalla.com/guides/php/laravel/deploy.md#deploy-with-a-dockerfile), and deploy both.

## Troubleshoot

| Symptom | Cause | Fix |
| - | - | - |
| The page never gets events, and the browser console shows `You must pass your app key when you instantiate Pusher.` | The `VITE_REVERB_*` variables weren't available during the build, so Vite built `echo.js` without them. | Make them available during build, then deploy with a build. With a Dockerfile, add the `ARG` line from [Deploy with a Dockerfile](#deploy-with-a-dockerfile). |
| Some open pages get an event and others don't | The web process has more than one instance, without Reverb's scaling mode. | Turn on scaling as in [Run more than one instance](#run-more-than-one-instance). |
| `php artisan install:broadcasting` fails with `TTY mode requires /dev/tty to be read/writable.` | The command's Node.js step needs an interactive terminal. | Run it with `--without-node`, then `npm install --save-dev laravel-echo pusher-js`. |
| The Reverb application's runtime logs show `Database file at path [/app/database/database.sqlite] does not exist.` | Reverb reads the cache, and the default cache store is a database that the application doesn't have. | Add `CACHE_STORE` with the value `file`, then deploy. |

## Deploy with the API

The same steps as [Run Reverb on Sevalla](#run-reverb-on-sevalla), as calls to the [Sevalla API](https://docs.sevalla.com/developer-tools/api.md). `APP_ID`, `WEB_ID` and `APP_HOST` are the application, its web process and its domain from [Deploy with the API](https://docs.sevalla.com/guides/php/laravel/deploy.md#deploy-with-the-api) in the deploy guide, with `API` and `H` set as there. Push the Reverb changes first: the deployment builds the branch as it is.

**1. Add the environment variables**

[Get process](https://api-docs.sevalla.com/v3/applications/get-process) (`getProcessV3`) returns the web process's internal hostname. [Create environment variable](https://api-docs.sevalla.com/v3/applications/create-environment-variable) (`createEnvironmentVariableV3`) adds one variable per call: new Reverb credentials and the server's address for runtime, and the browser's settings for the build:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
WEB_HOST=$(curl -s "$API/applications/$APP_ID/processes/$WEB_ID" -H "$H" | jq -r .internal_hostname)
REVERB_APP_ID=$((100000 + RANDOM))
REVERB_APP_KEY=$(openssl rand -hex 10)
REVERB_APP_SECRET=$(openssl rand -hex 20)
for pair in "BROADCAST_CONNECTION=reverb" "REVERB_APP_ID=$REVERB_APP_ID" "REVERB_APP_KEY=$REVERB_APP_KEY" \
  "REVERB_APP_SECRET=$REVERB_APP_SECRET" "REVERB_HOST=$WEB_HOST" "REVERB_PORT=8080" "REVERB_SCHEME=http"; do
  curl -s -X POST "$API/applications/$APP_ID/env-vars" -H "$H" -H "Content-Type: application/json" \
    -d "$(jq -n --arg key "${pair%%=*}" --arg value "${pair#*=}" '{key: $key, value: $value, is_runtime: true, is_buildtime: false}')" \
    | jq -r .key
done
for pair in "VITE_REVERB_APP_KEY=$REVERB_APP_KEY" "VITE_REVERB_HOST=$APP_HOST" "VITE_REVERB_PORT=443" "VITE_REVERB_SCHEME=https"; do
  curl -s -X POST "$API/applications/$APP_ID/env-vars" -H "$H" -H "Content-Type: application/json" \
    -d "$(jq -n --arg key "${pair%%=*}" --arg value "${pair#*=}" '{key: $key, value: $value, is_runtime: false, is_buildtime: true}')" \
    | jq -r .key
done
```

CLI: `sevalla apps env-vars create $APP_ID --key BROADCAST_CONNECTION --value reverb`, once per variable; the CLI makes each variable available during build and runtime. MCP: `set_environment_variables` with `runtime` and `buildtime` set as in the loops.

**2. Deploy**

[Trigger deployment](https://api-docs.sevalla.com/v3/applications/trigger-deployment) (`triggerDeploymentV3`). The build reads the `VITE_REVERB_*` variables, so the deployment must build the image:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
DEPLOYMENT_ID=$(curl -s -X POST "$API/applications/$APP_ID/deployments" -H "$H" -H "Content-Type: application/json" \
  -d '{}' | jq -r .id)
```

CLI: `sevalla apps deployments trigger $APP_ID`. MCP: `trigger_deployment`.

**3. Wait for the deployment**

Poll [Get deployment](https://api-docs.sevalla.com/v3/applications/get-deployment) (`getDeploymentV3`) until the status is final: `success`, `failed`, `cancelled` or `skipped`. The loop keeps polling through a failed request and stops after 30 minutes:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
for i in $(seq 1 120); do
  STATUS=$(curl -s "$API/applications/$APP_ID/deployments/$DEPLOYMENT_ID" -H "$H" | jq -r '.status // empty')
  case $STATUS in success|failed|cancelled|skipped) break ;; esac
  sleep 15
done
echo "$STATUS"
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
success
```

CLI: `sevalla apps deployments get $DEPLOYMENT_ID --app-id $APP_ID`. MCP: `get_deployment`.

Then run the checks in [Check from a terminal](#check-from-a-terminal), with `$APP_HOST` as the domain.

### Run Reverb in its own application with the API

The same steps as [Run Reverb in its own application](#run-reverb-in-its-own-application), after the steps before. `REPO_URL`, `BRANCH`, `CLUSTER_ID` and `H1_ID` are set as in the deploy guide, and `REVERB_APP_ID`, `REVERB_APP_KEY` and `REVERB_APP_SECRET` as in [Add the environment variables](#deploy-with-the-api).

**1. Create the Reverb application**

[Create application](https://api-docs.sevalla.com/v3/applications/create-application) (`createApplicationV3`) from the same repository and branch, then read its domain with [List application domains](https://api-docs.sevalla.com/v3/applications/list-application-domains) (`listApplicationDomainsV3`):

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REVERB_ID=$(curl -s -X POST "$API/applications" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg cluster "$CLUSTER_ID" --arg repo "$REPO_URL" --arg branch "$BRANCH" \
    '{display_name: "storefront-reverb", cluster_id: $cluster, source: "privateGit", git_type: "github",
      repo_url: $repo, default_branch: $branch}')" \
  | jq -r .id)
for i in $(seq 1 30); do
  REVERB_APP_HOST=$(curl -s "$API/applications/$REVERB_ID/domains" -H "$H" | jq -r '.data[]? | select(.is_primary) | .name')
  [ -n "$REVERB_APP_HOST" ] && break
  sleep 2
done
echo "$REVERB_APP_HOST"
```

CLI: `sevalla apps create --name storefront-reverb --cluster $CLUSTER_ID --source privateGit --git-type github --repository $REPO_URL --branch $BRANCH`. MCP: `create_application` with `deploy: false`.

**2. Start Reverb in its web process**

[Update process](https://api-docs.sevalla.com/v3/applications/update-process) (`updateProcessV3`) sets the start command, **H1** and a readiness probe on Reverb's `/up`:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REVERB_WEB_ID=$(curl -s "$API/applications/$REVERB_ID/processes" -H "$H" | jq -r '.data[] | select(.type == "web") | .id')
curl -s -X PATCH "$API/applications/$REVERB_ID/processes/$REVERB_WEB_ID" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg size "$H1_ID" \
    '{entrypoint: "exec php artisan reverb:start --host=0.0.0.0 --port=$PORT", resource_type_id: $size,
      readiness_probe: {httpGet: {path: "/up", port: 8080}, initialDelaySeconds: 0, periodSeconds: 10,
        timeoutSeconds: 1, successThreshold: 1, failureThreshold: 3}}')" \
  | jq -r .entrypoint
```

```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
exec php artisan reverb:start --host=0.0.0.0 --port=$PORT
```

CLI: `sevalla apps processes update $REVERB_WEB_ID --app-id $REVERB_ID --resource-type $H1_ID --command 'exec php artisan reverb:start --host=0.0.0.0 --port=$PORT'`. MCP: `sevalla_api_update` with `updateProcessV3` and the same body.

**3. Add the credentials and deploy**

The Reverb application needs the same three credentials, and `CACHE_STORE` set to `file`, because it has no database. Add them with [Create environment variable](https://api-docs.sevalla.com/v3/applications/create-environment-variable), then [trigger a deployment](https://api-docs.sevalla.com/v3/applications/trigger-deployment) and wait for it:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
for pair in "REVERB_APP_ID=$REVERB_APP_ID" "REVERB_APP_KEY=$REVERB_APP_KEY" "REVERB_APP_SECRET=$REVERB_APP_SECRET" "CACHE_STORE=file"; do
  curl -s -X POST "$API/applications/$REVERB_ID/env-vars" -H "$H" -H "Content-Type: application/json" \
    -d "$(jq -n --arg key "${pair%%=*}" --arg value "${pair#*=}" '{key: $key, value: $value, is_runtime: true, is_buildtime: false}')" \
    | jq -r .key
done
REVERB_DEPLOYMENT_ID=$(curl -s -X POST "$API/applications/$REVERB_ID/deployments" -H "$H" -H "Content-Type: application/json" \
  -d '{}' | jq -r .id)
for i in $(seq 1 120); do
  STATUS=$(curl -s "$API/applications/$REVERB_ID/deployments/$REVERB_DEPLOYMENT_ID" -H "$H" | jq -r '.status // empty')
  case $STATUS in success|failed|cancelled|skipped) break ;; esac
  sleep 15
done
echo "$STATUS"
```

CLI: `sevalla apps deployments trigger $REVERB_ID`. MCP: `set_environment_variables`, then `trigger_deployment`.

**4. Connect the main application to Reverb**

[Create internal connection](https://api-docs.sevalla.com/v3/applications/create-internal-connection) (`createApplicationInternalConnectionV3`) with `target_type` `app`:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s -X POST "$API/applications/$APP_ID/internal-connections" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg app "$REVERB_ID" '{target_id: $app, target_type: "app"}')" | jq -r .id
```

CLI: `sevalla apps internal-connections create $APP_ID --target-id $REVERB_ID --target-type app`. MCP: `sevalla_api_create` with `createApplicationInternalConnectionV3` and the same body.

**5. Point the main application at Reverb**

[List environment variables](https://api-docs.sevalla.com/v3/applications/list-environment-variables) (`listEnvironmentVariablesV3`) gives each variable's ID, and [Update environment variable](https://api-docs.sevalla.com/v3/applications/update-environment-variable) (`updateEnvironmentVariableV3`) changes `REVERB_HOST` to the Reverb application's internal hostname and `VITE_REVERB_HOST` to its domain. Then deploy the main application, so the build reads the new `VITE_REVERB_HOST`:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REVERB_INTERNAL_HOST=$(curl -s "$API/applications/$REVERB_ID/processes/$REVERB_WEB_ID" -H "$H" | jq -r .internal_hostname)
for pair in "REVERB_HOST=$REVERB_INTERNAL_HOST" "VITE_REVERB_HOST=$REVERB_APP_HOST"; do
  VAR_ID=$(curl -s "$API/applications/$APP_ID/env-vars" -H "$H" | jq -r --arg key "${pair%%=*}" '.data[] | select(.key == $key) | .id')
  curl -s -X PUT "$API/applications/$APP_ID/env-vars/$VAR_ID" -H "$H" -H "Content-Type: application/json" \
    -d "$(jq -n --arg value "${pair#*=}" '{value: $value}')" | jq -r .key
done
DEPLOYMENT_ID=$(curl -s -X POST "$API/applications/$APP_ID/deployments" -H "$H" -H "Content-Type: application/json" \
  -d '{}' | jq -r .id)
for i in $(seq 1 120); do
  STATUS=$(curl -s "$API/applications/$APP_ID/deployments/$DEPLOYMENT_ID" -H "$H" | jq -r '.status // empty')
  case $STATUS in success|failed|cancelled|skipped) break ;; esac
  sleep 15
done
echo "$STATUS"
```

CLI: `sevalla apps env-vars update $VAR_ID --app-id $APP_ID --value <value>`. MCP: `set_environment_variables`, then `trigger_deployment`.

Then run the checks in [Check from a terminal](#check-from-a-terminal) with `$REVERB_APP_HOST` as the listener's domain: the browser connects to the Reverb application now. To build both applications from the [Dockerfile](#deploy-with-a-dockerfile), send `{"build_type": "dockerfile", "dockerfile_path": "Dockerfile", "docker_context": "."}` to [Update application](https://api-docs.sevalla.com/v3/applications/update-application) (`updateApplicationV3`) for each before you deploy it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.