Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Laravel Envoy for Remote Task Automation

By Kokil Thapa | Last reviewed: September 2026

You deploy a Laravel 13 application, run migrations, clear caches, and restart PHP-FPM—then repeat the same five SSH commands on three servers every release. Laravel Envoy for remote task automation replaces that copy-paste routine with a version-controlled task file you run from your laptop or CI runner. Envoy is not a full deployment platform like Deployer; it is a lightweight SSH task runner with Blade-style syntax that fits teams who want readable automation without standing up a pipeline first. This guide covers installation, real Envoy.blade.php patterns, production gotchas I've hit on client servers, and when Envoy beats—or loses to—Deployer and GitLab CI.

What is Laravel Envoy and how does remote task automation work?

Envoy ships as a standalone Composer package. It reads a task definition file—conventionally Envoy.blade.php at your project root—and executes named tasks on remote hosts over SSH. Unlike Laravel scheduled tasks in production, which run inside the application on a cron schedule, Envoy runs from your local machine or a build server and pushes commands outward.

The mental model is simple: you declare servers, define tasks as shell blocks, and optionally chain tasks into stories. Envoy handles parallel execution across servers, confirmation prompts, and variable interpolation. For teams already comfortable with modern Laravel architecture, Envoy feels familiar because the syntax mirrors Blade.

Laravel Envoy Remote Task FlowDeveloperruns envoy runEnvoy.blade.php@task definitionsSSH Layerkey-based authProduction Webgit pull, migrateQueue Workerrestart supervisorStaging Servercache clearParallel execution across @serversOne command deploys to all targets
Laravel Envoy for remote task automation: one local command fans out to multiple SSH targets in parallel.

Envoy sits between ad-hoc shell scripts and heavyweight orchestration. You get structured, named tasks without Kubernetes, without writing a custom bash wrapper, and without committing your entire release process to a SaaS CI vendor on day one. On projects where I also maintain build automation pipelines, Envoy often handles the "last mile" SSH steps that CI cannot reach without extra configuration.

Core directives you will use daily

  • @servers — maps logical names to SSH connection strings (user@host:port).
  • @setup — PHP block executed once before tasks; ideal for reading .env values or setting branch names.
  • @task — a named unit of shell commands run on one or more servers.
  • @story — chains multiple tasks in order, with optional confirmation between steps.
  • @finished — hook that runs after a story completes, useful for Slack notifications.

Envoy is officially documented in the Laravel Envoy documentation. It works with any PHP project that has Composer—not only Laravel—though Laravel teams adopt it fastest because the Blade-like syntax matches their daily workflow.

How do you install and configure Laravel Envoy for production servers?

Install Envoy globally or per-project. For Laravel 13 on PHP 8.3 or higher, both approaches work; I prefer a project-level dev dependency so the whole team runs the same Envoy version via Composer 2.10.

Step 1: Install the package

composer require laravel/envoy --dev

Or install globally:

composer global require laravel/envoy
export PATH="$PATH:$HOME/.composer/vendor/bin"

Verify:

./vendor/bin/envoy --version

Step 2: Create Envoy.blade.php

Place this file at your repository root alongside composer.json:

@servers(['web' => 'deploy@203.0.113.10', 'worker' => 'deploy@203.0.113.11'])

@setup
    $repository = 'git@github.com:your-org/your-app.git';
    $appDir = '/var/www/your-app';
    $branch = $_ENV['DEPLOY_BRANCH'] ?? 'main';
@endsetup

@task('ping', ['on' => 'web'])
    hostname && php -v
@endtask

Run a smoke test before touching production:

./vendor/bin/envoy run ping

Step 3: Configure SSH for unattended runs

Envoy uses your system's SSH client. Production automation fails when keys are missing, passphrases prompt interactively, or known_hosts rejects new fingerprints. Fix this before writing deploy tasks.

  1. Generate a deploy key: ssh-keygen -t ed25519 -C "deploy@your-app" -f ~/.ssh/your_app_deploy
  2. Add the public key to the server's ~deploy/.ssh/authorized_keys.
  3. Create a host alias in ~/.ssh/config with IdentityFile pointing to that key.
  4. Pre-populate known_hosts: ssh-keyscan -H 203.0.113.10 >> ~/.ssh/known_hosts

For CI runners, inject the private key as a masked variable and write it to disk at pipeline start—same pattern used in build pipeline automation best practices. On Ubuntu 22/24 servers I administer through Linux system administration services, I restrict the deploy user with sudo rules only for systemctl reload php8.3-fpm and supervisorctl restart, never full root.

Envoy SSH Setup Checklist1. Deploy Keyed25519, no passphrase2. SSH ConfigHost alias + IdentityFile3. known_hostsssh-keyscan preloaded4. Server Userlimited sudo for FPM reload5. Smoke Testenvoy run ping firstCommon failure: wrong PHP binary in PATHUse full path: /usr/bin/php8.3 artisan
Production Laravel Envoy setup requires SSH keys, host aliases, and explicit PHP binary paths before deploy tasks run reliably.

How do you write Envoy tasks for common Laravel deployment workflows?

A realistic Laravel deploy story pulls code, installs dependencies, runs migrations, rebuilds caches, and reloads PHP-FPM so opcache picks up changes—a pattern I repeat across Laravel booking platforms and legal-tech portals. Here is a production-oriented Envoy.blade.php for a single-server symlink-free layout:

@servers(['web' => 'production'])

@setup
    $appDir = '/var/www/myapp/current';
    $php = '/usr/bin/php8.3';
@endsetup

@task('deploy', ['on' => 'web', 'confirm' => true])
    cd {{ $appDir }}

    git fetch origin
    git reset --hard origin/{{ $branch }}

    {{ $php }} /usr/local/bin/composer install \
        --no-dev --prefer-dist --no-interaction --optimize-autoloader

    {{ $php }} artisan migrate --force
    {{ $php }} artisan config:cache
    {{ $php }} artisan route:cache
    {{ $php }} artisan view:cache
    {{ $php }} artisan event:cache

    sudo systemctl reload php8.3-fpm
@endtask

@story('release')
    deploy
@endstory

Run the full story:

DEPLOY_BRANCH=main ./vendor/bin/envoy run release

Multi-server patterns

When web and queue workers live on separate boxes, target each role explicitly:

@task('deploy-app', ['on' => ['web-1', 'web-2']])
    cd {{ $appDir }} && git pull origin {{ $branch }}
    {{ $php }} artisan migrate --force
@endtask

@task('restart-workers', ['on' => 'worker'])
    sudo supervisorctl restart laravel-worker:*
@endtask

@story('full-release')
    deploy-app
    restart-workers
@endstory

Envoy runs deploy-app on web-1 and web-2 in parallel, then executes restart-workers on the worker host. For zero-downtime symlink releases—the approach I use with Deployer 7 on sister sites like Notary Kathmandu—point $appDir at the current symlink and keep shared storage/ and .env outside the release directory.

Variables, macros, and conditional tasks

The @setup block is plain PHP. Read environment variables, parse .env, or set defaults:

@setup
    $branch = $_ENV['DEPLOY_BRANCH'] ?? 'main';
    $runMigrations = filter_var(
        $_ENV['RUN_MIGRATIONS'] ?? 'true',
        FILTER_VALIDATE_BOOLEAN
    );
@endsetup

@if ($runMigrations)
@task('migrate', ['on' => 'web'])
    cd {{ $appDir }} && {{ $php }} artisan migrate --force
@endtask
@endif

Use @macro for reusable PHP helpers—handy when you need to resolve the latest Git tag or format a Slack payload. Keep macros small; complex logic belongs in a dedicated Artisan command that Envoy invokes, which keeps tasks testable through php artisan locally.

Integrating with Vite and frontend assets

On servers without Node.js 26 LTS installed—a common constraint on budget hosting—I build assets in CI and commit the compiled public/build directory, then let Envoy pull and cache. If your pipeline runs npm run build via Vite 8.x, add a pre-deploy CI step rather than installing Node on production. See Vite config for Laravel projects for the build side; Envoy handles only the SSH transfer and cache warming on the server.

Envoy @story Release Sequencegit pullfetch + resetcomposer--no-dev installmigrate--force flagartisan cacheconfig route viewFPM reloadopcache flushMaintenance task examplesenvoy run cache-clearenvoy run queue-restartenvoy run backup-dbenvoy run deploy --branch=hotfix
A typical Laravel Envoy release story chains git pull, Composer install, migrations, cache rebuild, and PHP-FPM reload in order.

How does Laravel Envoy compare to Deployer and CI/CD pipelines?

Teams often ask whether Envoy replaces Deployer, GitLab CI, or GitHub Actions. It does not replace all three—it occupies a specific slot. Envoy is a task runner; Deployer is a deployment framework with release management, rollbacks, and shared-directory conventions built in; CI/CD orchestrates tests, builds, and gated promotions across environments.

CriteriaLaravel EnvoyDeployer 7GitLab CI / GitHub Actions
Primary roleSSH task runner with Blade syntaxZero-downtime deployment frameworkFull pipeline: test, build, deploy, notify
Rollback supportManual—write your own taskBuilt-in dep rollbackDepends on deploy strategy
Release directoriesYou implement symlinks yourselfNative symlinked releasesVaries by recipe/script
Learning curveLow—one file, few directivesMedium—recipe PHP + host configMedium—YAML + runner setup
Best fitSolo devs, small teams, maintenance tasksProduction Laravel apps needing atomic deploysTeams needing automated tests before deploy
Runs fromLocal machine or CI SSH stepLocal or CI via dep deployCloud runner with repo access

In practice I combine them. GitLab CI runs PHPUnit, PHPStan, and npm run build; on success it calls dep deploy production for sites that need atomic releases. For smaller WordPress or Laravel sites on shared hosting—common among Laravel development projects in Nepal—Envoy alone is enough: one envoy run deploy from a trusted laptop after manual QA passes.

Deployer's documentation at deployer.org covers rollback and shared directories in depth. If your app serves paying customers and downtime costs money, Deployer or a similar release manager earns its place. If you need to restart queue workers across three servers at 2 AM after a config change, Envoy is the faster tool to write and read.

Envoy vs Deployer vs CI DecisionNeed deployment tool?Simple SSH tasksUse EnvoyZero-downtimeUse DeployerTests + gatesUse CI/CDHybrid pattern (recommended for production Laravel)CI runs tests and builds assetsDeployer handles releases; Envoy handles ad-hoc ops
Choose Laravel Envoy for lightweight remote tasks, Deployer for atomic releases, and CI/CD when automated testing must gate every deploy.

What are common Laravel Envoy mistakes on real production servers?

Envoy fails quietly or loudly depending on the mistake. These are the issues I troubleshoot most often during support and maintenance engagements.

Wrong PHP binary or stale opcache

A server with PHP 8.3 and 8.5 side-by-side will run the wrong version if you call bare php artisan. Always use the full path—/usr/bin/php8.3—matching your FPM pool. After deploy, reload PHP-FPM; otherwise opcache serves old bytecode and your "successful" deploy shows stale code. This is the same class of problem covered in replacing messy crontabs with Laravel scheduling, where the cron entry points at the wrong binary.

Running migrations on every web node

When deploy-app targets multiple web servers, running artisan migrate --force on each causes race conditions. Restrict migrations to one host:

@task('deploy-code', ['on' => ['web-1', 'web-2']])
    cd {{ $appDir }} && git pull origin {{ $branch }}
    {{ $php }} /usr/local/bin/composer install --no-dev -o
@endtask

@task('migrate', ['on' => 'web-1'])
    cd {{ $appDir }} && {{ $php }} artisan migrate --force
@endtask

Missing confirmation on destructive tasks

Add 'confirm' => true to production deploy tasks so Envoy prompts before execution. Skip confirmation only in CI by passing --no-interaction when you intend unattended runs. Destructive one-offs—artisan db:wipe, mass cache flushes during peak hours—deserve their own task name and a human gate.

Secrets in Envoy.blade.php

Never hardcode API keys or database passwords in the task file. Read from the server's existing .env or inject via CI variables at runtime. If you need to validate JSON config during deploy, pipe output through a local JSON formatter tool for debugging—do not echo secrets into Slack notifications from @finished hooks.

Forgetting file permissions after git pull

git pull as the deploy user can leave storage/ and bootstrap/cache/ owned incorrectly, breaking uploads and cache writes. Add an explicit fix:

chmod -R ug+rwx storage bootstrap/cache
chgrp -R www-data storage bootstrap/cache

On legal-tech portals like Court Marriage In Nepal, document upload failures from bad permissions are a support ticket I can prevent with one Envoy line.

No deployment lock

Envoy has no built-in mutex. Two developers running envoy run release simultaneously can interleave git operations. Use a simple flock on the server or restrict deploys to CI only:

@task('deploy', ['on' => 'web'])
    flock -n /tmp/deploy.lock -c '
        cd {{ $appDir }} && git pull origin {{ $branch }}
    ' || exit 1
@endtask

Key Takeaways

  • Install Envoy per-project with composer require laravel/envoy --dev so the team shares one version via Composer 2.10.
  • Configure SSH keys, host aliases, and known_hosts before writing deploy tasks—most Envoy failures are SSH problems, not Laravel problems.
  • Use @story to chain git pull, Composer, migrate, cache, and FPM reload; run migrations on a single server when you have multiple web nodes.
  • Call PHP by full path (/usr/bin/php8.3) on servers with multiple PHP versions installed side-by-side.
  • Pair Envoy with CI for tests and asset builds; use Deployer when you need atomic releases and one-command rollbacks.
  • Add confirmation prompts and flock locks so two operators cannot deploy over each other.

People Also Ask

Does Laravel Envoy work with Laravel 13?

Yes. Envoy is a standalone Composer package and is not tied to a specific Laravel framework version. It works alongside Laravel 13 on PHP 8.3 or higher. Your Envoy.blade.php lives at the project root and invokes standard artisan commands regardless of framework minor version.

Can Laravel Envoy replace GitHub Actions or GitLab CI?

Not entirely. Envoy executes SSH tasks; it does not run your test suite, lint code, or build frontend assets unless you shell out to those commands manually. The practical setup runs tests and Vite builds in CI, then triggers Envoy or Deployer for the SSH deploy step. Envoy replaces repetitive manual SSH sessions, not a full pipeline.

How do you run Laravel Envoy from GitLab CI?

Add a deploy stage that loads an SSH private key, installs Envoy via Composer, and runs vendor/bin/envoy run release --no-interaction. Store the key as a masked CI variable, run ssh-keyscan in before_script, and restrict the deploy key to the target server only. This mirrors the GitLab CI plus Deployer pattern I use on production sites.

Is Laravel Envoy the same as Envoy Proxy?

No. Laravel Envoy is a PHP task runner for SSH automation. Envoy Proxy is a CNCF service mesh and edge proxy for Kubernetes and microservices—they share a name only. If you need service mesh routing, that is a different tool entirely; see infrastructure documentation at envoyproxy.io for the proxy product.

Ship reliable deploys without reinventing your workflow

Laravel Envoy for remote task automation earns its place in any team that still SSHs into servers for releases. Start with a ping task, grow into a full release story, and graduate to Deployer plus CI when atomic rollbacks and automated tests become non-negotiable. The goal is not the fanciest toolchain—it is deploys you can repeat at midnight without guessing which PHP binary or cache command you forgot.

If you want help wiring Envoy, Deployer 7, or GitLab CI into a production Laravel stack—with correct permissions, opcache handling, and SEO-safe deploy practicesweb development services cover the full path from task file to live server. For ongoing ops after launch, hosting and domain setup and custom software development keep the pipeline maintainable. Learn more about my background, review Laravel client portal work, or contact us to audit your current deploy process.

Frequently Asked Questions

Laravel Envoy is a lightweight SSH task runner that reads Envoy.blade.php and executes named shell tasks on remote servers from your laptop or CI runner.

Install it as a project dev dependency with composer require laravel/envoy --dev so the team shares one version via Composer 2.10, or install globally with composer global require laravel/envoy and add the global vendor bin to PATH. Verify with ./vendor/bin/envoy --version. Place Envoy.blade.php at the repository root next to composer.json. Run a smoke test such as ./vendor/bin/envoy run ping before touching production. I prefer per-project installs because pinned versions prevent one developer running different Envoy behavior than CI.

Yes. Envoy is a standalone Composer package, works with Laravel 13 on PHP 8.3 or higher, and runs standard artisan commands from Envoy.blade.php.

No. Envoy runs SSH tasks only; CI runs tests, linting, and Vite builds. Use CI first, then Envoy or Deployer for deploy.

Envoy is an SSH task runner with Blade-like syntax—you implement release directories and rollbacks yourself. Deployer 7 is a deployment framework with native symlinked releases, shared directories, and dep rollback built in. Envoy fits solo devs and small teams running readable deploy scripts from a laptop or CI SSH step. Deployer earns its place when paying customers need atomic releases and one-command rollbacks. In practice I combine GitLab CI for PHPUnit and asset builds with Deployer for atomic sites, and Envoy for lightweight last-mile SSH work like restarting queue workers across multiple hosts.

@servers maps logical names to SSH connection strings like user@host:port. @setup runs PHP once before tasks to read environment variables or set branch names. @task defines a named shell block executed on one or more servers, with optional confirm prompts. @story chains tasks in order. @finished runs after a story completes, useful for Slack notifications. @macro holds small reusable PHP helpers; complex logic belongs in Artisan commands that Envoy invokes. Variables interpolate with Blade-style syntax, which feels familiar if you already write Laravel views daily.

Envoy uses your system SSH client, so most production failures are SSH problems, not Laravel problems. Generate a deploy key with ssh-keygen -t ed25519, add the public key to the server deploy user authorized_keys, and create a host alias in ~/.ssh/config with IdentityFile pointing to that key. Pre-populate known_hosts with ssh-keyscan so fingerprints do not prompt interactively. For CI runners, inject the private key as a masked variable and write it to disk at pipeline start. On Ubuntu 22/24 servers I restrict the deploy user with sudo only for systemctl reload php8.3-fpm and supervisorctl restart, never full root.

Define @servers and a @setup block with $appDir and the full PHP binary path such as /usr/bin/php8.3. Create a deploy @task that cd into the app directory, runs git fetch and reset to your branch, composer install with --no-dev and --optimize-autoloader, artisan migrate --force, rebuilds config, route, view, and event caches, then sudo systemctl reload php8.3-fpm so opcache picks up changes. Chain it in a @story like release. Run DEPLOY_BRANCH=main ./vendor/bin/envoy run release. Add confirm => true on production tasks so Envoy prompts before execution unless CI passes --no-interaction for unattended runs.

No. When deploy-app targets multiple web servers, running artisan migrate --force on each causes race conditions. Split code deployment from schema changes. Run git pull and composer install on all web nodes in parallel, but restrict migrate to a single host such as web-1 only. Envoy makes parallel execution easy across servers, which is convenient for pulling code but dangerous for migrations unless you deliberately isolate that task to one machine. This pattern matches how I deploy Laravel booking platforms and legal-tech portals where two web nodes sit behind a load balancer.

Two common causes appear on servers running multiple PHP versions side by side. First, calling bare php artisan may invoke PHP 8.5 while PHP-FPM serves PHP 8.3, so migrations and caches run against the wrong binary. Always use the full path /usr/bin/php8.3 matching your FPM pool. Second, skipping PHP-FPM reload leaves opcache serving old bytecode even though git pull succeeded. Add sudo systemctl reload php8.3-fpm as the final deploy step. I have seen teams celebrate a green Envoy run while production still showed yesterday's code because opcache was never invalidated.

Envoy has no built-in mutex, so two developers running envoy run release simultaneously can interleave git operations and corrupt a release. Wrap the deploy shell block in flock -n /tmp/deploy.lock -c 'your commands here' || exit 1 on the server. Alternatively restrict deploys to CI only so only one pipeline triggers SSH tasks at a time. Add confirm => true on production tasks as a human gate for destructive one-offs. For sites where downtime costs money, pairing Envoy with Deployer 7 or CI-gated deploys reduces the chance of overlapping operators fighting over the same directory.

Map each role in @servers, for example web-1, web-2, and worker with distinct SSH targets. Create role-specific @task blocks: deploy-app runs on web-1 and web-2 in parallel for git pull and composer install, while restart-workers targets the worker host for sudo supervisorctl restart laravel-worker:*. Chain both in a @story like full-release so Envoy runs web tasks concurrently then proceeds to workers. When you need zero-downtime symlink releases, point $appDir at the current symlink and keep shared storage and .env outside the release directory, similar to the Deployer 7 layout I use on sister sites.

On budget hosting without Node.js 26 LTS on production, build assets in CI with npm run build via Vite 8.x and commit the compiled public/build directory, then let Envoy pull code and warm Laravel caches on the server. Envoy handles SSH transfer and cache rebuild steps only; it does not replace a frontend build pipeline. If your GitLab CI or GitHub Actions runner already produces artefacts, Envoy's deploy task simply pulls the latest commit containing those built files. Installing Node on every production box just for deploy adds maintenance overhead most small teams should avoid.

Never hardcode API keys or database passwords in the task file because it is version controlled. Read secrets from the server's existing .env or inject them via CI variables at runtime. Do not echo sensitive values into Slack notifications from @finished hooks. Configure SSH with dedicated deploy keys rather than personal keys with broad access. Restrict the deploy user's sudo to specific commands like PHP-FPM reload and supervisor restarts. After git pull, fix storage and bootstrap/cache permissions explicitly with chmod and chgrp www-data so document uploads work without leaving directories world-writable.

Choose Envoy when you want version-controlled deploy scripts without standing up a full pipeline first, or when you need quick readable SSH automation such as restarting queue workers across three servers at 2 AM after a config change. Choose Deployer when atomic symlinked releases and dep rollback matter for customer-facing Laravel apps. Choose GitLab CI or GitHub Actions when PHPUnit, PHPStan, and asset builds must gate every deploy automatically. For smaller WordPress or Laravel sites on shared hosting common in Nepal, Envoy alone after manual QA is often enough. Envoy sits between ad-hoc shell scripts and heavyweight orchestration.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: