Laravel App CI/CD using GitHub Actions

This article is also published on https://engineering.wedevs.com/2020/05/04/laravel-app-ci-cd-using-github-actions/

Continuous integration becomes mandatory when every second of your application uptime is important. By using Continuous Integration (CI) we can test our codes are working or not through PHPUnit. When all of your tests are pass then you need to deploy the application.

Why GitHub Actions?

If your project is in GitHub then it is a great opportunity to use GitHub Actions. GitHub Actions is only for GitHub repositories. Another advantage of GitHub Actions is, it is much easier to set up than Jenkins and Travis CI

Continuous Integration

Let’s start with continuous integration, to do so first go to your GitHub repository and click on Actions menu, you will see something like the below image.

You will see GitHub will suggest a few workflow for you, In GitHub Actions, workflow is a YAML file where you can define each step of your integration. Let’s start with the suggested Laravel workflow. When we click on the Set up this workflow button it will open an editor with laravel.yml file in .github/workflows directory. Here I modify this file a little bit and rename the file to CI.yml, My file is given below.

name: Continuous Integration

on:
  push:
    branches:
      - master
      - develop
      - 'feature/**'

defaults:
  run:
    shell: bash

jobs:
  laravel-tests:
    runs-on: ubuntu-18.04
    env:
      DB_CONNECTION: sqlite
      DB_DATABASE: database/database.sqlite
    steps:
    - uses: actions/checkout@v2
    - name: Copy .env
      run: php -r "file_exists('.env') || copy('.env.example', '.env');"
    - name: Create sqlite database
      run: touch database/database.sqlite
    - name: Install dependencies
      run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist
    - name: Generate key
      run: php artisan key:generate
    - name: Set directory permissions
      run: chmod -R 777 storage bootstrap/cache
    - name: Execute tests (Unit and Feature tests) via PHPUnit
      run: vendor/bin/phpunit

After your modification of the file you have to commit this file via Start commit button. If your are not understand what is this YAML file talking about, you may read the documentation of GitHub Actions workflow file. When you commit your configuration file the integration is done. From now your Laravel application will test automatically on every push of master, develop and feature branches.

Continuous Deployment

The concept is if we push to master branch we will run a new job in the workflow. On the job first we will check all tests are pass or not. If all tests are pass then we will send request to a webhook URL. Here you have to manage the webhook by your own or you may read my article about Add Webhook on Ubuntu. Mainly when the webhook receive the request it run a shell script where it pull the codes from repository and install composer packages on server, also it can run other necessary commands. Our final CI.yml file will be look like below.

name: Continuous Integration

on:
  push:
    branches:
      - master
      - develop
      - 'feature/**'

defaults:
  run:
    shell: bash

jobs:
  laravel-tests:
    runs-on: ubuntu-18.04
    env:
      DB_CONNECTION: sqlite
      DB_DATABASE: database/database.sqlite
    steps:
    - uses: actions/checkout@v2
    - name: Copy .env
      run: php -r "file_exists('.env') || copy('.env.example', '.env');"
    - name: Create sqlite database
      run: touch database/database.sqlite
    - name: Install dependencies
      run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist
    - name: Generate key
      run: php artisan key:generate
    - name: Set directory permissions
      run: chmod -R 777 storage bootstrap/cache
    - name: Execute tests (Unit and Feature tests) via PHPUnit
      run: vendor/bin/phpunit
  deloy:
    if: ${{ github.ref == 'refs/heads/master' }}
    runs-on: ubuntu-18.04
    needs: laravel-tests
    steps:
      - name: Call server webhook
        run: |
          status_code=$(curl -d ${{ secrets.WEBHOOK_DATA }} --silent --show-error --write-out '%{http_code}' --output "/tmp/response.txt" ${{ secrets.WEBHOOK_URL }})
          cat /tmp/response.txt
          if [[ ${status_code} -ne 200 ]]; then exit 1; fi

Here you can see I have used two new keywords github and secrets. These are called Contexts in GitHub Actions. You can read the documentation about what is this and what they can do.

Check Integration log

GitHub run workflows on each commit, so if you to go to Actions menu you will see all of your commits. When you click on a specific commit you will see the log for it. Here I am giving my screenshoots.

When I click on the job it shows log for it.

Run Shell Script

Let’s make the things a little bit cleaner, We can move the cURL command to a shell script. First write the file on bin/deploy.sh

#!/bin/bash

status_code=$(curl -d $1 --silent --show-error --write-out '%{http_code}' --output "/tmp/response.txt" $2)

cat /tmp/response.txt

if [[ ${status_code} -ne 200 ]]; then
    exit 1;
fi

Now update the deploy job on CI.yml file.

  deloy:
    if: ${{ github.ref == 'refs/heads/master' }}
    runs-on: ubuntu-18.04
    needs: laravel-tests
    steps:
      - uses: actions/checkout@v2
      - name: Call server webhook
        run: bash bin/deploy.sh ${{ secrets.WEBHOOK_DATA }} ${{ secrets.WEBHOOK_URL }}

Tips

It is not possible to re-run a successful job, to do so you have to run an empty commit and push it.

git commit --allow-empty -m "Trigger GitHub actions"
git push origin master

Resources