Site logo
Published on

Automating Laravel CI/CD with GitHub Actions

Authors
  • avatar Nguyen Duc Xinh
    Name
    Nguyen Duc Xinh
    Twitter

In modern software development, continuous integration and continuous deployment (CI/CD) have become essential practices. Automating the testing and deployment processes ensures faster and more reliable software delivery. If you're working with Laravel and hosting your code on GitHub, GitHub Actions provides a powerful CI/CD solution.

In this guide, we'll walk through setting up a GitHub Actions workflow for a Laravel application, including dynamically updating the Laravel .env file with MySQL credentials stored as GitHub Secrets.

Prerequisites

Before we begin, make sure you have the following:

GitHub Actions Workflow CI(Continuous Integration)

1. Create CI Workflow

Now, let's create a GitHub Actions workflow file that automates the testing process. Create a .github/workflows/laravel-ci.yml file with the following content:

cd /path/to/your/project
mkdir -p .github/workflows
vim .github/workflows/laravel-ci.yml
name: Laravel CI

on:
  pull_request:
    branches:
      - main
      - develop
      - staging

jobs:
  build:
    name: CI
    runs-on: ubuntu-latest
    env:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel-user
      MYSQL_PASSWORD: secret

    
    services:
      mysql:
        image: mysql:8
        env:
          MYSQL_ROOT_PASSWORD: ${{ env.MYSQL_ROOT_PASSWORD }}
          MYSQL_DATABASE: ${{ env.MYSQL_DATABASE }}
          MYSQL_USER: ${{ env.MYSQL_USER }}
          MYSQL_PASSWORD: ${{ env.MYSQL_PASSWORD }}
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.2

      - name: Install dependencies
        run: composer install
    
      - name: Install Frontend dependencies
        run: npm install

      - name: Build Frontend
        run: npm run build

      - name: Copy environment file
        run: cp .env.example .env
      - name: Update Laravel .env
        run: |
          echo "DB_CONNECTION=mysql" >> .env
          echo "DB_HOST=127.0.0.1" >> .env
          echo "DB_PORT=3306" >> .env
          echo "DB_DATABASE=${{ env.MYSQL_DATABASE }}" >> .env
          echo "DB_USERNAME=${{ env.MYSQL_USER }}" >> .env
          echo "DB_PASSWORD=${{ env.MYSQL_PASSWORD }}" >> .env

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

      - name: Run database migrations
        run: php artisan migrate

      - name: Validate Format code
        run: ./vendor/bin/pint

      - name: Run tests
        run: vendor/bin/phpunit

Understanding the Workflow
  • on.pull_request.branches:: This workflow is triggered on each pull requet to the main/staging/develop branch.
  • jobs.build: build is job name. You can rename to an another name
  • jobs.build.runs-on[ubuntu-latest]. We specify that this workflow will run on ubuntu latest version
  • jobs.build.env:: Declare some environment variables to use in this workflow.
  • jobs.build.services.mysql: We use Containerized service Mysql to connect database to this workflow. More detail Containerized service
  • jobs.build.steps[Checkout code]: This is action checking out the repo. More detail actions/checkout@v2
  • jobs.build.steps[Setup PHP]: This is action to set up PHP. You can add parameter in with to specify php version, composer version, add extensions, php.ini configuration, etc.. . More detail shivammathur/setup-php@v2

2. Demo CI Workflow

  1. Create a new branch: feature/setup-ci-cd
  2. Add somme code changes
  3. Push code to Github
  4. Create a pull request from branch feature/setup-ci-cd to branch develop
  5. Check the list of running workflows . In this tutorial you can navigate to this link https://github.com/ducxinh/laravel-sample/actions laravel-ci-with-github-action-001
  6. Check the detail of running workflow. You should see that the workflow executed successfully laravel-ci-with-github-action-002

GitHub Actions Workflow CD(Continuous Deployment)

1. Generate deployment key

ssh-keygen -t rsa -b 4096 -f ~/.ssh/awesome-laravel-deployment -P ""
cat ~/.ssh/awesome-laravel-deployment.pub
# Copy the public key and add to server

More info for SSH keys: https://www.ssh.com/ssh/public-key-authentication

2. Add deployment key to server

Add deployment key to server by add the public key to ~/.ssh/authorized_keys file on the server

# Connect Server
ssh -i awesome-laravel-web.pem ubuntu@35.77.229.224
ssh -i /path/to/your-key.pem ubuntu@your-instance-public-ip

# Add public key to ~/.ssh/authorized_keys
sudo vim ~/.ssh/authorized_keys

3. Verify the connection to the server ok.

In this tutorial we use file awesome-laravel-deployment

ssh -i ~/.ssh/awesome-laravel-deployment ubuntu@your-instance-public-ip

4. Create CD Workflow

Let's automate the deployment process by creating a GitHub Actions workflow. Craft a .github/workflows/laravel-cd-dev.yml file with the provided content.

cd /path/to/your/project
mkdir -p .github/workflows
vim .github/workflows/laravel-cd-dev.yml

The .github/workflows/laravel-cd-dev.yml should looks like the following:

name: Laravel CD to EC2

on:
  push:
    branches:
      - develop

jobs:
  build:
    name: Deploy Dev
    runs-on: ubuntu-latest
    env:
      SOURCE_DIR: "./"
      APP_KEY: base64:d3dLgjYPsyYu8KJQQoFj9XPlbX5soiLcaJvjIX2lIj0=
      APP_URL: https://ducxinh.com
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      
      - name: Copy environment file
        run: cp .env.example .env
      
      - name: Update Laravel .env
        run: |
          echo '${{ secrets.LARAVEL_ENV_DEV }}' > .env

      - name: Deploy to EC2
        uses: easingthemes/ssh-deploy@main
        with: 
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          ARGS: "-rlgoDzvc -i --delete"
          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
          REMOTE_USER: ${{ secrets.REMOTE_USER }}
          TARGET: ${{ secrets.REMOTE_TARGET_DIR }}
          EXCLUDE: "/storage/logs/, /vendor, /node_modules/"
          SCRIPT_BEFORE: |
            whoami
          SCRIPT_AFTER: |
            echo "SCRIPT_AFTER1"
            whoami
            cd ${{ secrets.REMOTE_TARGET_DIR }}
            composer install --optimize-autoloader --no-dev
            php artisan migrate --force
            php artisan db:seed --force
            echo $RSYNC_STDOUT
Understanding the Workflow
  • on.push.branches:: Trigger deployment branch when only on push to the develop branch
  • jobs.deploy: deploy is job name. You can rename to an another name
  • jobs.deploy.runs-on[ubuntu-latest]. We specify that this workflow will run on ubuntu latest version
  • jobs.deploy.env:: Declare some environment variables to use in this workflow.
  • jobs.deploy.steps[Checkout code]: We need to checkout the pushed code to the runner by using a predefined action named actions/checkout@v2. More detail actions/checkout@v2
  • jobs.deploy.steps[Deploy to EC2]: We are deploying the code to the server, in order to do this we need to access the EC2 using ssh and perform rsync from the runner. For this we are going to use another GitHub action easingthemes/ssh-deploy

5. Add GitHub Secret Keys

Navigate to your repository on GitHub: Setting > Security > Secrets and variables > Actions. Add the following secret keys:

  • REMOTE_HOST
  • REMOTE_USER
  • SSH_PRIVATE_KEY = Content: cat ~/.ssh/awesome-laravel-deployment
  • REMOTE_TARGET_DIR
  • LARAVEL_ENV_DEV laravel-ci-with-github-action-007 laravel-ci-with-github-action-003

6. Trigger Deployment

  1. Modify resources/views/welcome.blade.php.
<title>{{ config('app.name') }}</title>
  1. Update APP_NAME in the environment file from Laravel to LaravelCICD.
  2. Push the develop branch to Github to trigger automated deployment workflow laravel-ci-with-github-action-004

7. Check Workflow Details

Monitor the progress of your running workflow for detailed insights laravel-ci-with-github-action-005

8. Verify the Deployment

Open your web browser and navigate to your server's IP address or domain to confirm the successful deployment of your Laravel application. laravel-ci-with-github-action-004

Handling IP Limitations in CD with GitHub Actions

In certain server configurations, IP whitelisting is enforced to enhance security. However, this can pose a challenge when using GitHub Actions for Continuous Deployment (CD).
If you encounter the error message ssh: connect to host *** port 22: Connection timed out during your GitHub Actions workflow, it likely indicates that the GitHub Actions runner's IP is not whitelisted.

Identifying GitHub Actions Runner IP

GitHub provides a range of IP addresses that their GitHub-hosted runners might use. To get a list of IP address ranges that GitHub Actions uses, you can use the GitHub REST API. For more information, see the actions key in the response of the "Meta" endpoint https://api.github.com/meta.

Authorizing GitHub Actions Runner IP

To handle this situation, you can dynamically authorize GitHub Actions IP addresses using AWS CLI commands authorize-security-group-ingress. This step allows you to add the GitHub Actions runner's IP to the security group associated with your EC2 instance.

  1. Create AWS IAM user and set following policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:GetCallerIdentity",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress"
            ],
            "Resource": "*"
        }
    ]
}
  1. Add Github Action secret keys
  • AWS_ACCESS_KEY
  • AWS_SECRET_ACCESS_KEY
  • EC2_SECURITY_GROUP_ID
  1. Update GitHub Actions workflow step:
      - name: Public IP
        id: ip
        uses: haythem/public-ip@v1.3
      
      - name: Print Runner Public IP
        run: |
          echo ${{ steps.ip.outputs.ipv4 }}
          echo ${{ steps.ip.outputs.ipv6 }}
      
      - name: AWS set Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
      
      - name: Add Github actions Runner IP to WhiteList
        run: |
          aws ec2 authorize-security-group-ingress \
            --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} \
            --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
      # - name: Deploy to EC2...
      #   uses: easingthemes/ssh-deploy@main
      #   ...

      - name: Revoke security group
        if: always()
        run: |
          aws ec2 revoke-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 22 --cidr "${{ steps.ip.outputs.ipv4 }}/32"
  1. Verifying and Monitoring

After implementing these changes, run your GitHub Actions workflow and monitor the logs to ensure that the authorization step is successful. You should observe the authorized IP addresses in your AWS security group.

  1. Result
  • Observe the public IP of the GitHub Actions runner: 20.57.43.165 laravel-ci-with-github-action-004
  • You can see the public IP of the GitHub Actions runner has been incorporated into your AWS security group. laravel-ci-with-github-action-004
  • Your GitHub Actions runner is now able to establish a connection and deploy to your server via port 22 laravel-ci-with-github-action-004

By incorporating these steps into your CD process, you ensure a secure and smooth deployment workflow, even in environments with strict IP limitations.

Conclusion

With this GitHub Actions workflow, you've automated the setup of your Laravel environment for CI/CD. GitHub Secrets keep your sensitive information secure, and each push to your repository triggers the workflow, ensuring consistent testing and deployment.

As you implement these practices, always consider security best practices and regularly monitor your workflows for any adjustments needed.

This guide provides a foundation; feel free to expand it with additional steps or any other actions that fit your development workflow.

Happy coding and deploying!