Introduction: Streamlining Laravel Deployments with Envoy #
Deploying Laravel applications can often be a repetitive and error-prone process. From pulling the latest code and installing dependencies to running database migrations and clearing caches, there are many steps involved. Laravel Envoy offers a simple, elegant solution to automate these tasks, allowing you to run common shell commands on your remote servers with minimal configuration.
Envoy provides a clean, Blade-like syntax to define tasks and macros, making your deployment scripts readable and easy to maintain. Whether you're deploying a small personal project or a large-scale application, Envoy can significantly streamline your workflow and reduce the likelihood of human error.
In this comprehensive tutorial, we'll dive deep into Laravel Envoy, covering its installation, configuration, key features, and a practical deployment example.
What is Laravel Envoy? #
Laravel Envoy is a first-party Laravel package that provides a clean, minimal way to run common tasks on your remote servers using SSH. It allows you to define a set of "tasks" in a single Envoy.blade.php file at the root of your project. These tasks can include anything from deploying your application to running artisan commands or even custom scripts. Envoy then executes these tasks over SSH, providing real-time output in your terminal.
Installation #
Envoy is a global Composer dependency, meaning you install it once on your local development machine (or CI/CD server) and use it across all your Laravel projects.
- Install Envoy via Composer:
composer global require laravel/envoy:"^2.0" - Ensure Composer's global bin directory is in your system's PATH. This allows you to run
envoycommand directly.- For macOS/Linux, add
~/.composer/vendor/binto yourPATHin your shell configuration file (e.g.,.bashrc,.zshrc,.bash_profile). - Example for
~/.bashrcor~/.zshrc:export PATH=$PATH:~/.composer/vendor/bin - After adding, run
source ~/.bashrc(or equivalent) or restart your terminal.
- For macOS/Linux, add
- Verify Installation:
You should see the Envoy version number.envoy --version
Configuration: The Envoy.blade.php File
#
All Envoy tasks are defined within an Envoy.blade.php file at the root of your project. This file uses Blade syntax, making it familiar to Laravel developers.
To get started, create this file:
touch Envoy.blade.php
Let's explore the key directives you'll use within this file.
Defining Servers (@servers)
Before you can run any tasks, you need to tell Envoy which servers to connect to. You define these using the @servers directive.
@servers(['web' => ['[email protected]'], 'db' => ['[email protected]']])
- The key (
web,db) is the server name you'll reference in your tasks. - The value is an array of connection strings. Each string should be in the format
user@host(e.g.,[email protected]). - Envoy uses SSH to connect. Ensure your public SSH key is installed on the remote server for passwordless authentication.
If all your tasks run on the same server, you can define a default server:
@servers(['default' => ['[email protected]']])
Defining Tasks (@task)
Tasks are the core of Envoy. They define a block of shell commands to be executed on your servers.
@task('deploy', ['on' => 'web'])
cd /home/user/your-app
git pull origin master
composer install --no-dev --prefer-dist
php artisan migrate --force
php artisan cache:clear
php artisan config:clear
php artisan view:clear
php artisan optimize
@endtask
@task('task-name', ['on' => 'server-name']): Defines a task namedtask-namethat will run on the server(s) specified byserver-name. Ifonis omitted, it defaults to thedefaultserver.- The commands within the
@taskblock are standard shell commands.
Initializing Tasks (@setup)
The @setup directive allows you to define commands that should run before any other tasks. This is useful for setting up environment variables, navigating to the application directory, or any other preliminary steps.
@setup
$repository = '[email protected]:your-username/your-repo.git';
$releases_dir = '/home/user/your-app/releases';
$app_dir = '/home/user/your-app';
$current_dir = '/home/user/your-app/current';
$php = '/usr/bin/php8.2';
$composer = '/usr/local/bin/composer';
@endsetup
- Variables defined in
@setupare available to subsequent tasks. - Note that the
@setupblock executes locally by default, but you can specify servers just like@task. Usually, it's for local variable definitions.
Grouping Tasks with Stories/Macros (@story)
For complex deployments, you might want to combine several tasks into a single, logical unit. Envoy calls these "stories" (or "macros" in older versions).
@story('full-deploy')
git-pull
composer-install
migrate-database
clear-caches
@endstory
@task('git-pull', ['on' => 'web'])
cd {{ $app_dir }}
git pull origin master
@endtask
@task('composer-install', ['on' => 'web'])
cd {{ $app_dir }}
{{ $composer }} install --no-dev --prefer-dist
@endtask
@task('migrate-database', ['on' => 'web'])
cd {{ $app_dir }}
{{ $php }} artisan migrate --force
@endtask
@task('clear-caches', ['on' => 'web'])
cd {{ $app_dir }}
{{ $php }} artisan cache:clear
{{ $php }} artisan config:clear
{{ $php }} artisan view:clear
{{ $php }} artisan optimize
@endtask
Now, instead of running each task individually, you can run: envoy run full-deploy.
Running Commands Locally (@task('name', ['on' => 'localhost']))
Sometimes you need to run commands on your local machine as part of a deployment process. Envoy supports this by specifying 'on' => 'localhost'.
@task('build-assets', ['on' => 'localhost'])
npm install
npm run prod
@endtask
Finalizing Tasks (@finished)
The @finished directive defines commands that will run on the local machine after all other tasks have completed, regardless of whether they succeeded or failed. This is useful for notifications.
@finished
echo "Deployment tasks completed!"
@endtask
Passing Variables to Tasks
You can pass variables to tasks from the command line:
envoy run deploy --branch=develop
Then, in your Envoy.blade.php:
@task('deploy', ['on' => 'web'])
cd /home/user/your-app
git pull origin {{ $branch ?? 'master' }}
@endtask
The {{ $branch ?? 'master' }} syntax uses Blade's null coalescing operator to provide a default value if the branch variable is not passed.
Advanced Deployment Workflow Example #
Let's consider a more robust deployment strategy using zero-downtime deployment techniques. This involves creating new release directories and symlinking to them.
@servers(['web' => ['[email protected]']])
@setup
$repository = '[email protected]:your-username/your-repo.git';
$app_dir = '/home/user/your-app';
$releases_dir = $app_dir . '/releases';
$current_dir = $app_dir . '/current';
$new_release_dir = $releases_dir . '/' . date('YmdHis');
$php = '/usr/bin/php8.2'; // Adjust to your PHP path
$composer = '/usr/local/bin/composer'; // Adjust to your Composer path
// Ensure storage path is owned by web server user and shared
$storage_link = $app_dir . '/storage'; // This should be a persistent storage
@endsetup
@story('deploy')
clone-repository
run-composer
update-symlinks
migrate-database
clear-caches
activate-new-release
cleanup-old-releases
@endstory
@task('clone-repository', ['on' => 'web'])
echo "Cloning repository..."
mkdir -p {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
cd {{ $new_release_dir }}
git reset --hard {{ $branch ?? 'master' }}
@endtask
@task('run-composer', ['on' => 'web'])
echo "Running Composer..."
cd {{ $new_release_dir }}
{{ $composer }} install --no-dev --prefer-dist --optimize-autoloader
@endtask
@task('update-symlinks', ['on' => 'web'])
echo "Updating symlinks..."
cd {{ $new_release_dir }}
// Link shared storage directory
rm -rf {{ $new_release_dir }}/storage
ln -nfs {{ $storage_link }} {{ $new_release_dir }}/storage
// Link .env file
ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
// Link public/storage
ln -nfs {{ $storage_link }}/app/public {{ $new_release_dir }}/public/storage
@endtask
@task('migrate-database', ['on' => 'web'])
echo "Running database migrations..."
cd {{ $new_release_dir }}
{{ $php }} artisan migrate --force
@endtask
@task('clear-caches', ['on' => 'web'])
echo "Clearing caches..."
cd {{ $new_release_dir }}
{{ $php }} artisan cache:clear
{{ $php }} artisan config:clear
{{ $php }} artisan route:clear
{{ $php }} artisan view:clear
{{ $php }} artisan optimize
@endtask
@task('activate-new-release', ['on' => 'web'])
echo "Activating new release..."
ln -nfs {{ $new_release_dir }} {{ $current_dir }}
{{ $php }} artisan queue:restart || true # Restart queues if applicable
sudo service php8.2-fpm reload # Reload PHP-FPM if using Nginx+PHP-FPM
@endtask
@task('cleanup-old-releases', ['on' => 'web'])
echo "Cleaning up old releases..."
cd {{ $releases_dir }}
ls -dt */ | tail -n +5 | xargs rm -rf # Keep last 4 releases + current
@endtask
@finished
echo "Deployment completed successfully!"
@endtask
Important Note on Storage & .env: In a production environment, your storage directory and .env file should typically exist outside of your release directories and be symlinked into each new release. This ensures persistent data and configuration across deployments.
Running Envoy Tasks #
Once you've configured your Envoy.blade.php file, you can execute tasks from your terminal:
-
Run a specific task:
envoy run task-nameExample:
envoy run clear-caches -
Run a story (macro):
envoy run story-nameExample:
envoy run deploy -
Pass variables:
envoy run deploy --branch=develop
Security Considerations #
- SSH Keys: Always use SSH keys for authentication to your servers. Never hardcode passwords in your
Envoy.blade.phpfile. Ensure your local machine's public SSH key is added to theauthorized_keysfile of the user on the remote server. - Permissions: Use a dedicated deployment user on your server with only the necessary permissions to manage your application directory. Avoid using root.
- Sensitive Information: Do not commit sensitive information (like API keys or passwords) directly into your
Envoy.blade.phpor your repository. Use environment variables on the server (e.g., in the.envfile, which is symlinked).
Conclusion #
Laravel Envoy simplifies and automates the deployment process for your Laravel applications. By defining your deployment steps in a clean, Blade-powered Envoy.blade.php file, you can ensure consistency, reduce errors, and significantly speed up your deployment workflow. Its straightforward syntax and powerful features make it an invaluable tool for any Laravel developer looking to streamline their DevOps practices. Embrace Laravel Envoy and enjoy smoother, more reliable deployments!