Deploying your Laravel application with Deployer
Published by Matthew Daly at 22nd January 2018 12:00 pm
Deployment processes have a nasty tendency to be a mish-mash of cobbled-together scripts or utilities in many web shops, with little or no consistency in practice between them. As a result, it's all too easy for even the most experienced developer to mess up a deployment.
I personally have used all kinds of bodged-together solutions. For a while I used Envoy scripts to deploy my Laravel apps, but then there was an issue with the SSH library in PHP 7 that made it impractical to use it. Then I adopted Fabric, which I'd used before for deploying Django apps and will do fine for deploying PHP apps too, but it wasn't much more sophisticated than using shell scripts for deployment purposes. There are third-party services like Deploybot, but these are normally quite expensive for what they are.
A while back I heard of Deployer, but I didn't have the opportunity to try it until recently on a personal project as I was working somewhere that had its own in-house deployment process. It's a PHP-specific deployment tool with recipes for deploying applications built with various frameworks and CMS's, including Laravel, Symfony, CodeIgniter and Drupal.
Installing Deployer
Deployer is installed as a .phar
file, much like you would with Composer:
1$ curl -LO https://deployer.org/deployer.phar2$ mv deployer.phar /usr/local/bin/dep3$ chmod +x /usr/local/bin/dep
With that done, you should be able to run the following command in your project's directory to create a Deployer script:
$ dep init
In response, you should see a list of project types:
1 Welcome to the Deployer config generator2345 This utility will walk you through creating a deploy.php file.6 It only covers the most common items, and tries to guess sensible defaults.78 Press ^C at any time to quit.910 Please select your project type [Common]:11 [0] Common12 [1] Laravel13 [2] Symfony14 [3] Yii15 [4] Yii2 Basic App16 [5] Yii2 Advanced App17 [6] Zend Framework18 [7] CakePHP19 [8] CodeIgniter20 [9] Drupal21 >
Here I chose Laravel as I was deploying a Laravel project. I was then prompted for the repository URL - this will be filled in with the origin remote if the current folder is already a Git repository:
1Repository [git@gitlab.com:Group/Project.git]:2 >
You'll also see a message about contributing anonymous usage data. After answering this, the file deploy.php
will be generated:
1<?php2namespace Deployer;34require 'recipe/laravel.php';56// Configuration78set('repository', 'git@gitlab.com:Group/Project.git');9set('git_tty', true); // [Optional] Allocate tty for git on first deployment10add('shared_files', []);11add('shared_dirs', []);12add('writable_dirs', []);131415// Hosts1617host('project.com')18 ->stage('production')19 ->set('deploy_path', '/var/www/project.com');2021host('beta.project.com')22 ->stage('beta')23 ->set('deploy_path', '/var/www/project.com');242526// Tasks2728desc('Restart PHP-FPM service');29task('php-fpm:restart', function () {30 // The user must have rights for restart service31 // /etc/sudoers: username ALL=NOPASSWD:/bin/systemctl restart php-fpm.service32 run('sudo systemctl restart php-fpm.service');33});34after('deploy:symlink', 'php-fpm:restart');3536// [Optional] if deploy fails automatically unlock.37after('deploy:failed', 'deploy:unlock');3839// Migrate database before symlink new release.4041before('deploy:symlink', 'artisan:migrate');
By default it has two hosts, beta
and production
, and you can refer to them by these names. You can also add or remove hosts, and amend the existing ones. Note the deploy path as well - this sets the place where the application gets deployed to.
Note that it's set up to expect the server to be using PHP-FPM and Nginx by default, so if you're using Apache you may need to amend the command to restart the server. Also, note that if like me you're using PHP 7 on a distro like Debian that also has PHP 5 around, you'll probably need to change the references to php-fpm
as follows:
1desc('Restart PHP-FPM service');2task('php-fpm:restart', function () {3 // The user must have rights for restart service4 // /etc/sudoers: username ALL=NOPASSWD:/bin/systemctl restart php-fpm.service5 run('sudo systemctl restart php7.0-fpm.service');6});7after('deploy:symlink', 'php-fpm:restart');
You will also need to make sure the acl
package is installed - on Debian and Ubuntu you can install it as follows:
$ sudo apt-get install acl
Now, the recipe for deploying a Laravel app will include the following:
- Pulling from the Git remote
- Updating any Composer dependencies to match
composer.json
- Running the migrations
- Optimising the application
In addition, one really great feature Deployer offers is rollbacks. Rather than checking out your application directly into the project root you specify, it numbers each release and deploys it in a separate folder, before symlinking that folder to the project root as current
. That way, if a release cannot be deployed successfully, rather than leaving your application in an unfinished state, Deployer will symlink the previous version so that you still have a working version of your application.
If you have configured Deployer for that project, you can deploy using the following command where production
is the name of the host you're deploying to:
$ dep deploy production
The output will look something like this:
1✔ Executing task deploy:prepare2✔ Executing task deploy:lock3✔ Executing task deploy:release4➤ Executing task deploy:update_code5Counting objects: 761, done.6Compressing objects: 100% (313/313), done.7Writing objects: 100% (761/761), done.8Total 761 (delta 384), reused 757 (delta 380)9Connection to linklater.shellshocked.info closed.10✔ Ok11✔ Executing task deploy:shared12✔ Executing task deploy:vendors13✔ Executing task deploy:writable14✔ Executing task artisan:storage:link15✔ Executing task artisan:view:clear16✔ Executing task artisan:cache:clear17✔ Executing task artisan:config:cache18✔ Executing task artisan:optimize19✔ Executing task artisan:migrate20✔ Executing task deploy:symlink21✔ Executing task php-fpm:restart22✔ Executing task deploy:unlock23✔ Executing task cleanup24✔ Executing task success25Successfully deployed!
As you can see, we first of all lock the application and pull the latest version from the Git remote. Next we copy the files shared between releases (eg the .env
file, the storage/
directory etc), update the dependencies, and make sure the permissions are correct. Next we link the storage, clear all the cached content, optimise our app, and migrate the database, before we set up the symlink. Finally we restart the web server and unlock the application.
In the event you discover a problem after deploy and need to rollback manually, you can do so with the following command:
$ dep rollback production
That makes it easy to ensure that in the event of something going wrong, you can quickly switch back to an earlier version with zero downtime.
Deployer has made deployments a lot less painful for me than any other solution I've tried. The support for rollbacks means that if something goes wrong it's trivial to switch back to an earlier revision.