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 = '[email protected]: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.
Also Read: Top 10 reasons to choose Laravel