🚀 Building a Production-Ready GitLab CI/CD Pipeline for Laravel: From Testing to Deployment

In this comprehensive guide, we'll build a robust GitLab CI/CD pipeline for a Laravel application that includes automated testing, code quality checks, asset building, and deployment to multiple environments. This pipeline ensures code quality, prevents regressions, and enables reliable deployments.

📋 Table of Contents

Introduction

A well-designed CI/CD pipeline is crucial for maintaining code quality and enabling rapid, reliable deployments. This guide demonstrates how to build a production-ready GitLab CI/CD pipeline for Laravel applications that includes comprehensive testing, code analysis, asset compilation, and automated deployment to multiple environments.

Our pipeline follows modern DevOps practices with parallel execution, intelligent caching, and environment-specific deployments while maintaining security and reliability.

Pipeline Overview

Our CI/CD pipeline consists of three main stages:

  1. Test Stage: Runs PHP and JavaScript tests in parallel
  2. Build Stage: Creates optimized production artifacts
  3. Deploy Stage: Deploys to development and production environments

The pipeline uses a custom Docker image optimized for Laravel applications and implements intelligent caching to reduce build times.

Prerequisites and Setup

Docker Image Configuration

We use a custom Docker image that includes PHP 8.4 with all necessary extensions:

image: registry.gitlab.com/dommmin/gitlab-ci/php-8.4-fpm:latest

This image should include:

  • PHP 8.4 with extensions (mbstring, xml, bcmath, etc.)
  • Composer
  • Node.js and npm
  • Git
  • SQLite (for testing)

Environment Variables

Configure these global variables for optimal performance:

variables:
  COMPOSER_NO_INTERACTION: 1
  COMPOSER_CACHE_DIR: .composer-cache
  NODE_VERSION: "22"
  FF_USE_FASTZIP: "true"
  TRANSFER_METER_FREQUENCY: "2s"
  PHPUNIT_JOBS: 4
  COMPOSER_PROCESS_TIMEOUT: 2000
  COMPOSER_MEMORY_LIMIT: -1

Key variables explained:

  • COMPOSER_NO_INTERACTION: Prevents Composer from asking interactive questions
  • FF_USE_FASTZIP: Enables faster artifact compression
  • PHPUNIT_JOBS: Enables parallel test execution
  • COMPOSER_MEMORY_LIMIT: Removes memory limits for Composer

Caching Strategy

Implement intelligent caching to reduce build times:

cache:
  key:
    files:
      - composer.lock
      - package-lock.json
  paths:
    - vendor/
    - node_modules/
    - .composer-cache/
  policy: pull-push
  when: on_success
  untracked: true

This configuration:

  • Creates cache keys based on lock files
  • Caches dependencies and build artifacts
  • Only updates cache on successful builds
  • Includes untracked files in cache

Pipeline Stages

Testing Stage

PHP Testing Job

test:php:
  stage: test
  services:
    - redis:latest
  variables:
    DB_CONNECTION: sqlite
    DB_DATABASE: ":memory:"
    SESSION_DRIVER: array
    CACHE_DRIVER: array
    QUEUE_CONNECTION: sync
  before_script:
    - *install_dependencies
    - echo "=== Building assets for PHP tests ==="
    - npm ci --no-audit --no-fund
    - npm run build
    - php artisan ziggy:generate
  script:
    - echo "=== Running PHP tests and analysis ==="
    - composer larastan || true
    - composer pint --test || true
    - ./vendor/bin/pest --log-junit=junit.xml --parallel
  artifacts:
    reports:
      junit: junit.xml
    paths:
      - public/build
    when: always
    expire_in: 1 week

This job:

  • Uses in-memory SQLite for fast database tests
  • Runs static analysis with Larastan
  • Checks code formatting with Laravel Pint
  • Executes tests in parallel with Pest
  • Generates JUnit reports for GitLab integration

JavaScript Testing Job

test:js:
  stage: test
  before_script:
    - *install_dependencies
  script:
    - echo "=== Running JavaScript tests and analysis ==="
    - npm ci --no-audit --no-fund
    - npm run build
    - php artisan ziggy:generate
    - npm run format || true
    - npm run types || true
    - npm run lint || true
  artifacts:
    paths:
      - public/build
    when: always
    expire_in: 1 week

This job handles:

  • TypeScript type checking
  • ESLint code quality checks
  • Prettier code formatting
  • Asset compilation and optimization

Building Stage

The build stage creates optimized production artifacts:

build:
  stage: build
  environment:
    name: production
  needs:
    - test:php
    - test:js
  script:
    - echo "=== Building application ==="
    - composer install --optimize-autoloader --classmap-authoritative --no-dev --prefer-dist --no-interaction --no-progress
    - php artisan config:clear
    - php artisan route:clear
    - php artisan view:clear
    - php artisan ziggy:generate
    - echo "=== Building frontend assets ==="
    - npm ci --omit=dev --no-audit --no-fund
    - npm install -g dotenv-cli
    - dotenv -e $ENV_FILE -- npm run build:ssr
    - echo "=== Creating release package ==="
    - mkdir -p release
    - shopt -s extglob
    - cp -r !(release|.git|tests|node_modules|*.tar.gz|*.log|coverage.xml|junit.xml) release/
    - tar -czf release.tar.gz -C release .
    - rm -rf release
    - ls -lah release.tar.gz
    - echo "✅ Build completed successfully"
  artifacts:
    paths:
      - release.tar.gz
    expire_in: 1 day

Key optimizations:

  • Production-optimized Composer installation
  • Cache clearing for optimal performance
  • SSR (Server-Side Rendering) build
  • Compressed artifact creation
  • Excludes unnecessary files from deployment

Deployment Stage

Deployment Template

.deploy_template: &deploy_template
  stage: deploy
  image: alpine:3.19
  needs: [build]
  before_script:
    - apk add --no-cache openssh-client
    - mkdir -p ~/.ssh
    - echo "$SSH_KEY" | tr -d '\r' > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -p $SSH_PORT $SSH_HOST >> ~/.ssh/known_hosts
  script:
    - scp -P $SSH_PORT release.tar.gz $SSH_USER@$SSH_HOST:/home/$SSH_USER/laravel/ || exit 1
    - scp -P $SSH_PORT $ENV_FILE $SSH_USER@$SSH_HOST:/home/$SSH_USER/laravel/shared/.env || exit 1
    - scp -P $SSH_PORT deploy.sh $SSH_USER@$SSH_HOST:/home/$SSH_USER/laravel/ || exit 1
    - ssh -p $SSH_PORT $SSH_USER@$SSH_HOST "cd laravel && chmod +x ./deploy.sh && ./deploy.sh" || exit 1

Environment-Specific Deployments

deploy_dev:
  <<: *deploy_template
  environment:
    name: development
  only:
    - develop

deploy_prod:
  <<: *deploy_template
  environment:
    name: production
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
      allow_failure: false

Features:

  • Automatic deployment to development from develop branch
  • Manual deployment to production from main branch
  • Secure SSH key management
  • Environment-specific configuration

Advanced Configuration

Parallel Testing

Enable parallel test execution for faster feedback:

variables:
  PHPUNIT_JOBS: 4

script:
  - ./vendor/bin/pest --log-junit=junit.xml --parallel

Artifact Management

Optimize artifact storage and transfer:

artifacts:
  reports:
    junit: junit.xml
  paths:
    - public/build
  when: always
  expire_in: 1 week

Security Considerations

  1. SSH Key Management: Store SSH keys as protected variables
  2. Environment Files: Use GitLab's file variables for sensitive configuration
  3. Branch Protection: Restrict production deployments to specific branches
  4. Manual Approvals: Require manual approval for production deployments

Common Issues and Solutions

1. Memory Issues with Composer

Problem: Composer runs out of memory during installation

Solution:

variables:
  COMPOSER_MEMORY_LIMIT: -1

2. Slow Asset Builds

Problem: Frontend builds take too long

Solutions:

  • Use npm ci instead of npm install
  • Implement proper caching
  • Use --no-audit --no-fund flags

3. Test Database Issues

Problem: Database tests fail or are slow

Solution:

variables:
  DB_CONNECTION: sqlite
  DB_DATABASE: ":memory:"

4. SSH Connection Failures

Problem: Deployment fails due to SSH issues

Solutions:

  • Verify SSH key format (no Windows line endings)
  • Add host to known_hosts
  • Check firewall and port settings

5. Cache Invalidation

Problem: Builds use outdated cached dependencies

Solution:

cache:
  key:
    files:
      - composer.lock
      - package-lock.json

Best Practices

1. Pipeline Optimization

  • Use parallel jobs where possible
  • Implement intelligent caching
  • Minimize artifact sizes
  • Use specific Docker image tags

2. Testing Strategy

  • Run tests in parallel
  • Use in-memory databases for speed
  • Include both unit and feature tests
  • Generate coverage reports

3. Security

  • Store sensitive data in GitLab variables
  • Use protected branches and environments
  • Implement manual approval for production
  • Regularly rotate SSH keys

4. Monitoring and Debugging

  • Use meaningful job names and descriptions
  • Include progress indicators in scripts
  • Store artifacts for debugging
  • Set appropriate artifact expiration

5. Environment Management

  • Use environment-specific variables
  • Implement proper secret management
  • Maintain separate deployment configurations
  • Use blue-green or rolling deployments

Conclusion

This GitLab CI/CD pipeline provides a robust foundation for Laravel application deployment with automated testing, code quality checks, and reliable deployment processes. The configuration emphasizes performance, security, and maintainability while providing flexibility for different deployment scenarios.

Key benefits of this approach:

  • Fast Feedback: Parallel testing and intelligent caching
  • Quality Assurance: Comprehensive testing and code analysis
  • Secure Deployment: SSH-based deployment with proper secret management
  • Environment Management: Separate development and production pipelines
  • Scalability: Easy to extend and modify for different requirements

Remember to:

  • Regularly update your Docker images and dependencies
  • Monitor pipeline performance and optimize bottlenecks
  • Keep your deployment scripts and configurations in version control
  • Test your pipeline thoroughly before deploying to production
  • Implement proper monitoring and alerting for your deployments

Follow me on LinkedIn for more DevOps and Laravel deployment tips!

Want to learn more about GitLab CI/CD optimization or Laravel deployment strategies? Let me know in the comments below!

Comments (0)
Leave a comment

© 2026 All rights reserved.