Using Github actions and Deployer for creating CI/CD for Laravel

In one of my previous articles I’ve written about how to create an automated pipeline for continuous integration and continuous delivery using travis. Lately GitHub actions are quite popular, I am also experimenting with them. I started by reading the articles written by the guys from Spatie, you can find them here and here. While setting up the actions I encountered some issues with the mysql service, I’ll dive into it later. Let’s set up the same pipeline as last time. Run the tests for every push and deploy the application if the tests are successful.

TL;DR;

For those who don’t have time to read the whole article, here is the final version of the yaml configuration:

name: Build

on: [push, pull_request]

jobs:
  laravel-tests:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:5.7
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: tracy_test
        ports:
          - 3307:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
    steps:
    - uses: actions/checkout@v1
    - name: Copy .env
      run: php -r "file_exists('.env') || copy('.env.ci', '.env');"
    - 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 up Passport
      run: php artisan passport:keys
    - name: Execute tests (Unit and Feature tests) via PHPUnit
      run: vendor/bin/phpunit

  deploy:
    if: github.ref == 'refs/heads/master'
    needs: [laravel-tests]
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - name: Deploy
      env:
        DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
      run: |
        eval "$(ssh-agent -s)"
        ssh-add - <<< "${DEPLOY_KEY}"
        mkdir ~/.ssh
        echo -e "HostName example.com\n\tStrictHostKeyChecking no\n\t"User deploy >> ~/.ssh/config
        composer install
        vendor/bin/dep deploy
      

Create the workflow

First we need to create a workflow by creating a .yaml file in the .github/workflows directory, for example: .github/workflows/laravel.yml. In this file we’re going to define the runner environment, the jobs and the steps to run.

We are defining the workflow to run on push and pull request events:

on: [push, pull_request]

We’ll have 2 jobs to run laravel-tests and deploy.

Run the tests

Let’s take a look what steps are necessary for running the tests.

Checkout the code

- uses: actions/checkout@v1

Create the .env file. For this step we assume you have and .env.ci file in your project (it should be committed to the repository!)

- name: Copy .env
  run: php -r "file_exists('.env') || copy('.env.ci', '.env');"

Install dependencies with composer

- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist

Generate the application key

- name: Generate key
run: php artisan key:generate

Create passport keys. This step is optional, only necessary if your project is using passport

- name: Set up Passport
run: php artisan passport:keys

And the last step, run the tests

- name: Execute tests (Unit and Feature tests) via PHPUnit
run: vendor/bin/phpunit

For testing you can use sqlite database which is very quick but does not have all the features as other databases such as MySQL. For my use case I am using the mysql service because the project uses spatial data types.

When I first time set up the mysql service, according to the articles I’ve read and according to the documentation, I got the following error when running the tests: SQLSTATE[HY000] [1049] Unknown database. After some experimenting, I realized that the GitHub runners come with preinstalled software (you can find the list here). They also have mysql preinstalled, but when you define the mysql service, it starts a mysql docker container.

I came up with two solutions:

  • Create the test database in the preinstalled mysql server:
- name: Create database
run: mysql -uroot -proot -e 'create database tracy_test;'
  • Use a different port for the docker service, and define the database in the service definition
services:
mysql:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: tracy_test
ports:
- 3307:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

Please note the ports section it cannot run on the same port as the built in mysql service, so it is using 3307 for the mysql. In this case you’d need to set up the .env.ci accordingly:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_DATABASE=tracy_test
DB_USERNAME=root
DB_PASSWORD=root

If you have other solution(s) for the mysql service problem, please let me know in comments.

Deploy

I am big fan of deployer, although there are plenty of other solutions for deployment like AWS, Docker etc. For my personal/small scale projects deployer just works well. I won’t go into the details of the server side setup, I did it in a previous article, if you are interested please check it here.

We need to allow the login from GitHub actions to our server. Generate a private/public key pair, add the public key to the authorized_keys of your deploy user on the server. Add the private key as a secret to your repository, you can find it under Settings/Secrets:

We only want to deploy form the master branch, and only if all the test have passed.

deploy:
if: github.ref == 'refs/heads/master'
needs: [laravel-tests]

The needs key ensures that the deploy only runs when the tests passed.

Add the DEPLOY_KEY to the env variables

- name: Deploy
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

Set up the ssh agent to use your key for login

run: |
  eval "$(ssh-agent -s)"
  ssh-add - <<< "${DEPLOY_KEY}"
  mkdir ~/.ssh
  echo -e "HostName example.com\n\tStrictHostKeyChecking no\n\t"User deploy >> ~/.ssh/config

Run composer install and deploy the project:

composer install
vendor/bin/dep deploy

If everything goes well the tests run and the project gets deployed automatically after each push on the master branch.

So far the GitHub actions seem to work well, probably I’ll use it for CI/CD related tasks for all my projects in the future.

If you have any suggestions or problem setting this up, please feel free to share them in comments.

2 Replies to “Using Github actions and Deployer for creating CI/CD for Laravel”

  1. I have no idea which is the best, but it also works this way:

    “`
    – name: Run Migration
    run: php artisan migrate -v
    env:
    DB_PORT: ${{ job.services.mysql.ports[‘3306’] }}
    “`

Leave a Reply

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