Zaher Ghaibeh
PHP Backend developer
I've experience in a few PHP Frameworks, such as Laravel, Lumen and Slim (The last two are used for building Microservices/API services).
How to test and auto deploy your laravel code from Gitlab
Published at Saturday, February 25, 2017 , Categorized under: Business, Code, docker, Laravel, Linux, MySQL, PHP

Few days ago, I was playing with the new Laravel 5.4 and Gitlab CI, and got to the point where I wanted the CI to test my code once I push it.

Using google lead me to this post on Laracasts, and it was the starting point for me, there you can get important files .gitlab-ci.yml and .gitlab-ci.sh  , basically the first one will define the stages for the testing and the second one will build the environment for your test.

So lets look at a short version of .gitlab-ci.yml

before_script:
  - bash .gitlab-ci.sh

variables:
  MYSQL_DATABASE: project_name
  MYSQL_ROOT_PASSWORD: secret

phpunit:php7.0:mysql5.6:
  image: php:7.0
  services:
    - mysql:5.6
  script:
    - php vendor/bin/phpunit --colors

phpunit:php7.1:mysql5.6:
  image: php:7.1
  services:
    - mysql:5.6
  script:
    - php vendor/bin/phpunit --colors

Looking at the file we can figure out that the before_script will run .gitlab-ci.sh file before any tests and variables define the variables for mysql and the other two is stages for executing the tests on two different php images (you can check Laracasts post which have the full version of the file as they also test it on php 5.x).

But what is image and services ? basically they are docker images which the CI is going to pull for you and link  them together so you can execute your tests, nice right :D, lastly script is the command that you want to execute inside your test image.

With those basics information, you have got a full testing CI environment for your Laravel application, which will be triggered whenever you pushed your code.

What about the auto deploy how we can do that? in Gitlab they have some integration with Docker and kubernetes which you can look at Gitlab documentations, but for me I use simple SSH methods which is nice and simple.

Lets talk about what we should do step by step:

Create Secret Variables:

GitLab CI allows you to define per-project secret variables that are set in the build environment. Those variables stored outside the repository and used within your CI so you dont need to add them as plain text. You can add your secret variables from within your repository Settings -> CI/CD Pipeline, once you are there you can define your secrets like the following images

Once you created them, you can use them in your .gitlab-ci.yml file.

You will need to create two secret variables, one for the SSH and the second for Github Access Token which we will be using for PHP Composer.

Optional, Create your own Docker image:

For me, I try to use any opportunity to learn the things I want to learn, and since the GitlabCI depends on Docker, so why don't I use my Docker knowledge to do something nice, plus am using Docker with PHPStorm so I wanted to use the same Docker image for both the CI and my development environment. The image am going to use is PHP7.1 with xDebug which I built and published on my  Docker Hub.

Create your own .gitlab-ci.yml file:

Our gitlab-ci instruction file will contain mainly four parts:

  1. before_script: In this section we will define the commands which we want to use on each and every stage, before anything else within the stage scripts section.
  2. stages: In this section we will define the order which our system is going to work (Jobs from the same stage will run in parallel).
  3. Testing stage: In this section we tell the CI what we want to execute and which Docker image it should pull.
  4. Deploy Stage: In this section we just define the second job that we want the CI to execute.

So it will looks like the following:

before_script:
  - ' ! -e /.dockerenv  && exit 0'

stages:
  - phpunit
  - deploy

variables:
  MYSQL_DATABASE: homestead
  MYSQL_ROOT_PASSWORD: secret

phpunit:php7.1:mysql5.6:
  stage: phpunit
  only:
    - develop
  image: zaherg/php-7.1-xdebug-alpine:latest
  services:
    - mysql:5.6
  script:
    - sh .gitlab-ci.sh

deploy_develop:
   stage: deploy
   before_script:
     - mkdir -p ~/.ssh
     - echo -e "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
     - chmod 600 ~/.ssh/id_rsa
     - ' -f /.dockerenv  && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
   script:
     - ssh sshuser@$DEV_SERVER "cd ~/www && git pull origin develop && ~/bin/composer install"
   environment:
     name: develop
     url: https://develop.zah.me/
   only:
     - develop

Lets explain it section by section:

before_script:
  - ' ! -e /.dockerenv  && exit 0'

First we need to make sure that we are running inside docker, if not then exit immediately.

stages:
  - phpunit
  - deploy

variables:
  MYSQL_DATABASE: homestead
  MYSQL_ROOT_PASSWORD: secret

Second we defined the two stages we want to work with, and add a Docker variables which will be used with MySQL docker image.

phpunit:php7.1:mysql5.6:
  stage: phpunit
  only:
    - develop
  image: zaherg/php-7.1-xdebug-alpine:latest
  services:
    - mysql:5.6
  script:
    - sh .gitlab-ci.sh

Third we define the testing stage for our Laravel code base, we first make sure to say that this job belongs to the phpunit stage, now since I need to test my code when I push it to develop branch only I specified that on my only section,  then we specify which PHP docker image we want to use, as I said before I use my own images, we specify the service which is linked to our code which is mysql and finally we specify the commands we want to execute in the script  section.

I just grouped all the commands inside .gitlab-ci.sh file which will do everything for me :

  1. configure composer and run it
  2. copy my testing env file to be the main one
  3. run migration and seeds
  4. and finally run phpunit
#!/bin/sh

set -eo pipefail

# Install php libraries.
echo "Start the update and the install"
composer config -g github-oauth.github.com $gitHubKey
composer install --no-interaction --optimize-autoloader

# Copy over testing configuration.
cp .env.testing .env

# Generate an application key. Re-cache.
echo "Run artisan"
php artisan key:generate
php artisan optimize
php artisan config:cache

# Run database migrations.
echo "Run migration & Seeds"
php artisan migrate --seed

# Run the tests.
echo "Run the tests"
php vendor/bin/phpunit --colors

As you can see I used the first secret variable out of the two we said we will define GitHubKey.

Now whenever I push to develop branch, I got the CI to test my code and if it fail I'll get something like this

Which is really really nice thing.

Now we are going to be on our last stage, Auto deploy if the test went green.

deploy_develop:
   stage: deploy
   before_script:
     - mkdir -p ~/.ssh
     - echo -e "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
     - chmod 600 ~/.ssh/id_rsa
     - ' -f /.dockerenv  && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
   script:
     - ssh sshuser@$DEV_SERVER "cd ~/www && git pull origin develop && ~/bin/composer install"
   environment:
     name: develop
     url: https://develop.remoteserver.com/
   only:
     - develop

its really simple, if you read it you will notice that its not different than the previous stage except that we dont pull any Docker image, we only SSH into the remove server and pull the code from gitlab.

Make sure that the Private SSH key that you have added to your secret variables, can access your remote server.

First we define that this job belongs to the deploy stage, then we overwrite the before_script so it will execute the one we define instead of the global on, basically this is what I did :

  1. Make sure that I create the ~/.ssh  folder with in Gitlab Docker image, cause at the end everything run inside a container.
  2. I use the second secret key that we have defined which will contain my the private key to access the remote server.
  3. Make sure that the file is not fully open, SSH will complain if the file is not set as 600 mode.
  4. Check if we are running inside docker and then disabled StrictHostKeyChecking for all the hosts inside ~/.ssh/config

Make sure that you either pulled your code once on the remote server to add gitlab servers to your know_hosts files, if the remote origin was not originally from gitlab.

The environment section will define your environments variable which you will see within your Environment section under the Pipelines section

You are free to create this manually or once you set them inside your gitlab-ci.yml file they will be auto created for you.

Now if you clicked on develop you will see how many times you have sent your code to the server, and even you can rollback to older version of your code and now whenever you pushed to develop your code will be tested and auto deploy without any manual interaction from your side.

And that's all, let me know if you have anything else that you can add to enhance my post.

As always, you may find some lingual mistakes in my post, as english is not my mother language, so if you do and want to help me out, just let me know so I can fix it.