Get started with 33% off your first certification using code: 33OFFNEW

How to run tests and deploy a Laravel app from GitHub Actions

5 min read
Published on 24th May 2023

GitHub Actions is a powerful tool that enables developers to automate their software development workflows. In this article, we will demonstrate how to use GitHub Actions to set up and run tests on a Laravel application and then deploy the application using Laravel Forge.

Setting Up GitHub Actions

To get started with GitHub Actions, you need to create a workflow file in your Laravel project repository. This file will define the actions that GitHub will perform. Create a new directory .github/workflows and add a new file main.yml within it.

This file is basically a list of instructions that GitHub runs sequentially in a VM. You can then configure how and when these commands are run.

name: Laravel CI/CD

on:
  push:
    branches: [ master ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

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

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

    - name: Install Dependencies
      run: composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-req=ext-imagick

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

    - name: Directory Permissions
      run: chmod -R 777 storage bootstrap/cache

    - name: Create Database
      run: |
        mkdir -p database
        touch database/database.sqlite
    - name: Migrate
      env:
        DB_CONNECTION: sqlite
        DB_DATABASE: database/database.sqlite
      run: php artisan migrate

    - name: Cache node modules
      id: cache-npm
      uses: actions/cache@v3

      env:
        cache-name: cache-node-modules
      with:
        # npm cache files are stored in `~/.npm` on Linux/macOS
        path: ~/.npm
        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-build-${{ env.cache-name }}-
          ${{ runner.os }}-build-
          ${{ runner.os }}-

    - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
      name: List the state of node modules
      continue-on-error: true
      run: npm list

    - name: Compile assets
      run: |
        npm install
        npm run production

    - name: Run tests
      run: php artisan test

  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest

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

    - name: Deploy to Forge
      env:
        FORGE_API_KEY: ${{ secrets.FORGE_API_KEY }}
        FORGE_SERVER_ID: ${{ secrets.FORGE_SERVER_ID }}
        FORGE_SITE_ID: ${{ secrets.FORGE_SITE_ID }}
      run: |
        curl -X POST "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$FORGE_SITE_ID/deploy" \
        -H "Authorization: Bearer $FORGE_API_KEY"

Let's run through exactly what we're doing here, step by step.

Job set up

Starting with the beginning of the file:

name: This sets the name of the workflow. on: This specifies the event that triggers the workflow. In this case, the workflow will run whenever you push changes to the master branch (change this line if you use main). jobs: This section contains the steps that GitHub Actions will perform.

Jobs

Now let's go through the list of jobs.

Build and test

The first job is build-and-test, which will run on an ubuntu-latest environment. This job has several steps:

Checkout repository: This step uses the actions/checkout@v2 action to check out your Laravel repository. Set up PHP: This step uses the shivammathur/setup-php@v2 action to set up PHP with the specified version and extensions.

Copy env: This step copies the example .env file. If you want to house some test-specific environment here you could have an .env.testing file you copy instead, just ensure you keep any sensitive variables OUT of here.

Install dependencies: This step installs the Laravel project dependencies using Composer.

Generate key: Laravel needs a key to run, so we generate it using it a command here.

Directory permissions: The storage and bootstrap cache folders need to be writable for most apps to function, so lets set that up.

Create database: Most apps need a database. The easiest way to do this is using SQLite, so that's what we're setting up here.

Migrate: Create database schema by migrating.

Cache node_modules: This is an optional step, but here we are going to cache the output of npm install. We've actually written a dedicated article on this step so we won't go into too much detail here: Caching npm install for faster build times on GitHub Actions.

Compile assets: Build our assets using npm run build or npm run production. Most Laravel apps will have tests that involve loading a frontend, so this step is essential.

Run tests: This step runs the Laravel tests using the php artisan test command.

Deploy

The second task is to deploy. To deploy here we're using Laravel Forge's API.

We've opted to use the API as it allows you to extend the GitHub Action to do more than simply deploy if you wanted to. There's a much easier way to deploy using Laravel Forge than use the API, and that's to use the deployment webhook for your environment. Simply go to the environment in Forge, find the webhook URL and then make a cURL request to it in your Action:

    - name: Deploy to Laravel Forge
      run: curl ${{ secrets.FORGE_DEPLOYMENT_WEBHOOK }}

You'll notice the use of ${{ secrets }} variable throughout here. You don't want to store anything sensitive in your repo, and that includes in your GitHub workflow file, so GitHub has a secret vault for you to use.

Anyway, back to the Forge API, here we're doing a couple of things. First off, we set a bunch of environment variables from the GitHub secrets vault:

      env:
        FORGE_API_KEY: ${{ secrets.FORGE_API_KEY }}
        FORGE_SERVER_ID: ${{ secrets.FORGE_SERVER_ID }}
        FORGE_SITE_ID: ${{ secrets.FORGE_SITE_ID }}

and then we run a cURL request to the API endpoint:

      run: |
        curl -X POST "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$FORGE_SITE_ID/deploy" \
        -H "Authorization: Bearer $FORGE_API_KEY"

This is pretty straightforward, it just makes a POST request to the deployment endpoint.

Adding GitHub secrets

Before any of this works you'll need to configure GitHub secrets:

  • FORGE_API_KEY: Your Forge API key, which can be obtained from the Forge dashboard.
  • FORGE_SERVER_ID: The ID of the server you created in Forge.
  • FORGE_SITE_ID: The ID of the site you created in Forge.

To add GitHub secrets navigate to the "Settings" tab of your GitHub repository, click on "Secrets" in the left sidebar, and then click on the "New repository secret" button.

Testing it all

Once everything is configured simply commit and push a change to your master (or main) branch and then load the Actions page of your repo in your browser. This is usually found at:

https://github.com/[your username]/[your repo]/actions

Just navigate to your repo URL and click the Actions tab at the top.

You'll be able to watch as the Action runs and each step is running. In our experience a full Laravel build, configuration and run of a small test suite takes about 2 minutes, but most of that is spent configuring the underlying VM and building your assets, so the actual time spent running will differ greatly depending on whether you're using Vite or Webpack, and the amount of assets or frontend components you're building. And, of course, the number of tests in your test suite.