Laravel deployments using Envoy – Step by Step Guide

In this article, we'll learn how to use Laravel Envoy to setup zero downtime Laravel deployments on live servers. We'll be covering everything step by step so make sure to follow each step carefully.

Reading Time: 4 minutes

Laravel Envoy is one of the most useful tools in the Laravel ecosystem and I use it almost every day. Nearly every project I work on gets it’s own Envoy script to makes deployments super simple.

Envoy is a task runner, and a simple one at that. It’s not Laravel specific (it’s not even PHP specific) but does make use of the blade templating language as a way to script actions. So Laravel developers will feel immediately comfortable. Although you could use Envoy to write deployment scripts for pretty much any tech you happen to be using, I’m only going to deal with Laravel here. However, I don’t think it would take much to to get it working for anything else.

Also Read: Best Sources to learn Laravel Development

Like other Laravel tools, Envoy is installed using composer and should be easy to set up as part of your CI/CD workflow.

Step#1 – Install Envoy Globally

Run the following command to install envoy globally on your machine

composer global require laravel/envoy

Step#2 – SSH Config

In order to communicate with the live server, you must have SSH keys stored on your local machine which you simply use to SSH into that particular instance. Simple is just to add the ssh details in ~/.ssh/config file which we’ll be using in the next step.

Host deploy-myapp
    HostName hostipaddress
    User username
    UseKeychain yes
    AddKeysToAgent yes
    IdentityFile ~/.ssh/key
    IdentitiesOnly yes

You can skip UseKeychain and AddKeystoAgent if you don’t have passphrase from your SSH Keys.

Step#3 – Configure Envoy.blade.php

Create a file called Envoy.blade.php in the root of your Laravel project & paste the following code for basic tasks. File is pretty straight forward and self explanatory, we’ve already added comments in place for better understanding.

{{-- servers array, deploy-myapp is the ssh hostname added in the config file in the prev step. --}}
@servers(['web'=>'deploy-myapp'])

{{-- add the git repo, specify branch(if you want to target specific branch), app_dir is the directory on your server, however you've configured it.
releases_dir as it'll be used to clone into those directories. --}}
@setup
$repo = 'git@github.com:usernam/myrepo.git';
$branch = "branch";
$app_dir      = '/var/www/mywebsite.com';
$releases_dir =  $app_dir . '/releases';
$release_dir  =  $app_dir . '/releases/' . date('YmdHis');

{{-- we'll be only storing last 3 no of releases and remove all rest. --}}
$keep = 3;

{{-- for permissions --}}
$writable = [
'storage'
];

{{-- shared files/directories --}}
$shared = [
'storage' => 'd',
'.env' => 'f',
];
@endsetup

{{-- define list of tasks to be executed on deploy runner --}}
@macro('deploy',['on'=>'web'])
fetch_repo
run_composer
update_permissions
assets_install
migrate
clean
update_symlinks
@endmacro

{{-- get and clone the repository in the releases directory. --}}
@task('fetch_repo')
[ -d {{ $releases_dir  }} ] || mkdir -p {{ $releases_dir }};
git clone {{ $repo }}  --branch={{ $branch }} {{ $release_dir }};
echo "Repository has been cloned";
@endtask

{{-- copy .env files and run composer install to install the dependencies --}}
@task('run_composer')
cd {{ $release_dir }};
cp .env.stage .env
composer install --prefer-dist --no-interaction;
echo "Composer dependencies have been installed";
@endtask

{{-- configure project files/folder permissions --}}
@task('update_permissions')
@foreach($writable as $item)
    chmod -R 755 {{ $release_dir }}/{{ $item }}
    chmod -R g+s {{ $release_dir }}/{{ $item }}
    chgrp -R www-data {{ $release_dir }}/{{ $item }}
    echo "Permissions have been set for  {{ $release_dir }}/{{ $item }}"
@endforeach
chmod -R ug+rwx {{ $release_dir }}
chgrp -R www-data {{ $release_dir }}
echo "Permissions have been set for  {{ $release_dir }}"
@endtask

{{-- run and build assets using npm --}}
@task('assets_install')
cd {{ $release_dir }};
npm install && npm run production;
@endtask

{{-- migrate database if any new changes --}}
@task('migrate')
php {{ $release_dir }}/artisan migrate --force --no-interaction;
@endtask


{{-- Clean old releases --}}
@task('clean')
echo "Clean old releases";
cd {{ $releases_dir }};
rm -rf $(ls -t | tail -n +{{ $keep }});
@endtask

{{-- update symlinks --}}
@task('update_symlinks')
[ -d {{ $app_dir }}/shared ] || mkdir -p {{ $app_dir }}/shared;

@foreach($shared as $item => $type)

    #// if the item passed exists in the shared folder and in the release folder then
    #// remove it from the release folder;
    #// or if the item passed not existis in the shared folder and existis in the release folder then
    #// move it to the shared folder

    if ( [ -{{ $type }} '{{ $app_dir }}/shared/{{ $item }}' ] && [ -{{ $type }} '{{ $release_dir }}/{{ $item }}' ] );
    then
    rm -rf {{ $release_dir }}/{{ $item }};
    echo "rm -rf {{ $release_dir }}/{{ $item }}";
    elif ( [ ! -{{ $type }} '{{ $app_dir }}/shared/{{ $item }}' ]  && [ -{{ $type }} '{{ $release_dir }}/{{ $item }}' ] );
    then
    mv {{ $release_dir }}/{{ $item }} {{ $app_dir }}/shared/{{ $item }};
    echo "mv {{ $release_dir }}/{{ $item }} {{ $app_dir }}/shared/{{ $item }}";
    fi

    ln -nfs {{ $app_dir }}/shared/{{ $item }} {{ $release_dir }}/{{ $item }}
    echo "Symlink has been set for {{ $release_dir }}/{{ $item }}"
@endforeach

ln -nfs {{ $release_dir }} {{ $app_dir }}/current;
chgrp -h www-data {{ $app_dir }}/current;
echo "All symlinks have been set"
@endtask

Also Read: Roles & Permissions in Laravel

Step#4 – Fix Directory Path on Server

Until now, your root directory path in apache/nginx configurations files would be something like /var/www/mywebsite.com/public. This we need to change to /var/www/mywebsite.com/current/public as it should be pointing to the current release.

Step#5 – Deploy

Now that we’re all set, just run the following command and you’ll see the deployment being done in no time without running multiple commands manually.

envoy run deploy

Final words

By now you should be able to deploy Laravel application with zero downtime using Envoy. If you have any queries regarding this article, you may leave a comment below or you can also ask us on Twitter.

Comments