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
- 🚀 Building a Production-Ready GitLab CI/CD Pipeline for Laravel: From Testing to Deployment
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:
- Test Stage: Runs PHP and JavaScript tests in parallel
- Build Stage: Creates optimized production artifacts
- 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 questionsFF_USE_FASTZIP: Enables faster artifact compressionPHPUNIT_JOBS: Enables parallel test executionCOMPOSER_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
developbranch - Manual deployment to production from
mainbranch - 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
- SSH Key Management: Store SSH keys as protected variables
- Environment Files: Use GitLab's file variables for sensitive configuration
- Branch Protection: Restrict production deployments to specific branches
- 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 ciinstead ofnpm install - Implement proper caching
- Use
--no-audit --no-fundflags
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!