Set up CI/CD for your Laravel app with GitHub, Travis, and Deployer

The Laravel ecosystem has great tools for managing automatic deployments: Envoyer, Forge and for serverless deployments there is Vapor. These are easy to use and affordable solutions for automatic deployment of Laravel applications. But if you are developing open source applications, or just looking for a free solution, you can set up Travis CI for continuous integration and continuous deployment. It is free for public GitHub projects. In this article I am going walk you through all the necessary steps from configuring the server through deployer configuration all the way to setting up Travis.

Server configuration

I assume you already have a VPS server, and a you are comfortable working in the linux cli. I also won’t deal with the Nginx configuration, only focusing on deployment. The examples here are using an Ubuntu server.
As a first step create a user for the deployment with disabled password:

sudo adduser --disabled-password deploy

Let’s assume we are deploying the example.com website and it should be deployed to the /var/www/vhosts/example.com directory. Go to the /var/www/vhosts/ and change the owner and the permissions of the example.com directory:

sudo chown -R youruser:deploy example.com
sudo chmod -R 775 example.com

With the above command we made the directory writable for youruser and also for the deploy group (the deploy user is in this group by default). This could be handy if you need to do some modifications to the website manually, for example change values in the .env file.

In the next step we’ll allow the deploy user to reload the php-fpm service. Open the /etc/sudoers:

sudo nano /etc/sudoers 

And add the following line to the file:

deploy ALL=(root) NOPASSWD: /usr/sbin/service php7.2-fpm reload

This allows the deploy user to run only the defined command with sudo without asking for a password. This step is optional, but it is recommended to reload the php-fpm service after the deploy, otherwise sometimes the deployed changes are not instantly visible.

Deployer configuration

Go to the root of your project and install deployer with composer:

composer require deployer/deployer --dev

You could use the /vendor/bin/dep init command to initialize the deployer recipe, but you can just create deploy.php in the root of your project and add the following content:

<?php
 namespace Deployer;

 require 'recipe/laravel.php';

 // Project name
 set('application', 'Example');

 // Project repository
 set('repository', 'https://github.com/your/repository.git');

 // [Optional] Allocate tty for git clone. Default value is false.
 set('git_tty', true);

 // Shared files/dirs between deploys
 add('shared_files', []);
 add('shared_dirs', []);
 // Writable dirs by web server
 add('writable_dirs', []);

 // Hosts
 host('example.com')
     ->set('user', 'deploy')
     ->set('deploy_path', '/var/www/vhosts/example.com');

 // Tasks
 task('build', function () {
     run('cd {{release_path}} && build');
 });

 // [Optional] if deploy fails automatically unlock.
 after('deploy:failed', 'deploy:unlock');

 // Migrate database before symlink new release.
 before('deploy:symlink', 'artisan:migrate');

 task('reload:php-fpm', function () {
     run('sudo /usr/sbin/service php7.2-fpm reload');
 });

 after('deploy', 'reload:php-fpm');

The deployer recipe is quite self explanatory, you should make the following changes to the file:

Set up the url of the repository

 // Project repository
 set('repository', 'https://github.com/your/repository.git'); 

Change the host, user and deploy_path if needed

host('example.com')
     ->set('user', 'deploy')
     ->set('deploy_path', '/var/www/vhosts/example.com');

More details about deployer configuration can be found in deployer’s documentation.

Travis configuration

Let’s assume your project uses PHPUnit as test framework, as usually Laravel projects do. We’ll set up the Travic CI to run all the tests, and if the tests run successfully it will automatically deploy the application.

Connect Travis with GitHub

Before you can use the CI, you’d need to create an account on travis-ci-org. The simplest way to do so is to sign in with GitHub. After you’ve connected your GitHub account with Travis, you can enable travis for your public repositories, by clicking on the “+” beside My Repositories:

And choose the repositories you want to run the CI on:

Install Travis CLI

We will allow the deploy user to log in to our server using its private key, but we don’t want to put the private key into a public repository. We are going to use travis cli to encrypt our private key and the encrypted key would be committed and pushed to the repository. You need ruby for installing and running the Travis CLI:

gem install travis -v 1.8.10 --no-rdoc --no-ri

If you need further information on how to do this, detailed installation instructions can be found here.

Create and encrypt the key

Go to your projects root directory, and generate the key pair by running the ssh-keygen:

ssh-keygen -t rsa -b 4096 -C 'build@travis-ci.org' -f ./deploy_rsa

This creates the private/public key pair. You should NEVER commit the private key to the repository!
Now we can encrypt the private key using the CLI tool, so first log in to travis:

travis login --org

If you have signed it to travis with GitHub, it might ask you for your GitHub credentials, just follow the on screen instructions to login.

Encrypt the private key and add it to Travis environment:

travis encrypt-file deploy_rsa --add

The above command creates the encrypted key file: deploy_rsa.enc and adds the decrypt key as and environment variable to the .travis.yml.

Commit the deploy_rsa.enc to the repository, and delete the unencrypted private key:

rm deploy_rsa

SSH into your server and allow deploy user to login with the previously generated keys by copying the content of the deploy_rsa.pub to the /home/deploy/.ssh/authorized_keys file:

sudo nano /home/deploy/.ssh/authorized_keys

When it is done, the public key can also be deleted from the project:

rm deploy_rsa.pub

Configure the .travis.yml

As a first step we set up travis to run our unit tests by adding the following content to the .travis.yml file

language: php
php:
  - 7.2
before_script:
  - composer self-update
  - composer install --no-interaction
script:
  - vendor/bin/phpunit 

The next step is to decrypt the private key and set up the ssh configuration:

before_deploy:
  - openssl aes-256-cbc -K $encrypted_<put_your_key_here>_key -iv $encrypted_<put_your_key_here>_iv -in deploy_rsa.enc -out /tmp/deploy_rsa -d
  - eval "$(ssh-agent -s)"
  - chmod 600 /tmp/deploy_rsa
  - ssh-add /tmp/deploy_rsa
  - echo -e "HostName example.com\n\tStrictHostKeyChecking no\n\t"User deploy >> ~/.ssh/config 

Now we are ready to set up the deployment, by adding the following lines to the .travis.yml:

deploy:
  skip_cleanup: true
  provider: script
  script: vendor/bin/dep deploy
  on:
    branch: master

We are using the script deploy provider, skipping the cleanup after the build and run deployer on the master branch.

The deploy will only only run if the command(s) in the script section have been finished without error. The deployment is also skipped when the build is running on a pull request. For more information about Travis deployments please visit the Travis deployment documentation page.

If everything went well all the tests should run and the changes should be automatically deployed to your server when you push changes to the master branch.

Conclusion

It is not so straightforward to set up a CI/CD pipeline manually as using Envoyer and Forge, but hopefully this article made it a bit easier for you.

If you have any questions or comments, please let me know in the comments section below.

Special thanks to Nicolas Martignoni, I learned the basics of Travis deployment, and key encryption from his blog post.

Leave a Reply

Your email address will not be published. Required fields are marked *