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

    Laravel 12.44: Adds HTTP Client afterResponse() Callbacks

    Laravel 12.44: Adds HTTP Client afterResponse() Callbacks

    Laravel Artifact: Manage Your Media Easily

    Laravel Artifact: Manage Your Media Easily

    Handling Large File Uploads in Laravel: A Guide to Chunking & Resuming

    Handling Large File Uploads in Laravel: A Guide to Chunking & Resuming

    Next-Gen Laravel Deployment: FrankenPHP + Octane on Ubuntu VPS

    Next-Gen Laravel Deployment: FrankenPHP + Octane on Ubuntu VPS

    Speed Up Your Laravel App: Mastering Concurrent API Requests with Http::pool and Batch

    Speed Up Your Laravel App: Mastering Concurrent API Requests with Http::pool and Batch

    Beyond the Basics: Building Production-Ready APIs with Laravel

    Beyond the Basics: Building Production-Ready APIs with Laravel

    PHP 8.6: Expected Release Window and RFCs to Watch

    PHP 8.6: Expected Release Window and RFCs to Watch

    Downloading Files from External URLs in Laravel

    Downloading Files from External URLs in Laravel

    Pause and Resume Laravel Queue Workers on Demand

    Pause and Resume Laravel Queue Workers on Demand

    Resume Canvas - Open Source Resume Builder

    Resume Canvas - Open Source Resume Builder