Tech Verse Logo
Enable dark mode
From GitHub Actions to Production Rollout: CI/CD for Laravel

From GitHub Actions to Production Rollout: CI/CD for Laravel

Tech Verse Daily

Tech Verse Daily

4 min read

Automate, deploy, and scale Laravel projects confidently with GitHub Actions, staging environments, and zero-downtime rollouts.

Introduction

Manual deployments are slow, error-prone, and often lead to those dreaded “it works on my machine” moments. For modern Laravel applications — especially in production — continuous integration and delivery (CI/CD) pipelines have become essential.

In this post, we’ll walk through how to set up a robust CI/CD pipeline for Laravel using GitHub Actions, staging environments, and zero-downtime deployment strategies. You’ll also learn how to handle database migrations safely and perform rollbacks without panic.

Understanding the Laravel Deployment Lifecycle

Before jumping into automation, it’s important to understand what happens when you deploy Laravel:

  1. Code Update — Fetching latest code from Git.

  2. Dependency Installation — Running composer install and npm install.

  3. Environment Configuration — Loading .env values and clearing caches.

  4. Database Migrations — Updating schema using php artisan migrate.

  5. Asset Compilation — Running npm run build for frontend assets.

  6. Cache Optimization — Running php artisan config:cache and php artisan route:cache.

A proper CI/CD pipeline automates these steps while ensuring consistency between environments.

Setting Up a GitHub Actions Workflow

Let’s start with a basic GitHub Actions workflow for a Laravel project.

Create a file at:
.github/workflows/deploy.yml

name: Deploy Laravel App

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

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

      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          extensions: mbstring, intl, bcmath, pdo_mysql

      - name: Install Composer dependencies
        run: composer install --no-dev --optimize-autoloader

      - name: Run tests
        run: php artisan test

      - name: Deploy to server
        uses: appleboy/scp-action@v0.1.4
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          source: "."
          target: "/var/www/laravel-app"

Explanation

  • Trigger: Runs when changes are pushed to the main branch.

  • Setup: Installs PHP, dependencies, and runs tests.

  • Deploy: Uses scp to copy files securely to your server.

This is the starting point — next, we’ll make it smarter.

Introducing Staging Environments

Before you deploy to production, you need a staging environment — a clone of production where you can test the release safely.

Branch strategy example:

  • develop → auto-deploys to staging.

  • main → auto-deploys to production (after review).

In your workflow:

on:
  push:
    branches:
      - main
      - develop

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      ...
      - name: Set environment target
        run: |
          if [ "${{ github.ref_name }}" = "main" ]; then
            echo "DEPLOY_PATH=/var/www/laravel-prod" >> $GITHUB_ENV
          else
            echo "DEPLOY_PATH=/var/www/laravel-staging" >> $GITHUB_ENV
          fi
      - name: Deploy
        uses: appleboy/scp-action@v0.1.4
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          source: "."
          target: ${{ env.DEPLOY_PATH }}

Now your workflow deploys automatically to the correct environment based on branch.

Zero-Downtime Deployment (Like a Pro)

The biggest risk during deployment is downtime — especially when running migrations or clearing caches.

You can achieve zero-downtime deployment using tools like:

  • Envoy (Laravel’s built-in SSH task runner)

  • Deployer (a PHP-based deployment tool)

Example: Using Envoy

Create Envoy.blade.php at your project root:

@servers(['web' => 'deploy@your-server-ip'])

@task('deploy', ['on' => 'web'])
    cd /var/www/laravel-app
    git pull origin main
    composer install --no-dev --optimize-autoloader
    php artisan migrate --force
    php artisan cache:clear
    php artisan config:cache
    php artisan route:cache
    php artisan view:cache
@endtask

Then trigger it via GitHub Actions:

- name: Run Envoy Deployment
  run: php vendor/bin/envoy run deploy --server=web

This approach executes commands remotely and keeps your app online during updates.

Safe Database Migrations

Migrations can be risky in production — especially when they modify large tables.

Best practices:

  • Always run migrations inside maintenance mode if there’s a schema change:

php artisan down
php artisan migrate --force
php artisan up
  • Use --force to skip confirmation prompts.

  • For complex schema changes, use rolling migrations (e.g., add nullable columns first, then fill data later).

  • Automate with your CI/CD, not manual SSH commands.

Handling Rollbacks Gracefully

Things can go wrong — a failed migration, missing environment variable, or misbehaving feature.

Here’s how to roll back safely:

  1. Versioned releases: Keep previous releases in /var/www/releases with symlinks.

  2. Switch instantly:

ln -sfn /var/www/releases/previous /var/www/current

3. Rollback migrations:

php artisan migrate:rollback --force

You can automate all of this using Deployer or Envoy tasks for consistency.

Adding Notifications

A good CI/CD pipeline should talk back.

You can add a step to send Slack or Discord notifications when a deployment completes:

- name: Notify Slack
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    fields: repo,message,commit,author
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

This ensures visibility across your team when something goes live.

Security & Secrets Management

Never hardcode credentials in your workflow. Use GitHub Secrets for environment variables like:

  • SERVER_IP

  • SERVER_USER

  • SSH_KEY

  • SLACK_WEBHOOK

For more advanced setups, integrate with AWS Secrets Manager, Vault, or Doppler.

    Latest Posts

    View All

    Introducing the Laravel AI SDK — Build Smarter Apps with AI

    Introducing the Laravel AI SDK — Build Smarter Apps with AI

    Laravel AI SDK: Building AI-Powered Applications the Laravel Way

    Laravel AI SDK: Building AI-Powered Applications the Laravel Way

    Getting Started with Mago – The Fastest PHP Tooling Chain

    Getting Started with Mago – The Fastest PHP Tooling Chain

    Best Stack Recommendations for Laravel Projects (Battle-Tested in Production)

    Best Stack Recommendations for Laravel Projects (Battle-Tested in Production)

    Laravel + React Authentication the Right Way: Sanctum, JWT, or Passport?

    Laravel + React Authentication the Right Way: Sanctum, JWT, or Passport?

    Laravel PDF Generator: Spatie Laravel PDF vs Laravel DomPDF (In-Depth Comparison)

    Laravel PDF Generator: Spatie Laravel PDF vs Laravel DomPDF (In-Depth Comparison)

    how to systematically optimize Laravel databases in production

    how to systematically optimize Laravel databases in production

    Optimize Images in Laravel with Intervention Image

    Optimize Images in Laravel with Intervention Image

    Common Security Mistakes in Laravel Apps and How to Fix Them Properly

    Common Security Mistakes in Laravel Apps and How to Fix Them Properly

    Clean, Reusable Query Logic the Right Way: Laravel Global Scopes & Local Scopes

    Clean, Reusable Query Logic the Right Way: Laravel Global Scopes & Local Scopes