CloudPloy

How to Deploy Laravel with CI/CD Pipeline

Complete CI/CD Guide for Laravel Applications

Setting up continuous integration and deployment (CI/CD) for Laravel applications streamlines your workflow, improves code quality, and enables reliable automated releases. This guide walks through a production-ready pipeline from development to deployment.

If you want a managed environment for the deployment target, compare pricing and features before you wire up the pipeline.

Time Required: 45-60 minutes
Difficulty: Intermediate to Advanced
Prerequisites: Laravel application, Git repository, basic Docker knowledge

Table of Contents

  1. CI/CD Pipeline Overview
  2. Prerequisites and Setup
  3. GitHub Actions Configuration
  4. GitLab CI Alternative
  5. Automated Deployment Setup
  6. Monitoring and Alerts
  7. Best Practices

CI/CD Pipeline Overview

A complete Laravel CI/CD pipeline typically includes:

Continuous Integration (CI) Stages

  • Code checkout: Pull latest code from repository
  • Dependency installation: Composer and npm packages
  • Code quality: Static analysis with PHPStan, Psalm
  • Testing: Unit tests, feature tests, browser tests
  • Build assets: Compile CSS/JS with Vite or Mix
  • Security scanning: Vulnerability checks

Continuous Deployment (CD) Stages

  • Build Docker image: Create production-ready container
  • Deploy to staging: Automatic deployment to staging environment
  • Integration tests: End-to-end testing on staging
  • Deploy to production: Automated or manual promotion
  • Health checks: Verify deployment success

Prerequisites and Setup

Required Tools and Accounts

  • ✅ Laravel application with Git repository
  • ✅ GitHub/GitLab account with repository access
  • ✅ CloudPloy account for hosting (Sign up free)
  • ✅ Docker installed locally for testing

Laravel Project Structure

Ensure your Laravel project has these essential files:

laravel-app/
├── .github/workflows/          # GitHub Actions workflows
├── docker/                     # Docker configuration
├── tests/                      # Laravel tests
├── phpunit.xml                 # PHPUnit configuration
├── composer.json               # PHP dependencies
├── package.json                # Node.js dependencies
├── vite.config.js             # Frontend build configuration
└── .env.example               # Environment variables template

GitHub Actions Configuration

Step 1: Create Workflow File

Create .github/workflows/deploy.yml in your repository:

name: Laravel CI/CD Pipeline

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: laravel_test
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

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

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: 8.2
        extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
        coverage: xdebug

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        cache: 'npm'

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

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

    - name: Install NPM dependencies
      run: npm ci

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

    - name: Build assets
      run: npm run build

    - name: Run PHPStan
      run: ./vendor/bin/phpstan analyse

    - name: Run tests
      run: php artisan test --coverage-clover coverage.xml
      env:
        DB_CONNECTION: mysql
        DB_HOST: 127.0.0.1
        DB_PORT: 3306
        DB_DATABASE: laravel_test
        DB_USERNAME: root
        DB_PASSWORD: password

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.xml

Step 2: Build and Deploy Job

Add the deployment job to the same workflow file:

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Setup Docker Buildx
      uses: docker/setup-buildx-action@v2

    - name: Login to Container Registry
      uses: docker/login-action@v2
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: ghcr.io/${{ github.repository }}:latest
        cache-from: type=gha
        cache-to: type=gha,mode=max

    - name: Deploy to CloudPloy
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.CLOUDPLOY_HOST }}
        username: ${{ secrets.CLOUDPLOY_USER }}
        key: ${{ secrets.CLOUDPLOY_SSH_KEY }}
        script: |
          cd /var/www/html
          git pull origin main
          composer install --no-dev --optimize-autoloader
          php artisan migrate --force
          php artisan config:cache
          php artisan route:cache
          php artisan view:cache
          sudo systemctl reload php8.2-fpm

Step 3: Configure Secrets

Add these secrets in GitHub repository settings:

Secret Name Description Value
CLOUDPLOY_HOST Server IP address Your CloudPloy server IP
CLOUDPLOY_USER SSH username ploy (or your configured user)
CLOUDPLOY_SSH_KEY SSH private key Your private SSH key

GitLab CI Alternative

For GitLab users, create .gitlab-ci.yml:

stages:
  - test
  - build
  - deploy

variables:
  MYSQL_DATABASE: laravel_test
  MYSQL_ROOT_PASSWORD: password
  DB_HOST: mysql

test:
  stage: test
  image: php:8.2-cli
  services:
    - mysql:8.0
  before_script:
    - apt-get update -yqq
    - apt-get install -yqq git libmcrypt-dev libpq-dev libcurl4-gnutls-dev libicu-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev
    - docker-php-ext-install mbstring pdo_mysql curl json intl gd xml zip bz2 opcache
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    - curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
    - apt-get install -y nodejs
  script:
    - cp .env.example .env
    - composer install --prefer-dist --no-ansi --no-interaction --no-progress --no-scripts
    - php artisan key:generate
    - npm ci
    - npm run build
    - php artisan test

build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - main

deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '
' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan $DEPLOY_HOST >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    - ssh $DEPLOY_USER@$DEPLOY_HOST "cd /var/www/html && git pull origin main && composer install --no-dev --optimize-autoloader && php artisan migrate --force && php artisan config:cache"
  only:
    - main

Automated Deployment Setup

Docker Configuration

Create a Dockerfile for containerized deployments:

FROM php:8.2-fpm-alpine

# Install system dependencies
RUN apk add --no-cache     git     curl     libpng-dev     libxml2-dev     zip     unzip     nodejs     npm

# Install PHP extensions
RUN docker-php-ext-install pdo pdo_mysql mbstring exif pcntl bcmath gd

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Set working directory
WORKDIR /var/www/html

# Copy application files
COPY . .

# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction

# Install and build frontend assets
RUN npm ci && npm run build

# Set permissions
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

EXPOSE 9000

CMD ["php-fpm"]

Deployment Script

Create scripts/deploy.sh for advanced deployment scenarios:

#!/bin/bash
set -e

echo "Starting deployment..."

# Pull latest changes
git pull origin main

# Install/update dependencies
composer install --no-dev --optimize-autoloader --no-interaction

# Build frontend assets
npm ci --production
npm run build

# Run database migrations
php artisan migrate --force

# Clear and cache configuration
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Restart services
sudo systemctl reload php8.2-fpm
sudo systemctl reload nginx

# Health check
if curl -f http://localhost/health-check; then
    echo "Deployment successful!"
else
    echo "Deployment failed - health check failed"
    exit 1
fi

Zero-Downtime Deployment

For production applications, implement zero-downtime deployment:

#!/bin/bash
set -e

APP_NAME="laravel-app"
DEPLOY_PATH="/var/www"
SHARED_PATH="$DEPLOY_PATH/shared"
RELEASES_PATH="$DEPLOY_PATH/releases"
CURRENT_PATH="$DEPLOY_PATH/current"
RELEASE=$(date +%Y%m%d%H%M%S)
RELEASE_PATH="$RELEASES_PATH/$RELEASE"

# Create directories
mkdir -p $RELEASES_PATH $SHARED_PATH/storage $SHARED_PATH/bootstrap/cache

# Clone to new release directory
git clone $GIT_REPO $RELEASE_PATH
cd $RELEASE_PATH

# Install dependencies
composer install --no-dev --optimize-autoloader
npm ci --production && npm run build

# Create symlinks to shared directories
ln -nfs $SHARED_PATH/storage $RELEASE_PATH/storage
ln -nfs $SHARED_PATH/bootstrap/cache $RELEASE_PATH/bootstrap/cache
ln -nfs $SHARED_PATH/.env $RELEASE_PATH/.env

# Run migrations
php artisan migrate --force

# Update current symlink
ln -nfs $RELEASE_PATH $CURRENT_PATH

# Restart services
sudo systemctl reload php8.2-fpm

# Clean up old releases (keep last 5)
cd $RELEASES_PATH && ls -1d */ | head -n -5 | xargs rm -rf

echo "Deployment completed: $RELEASE"

Monitoring and Alerts

Health Check Endpoint

Create a health check route in routes/web.php:

Route::get('/health-check', function () {
    $checks = [
        'database' => false,
        'cache' => false,
        'storage' => false,
    ];

    try {
        // Database check
        DB::connection()->getPdo();
        $checks['database'] = true;

        // Cache check
        Cache::put('health-check', 'ok', 10);
        $checks['cache'] = Cache::get('health-check') === 'ok';

        // Storage check
        $checks['storage'] = Storage::disk('local')->put('health-check.txt', 'ok');
        Storage::disk('local')->delete('health-check.txt');

    } catch (Exception $e) {
        Log::error('Health check failed: ' . $e->getMessage());
    }

    $allHealthy = !in_array(false, $checks);

    return response()->json([
        'status' => $allHealthy ? 'healthy' : 'unhealthy',
        'checks' => $checks,
        'timestamp' => now()->toISOString()
    ], $allHealthy ? 200 : 503);
});

Deployment Notifications

Add Slack notifications to your deployment workflow:

    - name: Slack Notification
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        channel: '#deployments'
        webhook_url: ${{ secrets.SLACK_WEBHOOK }}
        message: |
          ${{ job.status == 'success' && '✅' || '❌' }} Laravel deployment ${{ job.status }}
          Branch: ${{ github.ref }}
          Commit: ${{ github.sha }}
          Author: ${{ github.actor }}
      if: always()

Best Practices

🔒 Security Best Practices

  1. Environment Variables: Never commit sensitive data to Git
  2. SSH Keys: Use dedicated deployment keys with limited permissions
  3. Secrets Management: Rotate deployment secrets regularly
  4. Access Control: Limit who can trigger deployments

🚀 Performance Optimization

  1. Caching Strategy: Implement multi-layer caching in CI/CD
  2. Parallel Jobs: Run tests and builds in parallel
  3. Artifact Management: Cache dependencies between builds
  4. Build Optimization: Optimize Docker layers for faster builds

📊 Testing Strategy

Test Type When to Run Purpose
Unit Tests Every push Verify individual components
Feature Tests Every push Test application features
Browser Tests Before deployment End-to-end user workflows
Load Tests Scheduled/manual Performance validation

🔄 Rollback Strategy

Implement automatic rollback on deployment failure:

    - name: Rollback on failure
      if: failure()
      run: |
        echo "Deployment failed, rolling back..."
        ssh $DEPLOY_USER@$DEPLOY_HOST "
          cd /var/www &&
          ln -nfs $(ls -1d releases/*/ | tail -n 2 | head -n 1) current &&
          sudo systemctl reload php8.2-fpm
        "
        echo "Rollback completed"

CloudPloy CI/CD Benefits

When you deploy Laravel with CI/CD on CloudPloy, you get:

🚀 Optimized Performance

  • Laravel-optimized server configurations
  • Automatic OPcache and Redis caching
  • SSD storage with high IOPS
  • CDN integration for static assets

🛡️ Enhanced Security

  • Automatic security updates
  • Laravel-specific firewall rules
  • SSL certificate automation
  • Regular security scans

💼 Developer Experience

  • One-click staging environments
  • Integrated monitoring dashboards
  • Automated backup management
  • 24/7 deployment support

Common Issues and Solutions

Issue 1: Deployment Timeouts

Solution: Increase timeout values and optimize build process

timeout-minutes: 30  # Increase from default 10 minutes

Issue 2: Database Migration Failures

Solution: Use migration rollback and backup verification

php artisan migrate --force --step
php artisan migrate:status

Issue 3: Asset Build Failures

Solution: Use Node.js version management and clean installs

rm -rf node_modules package-lock.json
npm ci

Next Steps

After implementing CI/CD for your Laravel application:

  1. Set up Performance Optimization
  2. Implement Security Best Practices
  3. Configure Automated Backups
  4. Set up Team Notifications

Need Professional Help?

CloudPloy offers managed CI/CD setup services:

  • 💬 Expert Consultation: 24/7 DevOps specialists
  • 🛠️ Custom Pipeline Setup: Tailored to your needs
  • 📚 Team Training: CI/CD best practices workshop
  • 🎯 Optimization Services: Performance and security tuning

Deploy Laravel with Professional CI/CD

Experience seamless Laravel deployments with CloudPloy:

  • 🚀 Laravel-optimized infrastructure
  • 🔄 Automated CI/CD pipeline setup
  • 🛡️ Enterprise-grade security
  • 💬 24/7 DevOps expert support
  • 🎁 Free migration and setup assistance

View Plans

No credit card required • Expert DevOps setup • Cancel anytime


Last updated: 2025-08-30