How to write CI / CD for PHP Laravel with github actions


 This article provides a detailed guide for setting up a simple CI/CD pipeline with Github Actions for your Laravel project. 

However, let’s start by getting familiar with what continuous integration and continuous deployment pipelines are all about.

(if you are already familiar with the concepts, you can head straight to the guide below)

What is CI?

CI stands for Continuous Integration. It’s an automated approach for continuously integrating software development updates with the codebase. It automates the source code’s building, linting, and testing to ensure its validity. Using CI, development teams can discover errors and security problems more quickly and efficiently. Bugs are easier and cheaper to fix in the earlier stage.

Continuous integration places high importance on testing automation to ensure that the application works as expected when new commits are merged.

What is CD?

CD stands for continuous deployment. It’s an automated approach for deploying the software to different environments like development, staging, and production. With continuous deployment, all the steps for deploying a new software version, like pulling the new codebase version from the source control, linting, testing, building, installing dependencies, running database migrations, etc., get fully automated. 

Continuous deployment might also involve automation of infrastructure provisioning and environment setup and configuration.

Making the deployment process consistent, robust, and effortless allows development teams to deploy the updates frequently, which is probably the most important thing for ensuring the effective development of software products.

What is CI/CD Pipeline?

To implement Continuous Integration and Continuous Deployment for your project, you will need to set up pipelines. In simple terms, a pipeline consists of two main parts: a trigger event and a sequence of actions executed after the trigger event happens.

The trigger can be any event, like making a pull request, merging one branch into another, creating a new branch, or pushing a commit to the repository. If needed, you may also define custom trigger endpoints. 

You can already see how executing a particular sequence of actions can be helpful after those kinds of trigger events. For example, you might need to run a test suite, a linter, the build process, the deployment process, send a notification on success or failure, etc. You can fully automate all of that once you implement CI and CD pipelines.

Several tools help us to set up pipelines with their triggers and sequences. Some examples are Jenkins, Github Actions, and Gitlab CI/CD. Of course, there are many more, but let’s concentrate on the ones provided by Github and Gitlab. 

Because both host git repositories, Github Actions, and Gitlab CI/CD natively provide support for the git repository triggers. They are configurable with YML files, where you define the trigger events and describe pipeline actions in a declarative manner. 

The CI/CD tools give options to select the environment the pipeline will run in. For example, you can choose the Ubuntu server, and the pipeline will be executed in an Ubuntu environment. This means that your pipeline can execute Ubuntu shell commands, e.g., ‘docker build -t test-tag .’ to build a docker image and ‘docker push’ to push it to the Docker registry. 

With the Ubuntu server, you can do so much more. Besides building and testing the code, you can, for example, use AWS CLI to communicate with AWS for resource provisioning and configuration. So with the suitable trigger event and the environment, you can automate anything you do manually with the pipeline. 

Now let’s look at how you can set up a simple Continuous Deployment pipeline for your Laravel project with Github Actions.

Configuring a CI/CD pipeline with Github Actions for a Laravel project

Generating SSH key

Let’s start with the SSH key. We have to enable GitHub to access our server. We can achieve this without the SSH key, but this way is safer and less complicated.

So let’s get started.

First of all, we should indeed access the server ourselves through SSH:

ssh root@127.0.0.1

Then we should create an SSH key with the following command:

ssh-keygen

It will ask you several questions here; however, you are recommended to choose default in each case.

This is the command which creates public and private SSH keys. But we will need a public one just yet. To see the latter, you should write the following command in the terminal:

cat ~/.ssh/id_rsa.pub

You should copy the output of the command and then execute the command:

sudo vim ~/.ssh/authorized_keys

Adding SSHkeys on a GitHub

You should add previously created keys in several places on Github. Adding public keys to our profile goes as follows:

  • Go to Settings

  • Find SSH and GDP keys in the Settings

  • Then add a new ssh key

  • We should copy our public key by running cat ~/.ssh/id_rsa.pub and paste it here. The title field is not that important; however, try to make it clear. You might need to add multiple SSH keys in the future.

Now you should go to the repository where you want to set up CI/CD Pipeline.

Switch to the repository settings and find the Secrets tab.

Add several repository secrets:

1) SSH_HOST, which will be our server IP address.

2) SSH_USERNAME, our username (one that we use in the SSH command). 

3) SSH_KEY, which is our private key (do not paste it anywhere else!); You can access your private SSH key by running this command:

cat ~/.ssh/id_rsa

Then copy the entire output of that command. 

Configuring CD pipeline

And now, it’s time to configure the CD pipeline. 

Create two folders in the project: First, create it in the .github project route and put the .workflows in the .github. Next, you should create a file in .workflows. (you can name it whatever you want, but it should be clear). So let’s create the yml file called deploy.yml. 

Write the following configuration in this file:

name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: mirromutth/mysql-action@v1.1
with:
mysql database: laravel-test-db
mysql user: laravel_test_user
mysql password: example
- name: Copy .env
run: cp .env.example .env
- name: Install composer Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Install node dependencies
run: npm ci
- name: Setup Project
run: |
php artisan config:clear
php artisan cache:clear
php artisan key:generate
npm run build
- name: Directory Permissions
run: chmod 755 -R storage bootstrap/cache
- name: Run Unit tests
env:
APP_ENV: testing
DB_CONNECTION: mysql
DB_USERNAME: laravel_test_user
DB_PASSWORD: super_secret
DB_DATABASE: laravel_test_db
run: php artisan test
- name: Deploy to Server
if: ${{ success() }}
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
script_stop: true
script: |
cd apps
cd laravel-example
git pull
npm ci
npm run prod
composer i
php artisan migrate --force

If you use laravel mix instead of vite, you should replace the npm run build with the npm run prod

So let’s explain the process in sections:

First, here is declared which event triggers the Github Actions workflow.

on:
push:
branches: [main]

In this case, the Github Actions workflow will run on the main branch on any push(commit, merge, etc…) event.

runs-on: ubuntu-latest

We tell our job what operating system should be set up with the docker container, which will run the Github Actions workflow.

steps:
- uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: mirromutth/mysql-action@v1.1
with:
mysql database: laravel-test-db
mysql user: laravel_test_user
mysql password: example
- name: Copy .env
run: cp .env.example .env
- name: Install composer Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Install node dependencies
run: npm ci
- name: Setup Project
run: |
php artisan config:clear
php artisan cache:clear
php artisan key:generate
npm run build
- name: Directory Permissions
run: chmod 755 -R storage bootstrap/cache
- name: Run Unit tests
env:
APP_ENV: testing
DB_CONNECTION: mysql
DB_USERNAME: laravel_test_user
DB_PASSWORD: super_secret
DB_DATABASE: laravel_test_db
run: php artisan test

This is a workflow for testing our project.

First, we use a few helping tools:

Next, we declare which commands should run to:

  • Copy .env.example file
  • Install composer packages,
  • Clear Laravel cache, config, create project key and compile assets.
  • Enable laravel to create files in bootstrap/cache, read and delete.
  • Create an SQLite file, which we are going to use while testing
  • Change laravel .env in order to use the current MySQL database with the correct credentials
  • And at last, we indeed run the test suite.
- name: Deploy to Server
if: ${{ success() }}
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
script_stop: true
script: |
cd apps
cd laravel-example
git pull
npm ci
npm run prod
composer i
php artisan migrate --force

And here is the part of the workflow that deploys to the server. appleboy/ssh-action@master is an assistant tool for running SSH commands.

if: ${{ success() }}

First, we ensure that our changes will not be deployed to the server if the test suite fails.

Host, key, and username use the values we added in secrets. Ensure that the variable names you write here are the same ones you wrote in secrets.

Script_stop ensures that if one command fails, it will not execute the other commands. We have written in the script the commands that should be executed on a server line by line. This example shows the commands necessary for the Laravel application to reflect the updates. You can fit this part to your application’s specific needs. 

Then push the changes to deploy.yml to Github, and that’s it. 🎉🎉🎉

If everything goes well, the workflow should run, and the changes should be illustrated on a server.

So if this is so, then congratulations🎉

But still, let’s not stop reading the blog here; I will show you some helpful things below.

When something fails in the workflow, we will receive it in the e-mail. However, we can also check this on Github – go to the repository -> actions tab, and we will see all the workflows. If any of them fails, we can choose them and see the logs. 🙌

For example, this workflow failed because we did not insert a public key on our profile.

After inserting the public key, we can rerun this action by clicking the rerun button.

That’s it. I hope you found those instructions helpful.

No comments:

Post a Comment