> ## 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 Horizon

> Run Laravel Horizon in a Sevalla background worker on a Redis or Valkey database, size it to its worker processes, stop it cleanly on deployments, and sign in to its dashboard.

Laravel Horizon runs Laravel's Redis queues and adds a dashboard at `/horizon` that shows jobs, throughput and failures. On Sevalla, `php artisan horizon` runs in a background worker: a process that runs from the same image as the web process with its own start command. Horizon starts a supervisor and the worker processes that run the jobs, and adds or removes worker processes as the queue grows, so the worker's size decides how many it can run.

## 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 | `Horizon` background worker (`exec php artisan horizon`, 1 instance) |
| Services | A Redis or Valkey database over an internal connection, next to the deploy guide's PostgreSQL |
| Size | **S1** for the Horizon worker, or **H1** with `HORIZON_MAX_PROCESSES` set to `5`; **DB1** for the Redis database |

### Environment variables

| Variable | Required | Available during | Secret | Value |
| - | - | - | - | - |
| `QUEUE_CONNECTION` | Yes | Runtime | No | `redis`. Horizon runs only Redis queues. |
| `REDIS_HOST` | Yes | Runtime | No | Added by the internal connection: the database's internal hostname |
| `REDIS_PORT` | Yes | Runtime | No | Added by the internal connection: `6379` |
| `REDIS_PASSWORD` | Yes | Runtime | Yes | Added by the internal connection: the database password |
| `REDIS_URL` | No | Runtime | Yes | Added by the internal connection: `redis://:<password>@<host>:<port>/0`, or `valkey://` for Valkey |
| `HORIZON_MAX_PROCESSES` | No | Runtime | No | The most worker processes Horizon starts, read in `config/horizon.php`: `10` when unset, `5` on **H1** |

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 Horizon

Install Horizon 5 and publish its configuration and service provider:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
composer require laravel/horizon
php artisan horizon:install
```

`horizon:install` writes `config/horizon.php` and `app/Providers/HorizonServiceProvider.php`, and registers the provider in `bootstrap/providers.php`. Horizon needs the `pcntl` and `posix` PHP extensions: Ingot finds `ext-pcntl` in `composer.lock` and installs it, and its build plan lists it:

```text Build log theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
│ Extensions           pcntl                                      composer.lock                      │
│                      bcmath, intl, pdo_mysql, pdo_pgsql, redis  Laravel defaults                   │
```

### Create a job

Create a job class:

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

Replace the generated class with a job that takes an order ID and logs a line:

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

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class SendOrderConfirmation implements ShouldQueue
{
    use Queueable;

    public function __construct(public int $orderId)
    {
    }

    public function handle(): void
    {
        Log::info("Sending the confirmation for order {$this->orderId}");
    }
}
```

Queue it from a controller or anywhere else with `SendOrderConfirmation::dispatch($order->id)`. With `QUEUE_CONNECTION` set to `redis`, the job waits in Redis until Horizon runs it.

### Set the number of worker processes

Horizon's `production` environment, which Laravel uses when `APP_ENV` isn't set, starts 1 worker process and adds more while jobs wait, up to `maxProcesses`, 10 by default. Each worker process is a PHP process with the whole application loaded, so the memory Horizon needs grows with them. Read the limit from an environment variable, so you can fit it to the worker's size without a code change:

```php config/horizon.php theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
'environments' => [
    'production' => [
        'supervisor-1' => [
            'maxProcesses' => (int) env('HORIZON_MAX_PROCESSES', 10),
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
        ],
    ],
```

[Size the Horizon worker](#size-the-horizon-worker) has the memory each number of processes needs.

### Protect the dashboard

Outside the `local` environment, Horizon shows its dashboard only to users that its `viewHorizon` gate allows, and answers everyone else with `403`. Laravel's new projects have no sign-in page, so use HTTP basic authentication: add Laravel's `auth.basic` middleware to Horizon's routes, which asks for the email and password of a user in the `users` table:

```php config/horizon.php theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
'middleware' => ['web', 'auth.basic'],
```

Then list who may open the dashboard in the gate:

```php app/Providers/HorizonServiceProvider.php theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
protected function gate(): void
{
    Gate::define('viewHorizon', function ($user = null) {
        return in_array(optional($user)->email, [
            'ops@example.com',
        ]);
    });
}
```

Commit and push the changes, `composer.json` and `composer.lock` included.

## Connect a Redis database

**1. Create the database**

In the sidebar, click **Databases**, then click **Create database**. Under **Database**, choose **Redis**, or **Valkey**: Horizon works with both. Enter `storefront-redis` as the **Display name**, choose the same **Data center** as the application and a **Size**, then click **Create database**.

**2. Connect it to the application**

In the sidebar, click **Applications**, then click the application. Click the **Networking** tab. Under **Connected services**, click **Add internal connection**. Choose `storefront-redis` as the **Service**, turn on **Add environment variables to the application**, and click **Add internal connection**. Sevalla adds `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` and `REDIS_URL`, the names Laravel's `config/database.php` reads. See [Connect an application](https://docs.sevalla.com/databases/connect.md#connect-an-application).

**3. Switch the queue connection**

Click the **Environment variables** tab, then click **Add environment variables**. Add `QUEUE_CONNECTION` with the value `redis`, and click **Add environment variables**.

Horizon talks to Redis with the `redis` PHP extension (phpredis), Laravel's default client, which Ingot installs for Laravel.

## Run Horizon on Sevalla

**1. Create the background worker**

On the application's **Processes** tab, click **Create process**. Choose **Background worker**, enter `Horizon` as the **Name** and this **Custom start command**:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
exec php artisan horizon
```

Choose **S1** as the **Resource**, keep the instances at 1, and click **Create process**.

**2. Deploy**

Click **Deploy** in the undeployed changes bar at the bottom of the page.

![The Create process sheet with Background worker selected, the Name and Custom start command fields, the resource and the scaling settings](https://assets.sevalla.com/screenshots/light/app-processes-worker-create.png)

The deployment's log shows `🚀 Horizon has been deployed successfully.`, and the worker's runtime logs show `Horizon started successfully.` Horizon replaces `php artisan queue:work`: if the application has a queue worker from [Run Laravel queues](https://docs.sevalla.com/guides/php/laravel/queues.md), delete it, so jobs run only under Horizon. Horizon scales its own worker processes, so keep the background worker at 1 instance and raise `HORIZON_MAX_PROCESSES` and the size instead.

### Stop Horizon cleanly on deployments

Sevalla runs a custom start command with `/bin/sh -l -c`. `exec` replaces that shell with Horizon, so Horizon receives the `SIGTERM` that Sevalla sends when it stops an instance during a deployment: it tells each worker process to finish its job, logs `Worker STOPPED Interrupted` for each, and exits when they have. In a test, a job that works for 20 seconds had run for 3 when Horizon received `SIGTERM`: it ran to the end, logged `20s DONE`, and then Horizon stopped. The signal does end PHP's `sleep()` early, so a job that waits with `sleep(20)` returned after 4 seconds instead; wait in a loop that checks the time if the wait matters.

Sevalla sends `SIGTERM` 5 seconds after the shutdown begins and stops the instance 30 seconds after it began, so a job has about 25 seconds to finish. A job that's still running then stays reserved in Redis and fails with `has been attempted too many times.` once its `retry_after` has passed. Without `exec`, the shell receives the signal and ignores it, and every running job is cut off at 30 seconds. See [Stop the worker cleanly on deployments](https://docs.sevalla.com/guides/php/laravel/queues.md#stop-the-worker-cleanly-on-deployments).

## Size the Horizon worker

Horizon runs a master process, a supervisor and the worker processes in one instance. Each PHP process loads the application, and the instance's memory grows with the number of worker processes, measured on Sevalla with jobs that use little memory of their own:

| Worker processes | Memory of the instance |
| - | - |
| 1, while the queue is empty | about 105 MB |
| 5 | about 260 MB |
| 10, the default `maxProcesses` | about 440 MB |

**S1** (1 GB) fits the default of 10. On **H1** (0.3 GB), a busy queue makes Horizon start more worker processes than 0.3 GB holds: Sevalla stops the instance for running out of memory and starts it again, Horizon scales up again to work through the same queue, and the instance restarts in a loop. The jobs that were running fail with `has been attempted too many times.` To run Horizon on **H1**, add the `HORIZON_MAX_PROCESSES` environment variable with the value `5`, and deploy. Jobs that load more data need more memory per process: check the instance's memory on the application's **Analytics** tab while the queue is busy.

## Open the dashboard

The dashboard asks for the email and password of a user in the `users` table whose email is in the `viewHorizon` gate. Create the user once:

1. Click the **Web terminal** tab, and in the shell that opens in the **Web** process, create the user with a password of your own:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   php artisan tinker --execute 'App\Models\User::create(["name" => "Ops", "email" => "ops@example.com", "password" => "a-long-random-password"]);'
   ```

   The `User` model hashes the password when it saves it.
2. Open `/horizon` on the application's domain, for example `https://storefront-x7k2m.sevalla.app/horizon`. The browser asks for a username and password: enter the email and the password. The dashboard shows the status **Active**.

A visitor without credentials gets `401`, and a signed-in user whose email isn't in the gate gets `403`. Basic authentication sends the password with every request, which Sevalla's HTTPS protects.

## Update the Dockerfile

If the application builds from the [Dockerfile](https://docs.sevalla.com/guides/php/laravel/deploy.md#deploy-with-a-dockerfile) in the deploy guide, install the `pcntl` extension, which Horizon needs to start and to stop cleanly, and the `redis` extension for the Redis connection. Ingot installs both on its own. `posix`, which Horizon also needs, is part of the PHP image. Change the first `RUN` line:

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

Without `pcntl`, the build stops at `composer install` with `laravel/horizon v5.50.1 requires ext-pcntl * -> it is missing from your system. Install or enable PHP's pcntl extension.` The `ldd` line finds the libraries the `redis` extension loads, so the final stage installs them with no other change. The start command is the same with both build strategies.

## Verify

1. When the deployment's status is **Deployed**, click the **Web terminal** tab, and in the shell that opens in the **Web** process, queue a job:

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

2. Click the **Logs** tab and look at the `Horizon` process's runtime logs. They show the job running, the line it logs, and `DONE`:

   ```text Runtime logs theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
     2026-10-30 08:18:33 App\Jobs\SendOrderConfirmation ................. RUNNING
   [2026-10-30 08:18:33] production.INFO: Sending the confirmation for order 42
     2026-10-30 08:18:33 App\Jobs\SendOrderConfirmation ............. 6.30ms DONE
   ```

3. Open the dashboard as in [Open the dashboard](#open-the-dashboard), and click **Completed Jobs**: the job is listed there.

If no job runs, check that `QUEUE_CONNECTION` is `redis`: with the deploy guide's `database` queue, jobs wait in PostgreSQL, where Horizon doesn't look. Jobs that failed are under **Failed Jobs** in the dashboard.

### Check from a terminal

These checks need no dashboard. `API`, `H`, `APP_ID`, `WEB_ID` and `HORIZON_PASSWORD` are set as in [Deploy with the API](#deploy-with-the-api).

1. Horizon's status, from the dashboard's own API with the user's credentials:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s -u "ops@example.com:$HORIZON_PASSWORD" https://storefront-x7k2m.sevalla.app/horizon/api/stats | jq -r .status
   ```

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

2. The dashboard without credentials:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s -o /dev/null -w '%{http_code}\n' https://storefront-x7k2m.sevalla.app/horizon
   ```

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

3. Queue a job in the web process with [Run a command in a process](https://api-docs.sevalla.com/v3/applications/run-a-command-in-a-process) (`execProcessCommandV3`), the API's counterpart of the web terminal:

   ```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\\Jobs\\SendOrderConfirmation::dispatch(42);"], "timeout": 30}' \
     | jq -c .
   ```

   ```json theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   {"stdout":"","stderr":"","exit_code":0}
   ```

4. The line the job logs, from [Get application runtime logs](https://api-docs.sevalla.com/v3/applications/get-application-runtime-logs) (`getApplicationRuntimeLogsV3`), after waiting 10 seconds for the log to arrive:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   sleep 10
   curl -s "$API/applications/$APP_ID/runtime-logs?search=order%2042" -H "$H" | jq -r '.[].message'
   ```

   ```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   [2026-10-30 08:18:33] production.INFO: Sending the confirmation for order 42
   ```

CLI: `sevalla apps processes exec $WEB_ID --app-id $APP_ID --timeout 30 -- php artisan tinker --execute 'App\Jobs\SendOrderConfirmation::dispatch(42);'`, then `sevalla apps logs runtime $APP_ID --search "order 42"`. MCP: `sevalla_api_update` with `execProcessCommandV3`, and `search_runtime_logs`.

## Troubleshoot

| Symptom | Cause | Fix |
| - | - | - |
| The `Horizon` process restarts over and over while jobs wait, and jobs fail with `App\Jobs\SendOrderConfirmation has been attempted too many times.` | Horizon started more worker processes than the size has memory for, and Sevalla stopped the instance. | Set `HORIZON_MAX_PROCESSES` as in [Size the Horizon worker](#size-the-horizon-worker), or choose a larger size, then deploy. |
| A Dockerfile build fails at `composer install` with `requires ext-pcntl * -> it is missing from your system.` | The image has no `pcntl` extension, which `laravel/horizon` requires. | Add `pcntl` to the `install-php-extensions` line, as in [Update the Dockerfile](#update-the-dockerfile). |
| `/horizon` returns `403` after you sign in | The user's email isn't in the `viewHorizon` gate. | Add the email to the gate in `app/Providers/HorizonServiceProvider.php`, then deploy. |
| Jobs stay in the queue and the dashboard shows **Inactive** | The `Horizon` process isn't running, or `QUEUE_CONNECTION` isn't `redis`. | Check the process on the **Processes** tab and its runtime logs, and set `QUEUE_CONNECTION` to `redis`. |

## Deploy with the API

The same steps as [Run Horizon on Sevalla](#run-horizon-on-sevalla), as calls to the [Sevalla API](https://docs.sevalla.com/developer-tools/api.md). `APP_ID`, `CLUSTER_ID`, `WEB_ID`, `H1_ID` and `DB1_ID` are the application, its location, its web process and the **H1** and **DB1** sizes 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 Horizon changes first: the deployment builds the branch as it is.

**1. Look up the Redis version**

[List database versions](https://api-docs.sevalla.com/v3/resources/list-database-versions) (`listDatabaseVersionsV3`) gives the newest Redis version in the location. For Valkey, select `valkey` here and in the next step:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REDIS_VERSION=$(curl -s "$API/resources/database-versions?cluster_id=$CLUSTER_ID" -H "$H" \
  | jq -r '.[] | select(.type == "redis") | .versions[-1]')
```

CLI: `sevalla resources db-versions --cluster $CLUSTER_ID --type redis`. MCP: `sevalla_api_read` with `listDatabaseVersionsV3`.

**2. Create the database**

[Create database](https://api-docs.sevalla.com/v3/databases/create-database) (`createDatabaseV3`), then poll [Get database](https://api-docs.sevalla.com/v3/databases/get-database) (`getDatabaseV3`) until it's `ready`:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REDIS_PASSWORD=$(openssl rand -hex 16)
REDIS_ID=$(curl -s -X POST "$API/databases" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg cluster "$CLUSTER_ID" --arg size "$DB1_ID" --arg version "$REDIS_VERSION" --arg password "$REDIS_PASSWORD" \
    '{display_name: "storefront-redis", type: "redis", version: $version, cluster_id: $cluster,
      resource_type_id: $size, db_name: "storefront", db_password: $password}')" \
  | jq -r .id)
for i in $(seq 1 60); do
  DB_STATUS=$(curl -s "$API/databases/$REDIS_ID" -H "$H" | jq -r '.status // empty')
  case $DB_STATUS in ready|error) break ;; esac
  sleep 10
done
echo "$DB_STATUS"
```

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

CLI: `sevalla databases create --name storefront-redis --type redis --db-version $REDIS_VERSION --cluster $CLUSTER_ID --resource-type $DB1_ID --db-name storefront --db-password $REDIS_PASSWORD`. MCP: `create_database`.

**3. Connect it to the application**

[Create internal connection](https://api-docs.sevalla.com/v3/applications/create-internal-connection) (`createApplicationInternalConnectionV3`). It prints the connection's ID; if it prints nothing or `null`, run it again:

```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 db "$REDIS_ID" '{target_id: $db, target_type: "database"}')" | jq -r .id
```

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

**4. Add the environment variables**

[Create environment variable](https://api-docs.sevalla.com/v3/applications/create-environment-variable) (`createEnvironmentVariableV3`), once per variable: the connection's variables in the dashboard's names and format, then `QUEUE_CONNECTION`, all for runtime only:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
REDIS_HOST=$(curl -s "$API/databases/$REDIS_ID" -H "$H" | jq -r .internal_hostname)
REDIS_PORT=$(curl -s "$API/databases/$REDIS_ID" -H "$H" | jq -r .internal_port)
for pair in "REDIS_HOST=$REDIS_HOST" "REDIS_PORT=$REDIS_PORT" "REDIS_PASSWORD=$REDIS_PASSWORD" \
  "REDIS_URL=redis://:$REDIS_PASSWORD@$REDIS_HOST:$REDIS_PORT/0" "QUEUE_CONNECTION=redis"; 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
```

CLI: `sevalla apps env-vars create $APP_ID --key QUEUE_CONNECTION --value redis`, once per variable, for build and runtime. MCP: `set_environment_variables` with `runtime: true` and `buildtime: false`.

**5. Create the Horizon worker**

[List process resource types](https://api-docs.sevalla.com/v3/resources/list-process-resource-types) (`listProcessResourceTypesV3`) gives the ID of **S1**, and [Create process](https://api-docs.sevalla.com/v3/applications/create-process) (`createProcessV3`) with `type` `worker` needs the size and a `scaling_strategy`. Keep `exec` in the start command:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
S1_ID=$(curl -s "$API/resources/process-resource-types" -H "$H" | jq -r '.[] | select(.name == "s1") | .id')
HORIZON_ID=$(curl -s -X POST "$API/applications/$APP_ID/processes" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg size "$S1_ID" \
    '{display_name: "Horizon", type: "worker", entrypoint: "exec php artisan horizon", resource_type_id: $size,
      scaling_strategy: {type: "manual", config: {instanceCount: 1}}}')" \
  | jq -r .id)
```

CLI: `sevalla apps processes create $APP_ID --name Horizon --type worker --resource-type $S1_ID --command "exec php artisan horizon"`. MCP: `sevalla_api_create` with `createProcessV3` and the same body.

**6. Deploy**

[Trigger deployment](https://api-docs.sevalla.com/v3/applications/trigger-deployment) (`triggerDeploymentV3`):

```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`.

**7. 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`.

**8. Create the dashboard user**

[Run a command in a process](https://api-docs.sevalla.com/v3/applications/run-a-command-in-a-process) (`execProcessCommandV3`) creates the user in the web process, with a new password that you keep for the checks:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
HORIZON_PASSWORD=$(openssl rand -hex 16)
curl -s -X POST "$API/applications/$APP_ID/processes/$WEB_ID/exec" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg password "$HORIZON_PASSWORD" '{command: ["php", "artisan", "tinker", "--execute",
    "App\\Models\\User::create([\"name\" => \"Ops\", \"email\" => \"ops@example.com\", \"password\" => \"\($password)\"]);"], timeout: 30}')" \
  | jq -c .exit_code
```

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

CLI: `sevalla apps processes exec $WEB_ID --app-id $APP_ID --timeout 30 -- php artisan tinker --execute '...'`. MCP: `sevalla_api_update` with `execProcessCommandV3`.

Then run the checks in [Check from a terminal](#check-from-a-terminal), with `https://$APP_HOST` as the domain. To build from the [Dockerfile](#update-the-dockerfile), send `{"build_type": "dockerfile", "dockerfile_path": "Dockerfile", "docker_context": "."}` to [Update application](https://api-docs.sevalla.com/v3/applications/update-application) (`updateApplicationV3`) before you deploy.


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