skip to content

With Laravel Envoy, how do @servers, @task and @story describe a deploy, and what happens when one command in a task fails?

level: middleimportance: nice to knowfreq 18%

answer

  1. Envoy.blade.php at the project root
  2. @servers on a single line
  3. @task with on and parallel options
  4. @story groups tasks by name
  5. set -e, stops unless --continue

basics

~20 s

Envoy.blade.php declares @servers, shell @task blocks for named servers, and @story blocks that run tasks in order. Each task runs under set -e, so a failing command fails the task and stops the story unless --continue is passed.

solid answer

~40 s

Envoy is a dev dependency (`composer require laravel/envoy --dev`) that reads `Envoy.blade.php`. `@servers(['web-1' => '[email protected]', 'web-2' => '[email protected]'])`, written on one line, names the hosts. `@task('migrate', ['on' => 'web-1'])` holds shell commands; `on` may list several servers, which run one after another unless `'parallel' => true`, and `'confirm' => true` asks before running. `@story('deploy')` lists task names to run in order. `vendor/bin/envoy run deploy --branch=main` runs it and makes `--branch` available as `$branch`. Envoy pipes each task to `bash -se` over SSH with `set -e`, so the first failing command ends that task with a non-zero code; the story then stops with "This task did not complete successfully" unless `--continue` was given. `@before`, `@after`, `@error`, `@success` and `@finished` hooks run PHP locally, not on the servers.

go deeper

for a junior

Recall that Envoy runs shell tasks over SSH from Envoy.blade.php, with @task for commands and @story to group them.

for a middle

Explain on, parallel and confirm, how CLI options become variables, and that set -e plus story-level stopping make failures halt the deploy.

for a senior

Structure stories so one-off steps run on one server, use hooks locally for notifications, and add release directories yourself if you need them.

for a principal

Judge when a thin SSH runner like Envoy is enough and when a platform or pipeline should own deploys instead.

## What Envoy is Laravel Envoy is a small tool for running shell tasks on remote servers over SSH, written with Blade-style syntax. It is installed per project as a dev dependency: ```bash composer require laravel/envoy --dev php vendor/bin/envoy run deploy ``` It runs on Mac and Linux; Windows needs WSL2. Tasks live in `Envoy.blade.php` at the project root. ## The building blocks | Directive | Purpose | |---|---| | `@servers([...])` | names the hosts; must be written on a single line | | `@task('name', [...])` ... `@endtask` | shell commands to run on the servers in `on` | | `@story('name')` ... `@endstory` | a list of task names run in order | | `@setup` ... `@endsetup` | PHP run locally before tasks, for variables | | `@import('path')` | pulls in another Envoy file's tasks and stories | | `@before`, `@after`, `@error`, `@success`, `@finished` | PHP hooks run **locally** around tasks | Task options worth knowing: - `on` — one server name or an array of them; - `parallel` — `true` runs the task on all its servers at once instead of one after another; - `confirm` — `true` asks for confirmation before running, useful for destructive tasks. Command-line options become Blade variables: `vendor/bin/envoy run deploy --branch=main` makes `{{ $branch }}` available inside tasks, and `@if ($branch)` works too. ## A deploy for the ticketing platform ```html @servers(['web-1' => '[email protected]', 'web-2' => '[email protected]']) @story('deploy') install migrate finish @endstory @task('install', ['on' => ['web-1', 'web-2'], 'parallel' => true]) cd /var/www/tickets git pull origin {{ $branch }} composer install --no-dev --no-interaction @endtask @task('migrate', ['on' => 'web-1']) cd /var/www/tickets php artisan migrate --force @endtask @task('finish', ['on' => ['web-1', 'web-2']]) cd /var/www/tickets php artisan optimize php artisan reload @endtask ``` Migrations run on one server only; the other steps run on every web server. ## What happens on failure Envoy sends each task's script to the server as `ssh <host> 'bash -se' << EOF` with `set -e` at the top. Two consequences follow: 1. **Inside a task,** the first command that exits non-zero ends the task on that server; later lines do not run. 2. **Across a story,** `envoy run` stops at the first task that returns a non-zero code and prints "This task did not complete successfully on one of your servers." Remaining tasks do not run unless you pass `--continue`. Afterwards the hooks fire: `@error` after a failed task, `@success` only when everything succeeded, `@finished` always, with the exit code. They run on the machine that invoked Envoy, which is where notification directives such as `@slack` belong. ## Useful options - `--pretend` prints the generated Bash script instead of running it, a quick way to check a story. - `--path` and `--conf` point Envoy at a different file. ## Common mistakes - Splitting `@servers` over several lines; the docs require a single line. - Running migrations in a task that targets every server, so they race each other. - Expecting hooks to run remotely; they run in the local PHP process. - Forgetting that each task starts in a fresh shell, so every task needs its own `cd` into the application directory. ## Where Envoy fits Envoy is a thin runner: it does not provide release directories, rollbacks or health checks by itself. Those come from how you write the tasks.

  • In an Envoy story, why would you mark a task 'parallel' => true, and when should you not?
    Parallel runs the task on every listed server at once, which shortens steps like `composer install` across many web servers. Leave steps that must happen once or in sequence, such as `php artisan migrate --force` or a rolling restart, as serial tasks on the right servers.
  • Where do Envoy's @error and @finished hooks run?
    Locally, on the machine that ran `vendor/bin/envoy run`, not on the remote servers. Their bodies are PHP; `@finished` receives the exit code, so it is the natural place for notification directives such as `@slack` or `@discord`.

saying these in an interview costs you the question

  • Envoy keeps running later commands after one fails
  • Envoy hooks run on every remote server
  • @story runs its tasks in parallel by default
  • Envoy provides automatic rollback and release directories
  • @servers may span several lines like any PHP array