Home / Blog / CI/CD for Laravel with GitHub Actions: A Practical Pipeline
DevOps & Cloud

CI/CD for Laravel with GitHub Actions: A Practical Pipeline

AJAjish Stephen August 8, 2026 8 min read
CI/CD for Laravel with GitHub Actions: A Practical Pipeline

Manual deployment — SSH in, pull the latest code, run migrations, hope nothing breaks — works until it doesn't. A CI/CD pipeline removes the guesswork: every push runs your tests automatically, and only code that passes gets deployed. Here's a complete, working GitHub Actions setup for a Laravel app.

The full workflow file

Create this at .github/workflows/deploy.yml:

name:Laravel CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on:ubuntu-latest

    services:
      mysql:
        image:mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD:secret
          MYSQL_DATABASE:testing
        ports:["3306:3306"]

    steps:
      - uses:actions/checkout@v4

      - name:Setup PHP
        uses:shivammathur/setup-php@v2
        with:
          php-version:'8.3'
          extensions:mbstring, pdo_mysql, zip

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

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

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

      - name:Run migrations
        run:php artisan migrate --force
        env:
          DB_HOST:127.0.0.1
          DB_DATABASE:testing
          DB_USERNAME:root
          DB_PASSWORD:secret

      - name:Run tests
        run:php artisan test

  deploy:
    needs:test
    if:github.ref == 'refs/heads/main'
    runs-on:ubuntu-latest

    steps:
      - name:Deploy via SSH
        uses:appleboy/ssh-action@v1
        with:
          host:$@{{ secrets.DEPLOY_HOST }}
          username:$@{{ secrets.DEPLOY_USER }}
          key:$@{{ secrets.DEPLOY_SSH_KEY }}
          script:|
            cd /var/www/app
            git pull origin main
            composer install --no-dev --optimize-autoloader
            php artisan migrate --force
            php artisan config:cache
            php artisan route:cache
            php artisan queue:restart

What this pipeline actually does

Every push to main or pull request spins up a fresh MySQL service, installs dependencies, runs migrations, and runs the full test suite
The deploy job only runs after needs: test passes, and only on main — pull requests get tested but never trigger a deploy
Deployment connects over SSH, pulls the latest code, reinstalls production dependencies, migrates, and rebuilds config/route caches — the same steps you'd run by hand, just guaranteed to happen every time in the same order

Setting up the secrets

In your GitHub repo, go to Settings → Secrets and variables → Actions and add:

DEPLOY_HOST
DEPLOY_USER
DEPLOY_SSH_KEY

The SSH key should be a private key whose matching public key is already added to the server's ~/.ssh/authorized_keys for that user. Never commit these values directly into the YAML file or the repo.

Setting up CI/CD for a real production system, or want your deployment process reviewed for gaps? This is exactly the kind of work I do as part of DevOps services engagements.

Common questions

Do I need CI/CD for a small Laravel project?
Even a solo project benefits from automated tests running on every push — it catches regressions before they reach production. Automated deployment matters more once you're deploying frequently or working with a team.
What's the difference between CI and CD?
Continuous Integration (CI) is automatically testing and validating code on every push. Continuous Deployment (CD) takes that further by automatically deploying code that passes CI.
How do I keep secrets out of my GitHub Actions workflow file?
Store sensitive values in GitHub's repository or environment secrets, then reference them in the workflow using GitHub's secrets syntax. Never hardcode credentials directly in the YAML file.
Should tests run before or after deployment?
Always before. The deployment job should depend on the test job passing first, so broken code never reaches production.
Still deploying by hand?
I set up CI/CD pipelines that catch problems before they reach your users.
Explore DevOps Services →
© Copyright 2024 Ajish Stephen