Skip to content
TutorialDevOps

Build Your First GitHub Actions Workflow for Laravel

D

Dinesh Wijethunga

July 20, 2026 4 min readBeginner 38 views
ShareX / TwitterLinkedIn
Build Your First GitHub Actions Workflow for Laravel

In the last post I explained what CI/CD is and why you want it. Now we build. By the end of this post you will have a working GitHub Actions workflow that runs automatically every time anyone pushes to your repository.

I'll explain every line. If you've never written YAML before, that's fine — the syntax is simple once you see the pattern.

Step 1: Create the Workflow File

In the root of your Laravel project, create this directory structure:

your-project/
└── .github/
    └── workflows/
        └── api-ci.yml

The .github folder must be at the root of your repository (same level as composer.json). GitHub looks for workflow files specifically in .github/workflows/.

Step 2: The Trigger Block

Open api-ci.yml and start with the trigger:

name: API CI

on:
  push:
    branches: [main, develop]
    paths: ['api/**']
  pull_request:
    branches: [main, develop]
    paths: ['api/**']

What this does:

  • name: is just the label shown in the GitHub Actions UI
  • on: defines when the workflow fires
  • push: + branches: — runs when you push to main or develop
  • pull_request: — runs when you open or update a PR targeting those branches
  • paths: — only runs when files inside api/ change (skip it if you have a single Laravel app, not a monorepo)

Step 3: The First Job

defaults:
  run:
    working-directory: api   # remove this if your Laravel app is at the root

jobs:
  tests:
    name: Tests (PHP 8.4)
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP 8.4
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          extensions: mbstring, pdo, pdo_mysql, bcmath, gd, zip, intl
          coverage: none
          tools: composer:v2

actions/checkout@v4 downloads your repository code onto the runner. Without it, the VM has an empty disk.

shivammathur/setup-php@v2 is the community-standard action for installing PHP. You specify the version and extensions. The tools: composer:v2 line ensures Composer 2 is installed.

Step 4: Dependency Caching

Without caching, every CI run downloads all your Composer packages from scratch. On a project with 80 dependencies this takes 2–3 minutes. With caching it takes 15 seconds.

      - name: Cache Composer dependencies
        uses: actions/cache@v4
        with:
          path: api/vendor
          key: php-8.4-composer-${{ hashFiles('api/composer.lock') }}
          restore-keys: php-8.4-composer-

The key includes a hash of composer.lock. When your lock file changes (you added a package), the cache key changes and CI downloads fresh dependencies. When nothing changed, it restores the cached vendor/ folder.

Step 5: Install and Test

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist --no-progress

      - name: Copy environment file
        run: cp .env.example .env

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

      - name: Run tests
        run: ./vendor/bin/pest

--no-interaction prevents Composer from asking questions. --prefer-dist downloads zip archives instead of cloning git repos (faster). --no-progress keeps the logs clean.

The cp .env.example .env step is important: Laravel won't boot without an .env file. Your .env.example must have sensible defaults for all values the app reads at startup.

Step 6: Push It

git add .github/workflows/api-ci.yml
git commit -m "ci: add initial GitHub Actions workflow"
git push origin main

Go to your repository on GitHub, click Actions. You'll see the workflow appear and start running. Click into it and watch the steps execute in real time.

If it goes green — congratulations. You have CI. Every future push will run this automatically.

Common First-Run Failures

"Process completed with exit code 1" on key:generate
Your .env.example is missing APP_KEY=. Add APP_KEY= (empty value) and key:generate will fill it.

"Class not found" errors in tests
Run composer dump-autoload locally and commit the result. CI runs composer install which includes autoload generation, but if your composer.json has stale paths this surfaces here.

Tests passing locally but failing in CI
Your tests depend on something that only exists on your machine — a database, a cache entry, or an environment variable. We fix this properly in Post 3 (MySQL) and Post 5 (secrets).

In the next post we add a MySQL service container so your tests run against a real database instead of SQLite.

Frequently Asked Questions

Where does a GitHub Actions workflow file go?
In .github/workflows/ at the repository root, at the same level as composer.json. GitHub only detects workflow files in that exact directory.
Why does php artisan key:generate fail in CI?
Your .env.example is missing an APP_KEY= line. Add it with an empty value and key:generate will fill it in.
D
Dinesh Wijethunga

Senior Full Stack Developer · Building SaaS products & teaching Laravel/React · 10+ years experience · Founder of Orion360 · Based in Dubai, UAE.

Was this post helpful?

Reviews & Ratings

Sign in to leave a review.

Comments(0)

Guest comments are held for moderation.

You might also like