Setting up a robust CI/CD pipeline for Laravel applications is crucial for maintaining code quality, accelerating development cycles, and ensuring smooth, reliable deployments. This guide will walk you through building an effective pipeline using GitHub Actions, leveraging Docker for consistent environments, and incorporating automated testing.
\n---
\nWhy CI/CD for Laravel?
\nContinuous Integration (CI) and Continuous Delivery/Deployment (CD) automate critical steps in the software development lifecycle. For Laravel projects, this translates to:
\n- \n
- Faster Feedback Loops: Catch errors early with automated tests. \n
- Consistent Environments: Docker ensures your application behaves the same from development to production. \n
- Reduced Manual Errors: Automate repetitive deployment tasks. \n
- Increased Confidence: Deploy with assurance knowing your code has passed all checks. \n
---
\nCore Components of Our Pipeline
\nWe''ll focus on these key technologies:
\n- \n
- GitHub Actions: Our CI/CD orchestrator. \n
- Docker: For containerizing the Laravel application and its dependencies. \n
- PHPUnit: Laravel''s built-in testing framework. \n
- Composer: PHP dependency management. \n
- Artisan: Laravel''s command-line interface. \n
---
\nStep 1: Project Setup and Dockerization
\nFirst, ensure your Laravel project is set up to be Dockerized. This typically involves a Dockerfile for your application and a docker-compose.yml for local development.
Example Dockerfile
\n # For example, using an official PHP-FPM image\n FROM php:8.2-fpm-alpine\n\n # Install system dependencies\n RUN apk add --no-cache \\\n nginx \\\n mysql-client \\\n nodejs \\\n npm\n\n # Install PHP extensions\n RUN docker-php-ext-install pdo_mysql opcache\n\n # Set working directory\n WORKDIR /var/www/html\n\n # Copy composer.lock and composer.json\n COPY composer.json composer.lock ./\n\n # Install composer dependencies\n RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer\n RUN composer install --no-dev --optimize-autoloader --no-interaction\n\n # Copy the rest of the application code\n COPY . .\n\n # Generate application key\n RUN php artisan key:generate --ansi\n\n # Expose port\n EXPOSE 80\n\n # Start PHP-FPM and Nginx\n CMD php-fpm && nginx -g "daemon off;"\nExample docker-compose.yml (for local development)
\n version: '3.8'\n\n services:\n app:\n build:\n context: .\n dockerfile: Dockerfile\n ports:\n - "8000:80"\n volumes:\n - .:/var/www/html\n environment:\n DB_CONNECTION: mysql\n DB_HOST: db\n DB_PORT: 3306\n DB_DATABASE: laravel\n DB_USERNAME: root\n DB_PASSWORD: password\n\n db:\n image: mysql:8.0\n environment:\n MYSQL_DATABASE: laravel\n MYSQL_ROOT_PASSWORD: password\n ports:\n - "3306:3306"\n volumes:\n - db_data:/var/lib/mysql\n\n volumes:\n db_data:\n---
\nStep 2: Setting up GitHub Actions Workflow
\nCreate a .github/workflows/main.yml file in your repository. This file defines the CI/CD workflow.
Basic CI Workflow (Testing and Building)
\n name: Laravel CI/CD\n\n on:\n push:\n branches:\n - main\n pull_request:\n branches:\n - main\n\n jobs:\n build-and-test:\n runs-on: ubuntu-latest\n steps:\n - name: Checkout code\n uses: actions/checkout@v3\n\n - name: Set up PHP\n uses: shivammathur/setup-php@v2\n with:\n php-version: '8.2'\n extensions: pdo_mysql, mbstring, dom, filter, gd, curl, json, iconv, imagick, zip\n ini-values: post_max_size=256M, upload_max_filesize=256M\n tools: composer, phpunit\n\n - name: Install Composer Dependencies\n run: composer install --no-dev --prefer-dist --no-interaction\n\n - name: Create .env file\n run: cp .env.example .env\n\n - name: Generate Application Key\n run: php artisan key:generate\n\n - name: Run Migrations\n run: php artisan migrate --force --seed --env=testing\n\n - name: Run PHPUnit Tests\n run: vendor/bin/phpunit\n\n - name: Build Docker Image\n run: docker build -t your-dockerhub-username/your-laravel-app:latest .\n # Replace with your Docker Hub username and app name\n\n - name: Log in to Docker Hub\n uses: docker/login-action@v2\n with:\n username: ${{ secrets.DOCKER_USERNAME }}\n password: ${{ secrets.DOCKER_PASSWORD }}\n\n - name: Push Docker Image\n run: docker push your-dockerhub-username/your-laravel-app:latest\nExplanation:
\n- \n
on: pushandpull_request: Triggers the workflow on pushes tomainand all pull requests targetingmain. \nbuild-and-testjob: \nCheckout code: Fetches your repository. \nSet up PHP: Configures the PHP environment with necessary extensions and tools. \nInstall Composer Dependencies: Installs your project''s PHP dependencies. \n.envandkey:generate: Essential Laravel setup. \nRun Migrations: Migrates your database schema for testing. For actual deployments, you''ll run this on the server. \nRun PHPUnit Tests: Executes your application''s tests. \nBuild Docker Image: Creates a Docker image of your application. \nLog in to Docker Hub: Authenticates with Docker Hub using secrets (set these in your GitHub repository settings). \nPush Docker Image: Pushes the built image to your Docker Hub repository. \n
---
\nStep 3: Deployment Strategy (Example: SSH to VPS)
\nFor deployment, you can extend your GitHub Actions workflow to connect to a server (e.g., a VPS) and pull the latest Docker image.
\nAdd Deployment Step
\n # ... (previous build-and-test job)\n\n deploy:\n needs: build-and-test # This job depends on the success of build-and-test\n runs-on: ubuntu-latest\n environment:\n name: production # Good practice to define environments\n steps:\n - name: Deploy to Server\n uses: appleboy/ssh-action@master\n with:\n host: ${{ secrets.SSH_HOST }}\n username: ${{ secrets.SSH_USERNAME }}\n key: ${{ secrets.SSH_PRIVATE_KEY }}\n script: |\n cd /var/www/your-laravel-app # Your application directory on the server\n docker pull your-dockerhub-username/your-laravel-app:latest\n docker-compose down # Stop existing containers\n docker-compose up -d # Start new containers\n docker system prune -f # Clean up old Docker images\n php artisan migrate --force # Run migrations on production\n php artisan cache:clear\n php artisan config:clear\n php artisan route:clear\n php artisan view:clear\n # Add any other post-deployment commands like queue restarts\nImportant Secrets:
\n- \n
DOCKER_USERNAME: Your Docker Hub username. \nDOCKER_PASSWORD: Your Docker Hub password or access token. \nSSH_HOST: IP address or hostname of your server. \nSSH_USERNAME: SSH username for your server. \nSSH_PRIVATE_KEY: Your SSH private key (ensure it''s secured and does not have a passphrase in CI). \n
How to set up secrets in GitHub:
\nGo to your GitHub repository -> Settings -> Security -> Secrets and variables -> Actions -> New repository secret.
\n---
\nStep 4: Environment Management
\n- \n
.envfiles: Have different.envfiles for local, testing, and production environments. Never commit sensitive production credentials to your repository. \n- GitHub Secrets: Use GitHub Secrets for sensitive information like database credentials, API keys, and SSH private keys. \n
- Docker Environment Variables: Pass environment variables to your Docker containers. \n
---
\nDatabase Migrations
\n- \n
- CI: Run
php artisan migrate --force --seed --env=testingto prepare the database for tests. \n - CD (Production): Run
php artisan migrate --forceafter deploying new code to apply schema changes. Consider using a deployment user with minimal database privileges. \n
---
\nRollback Strategy
\nWhile not explicitly covered, a good CI/CD pipeline includes a rollback strategy.
\n- \n
- Docker Tagging: Instead of always using
latest, tag your Docker images with Git commit SHAs or version numbers. This allows you to easilydocker pullan older, stable version if a deployment fails. \n - Blue/Green or Canary Deployments: For more complex setups, explore advanced deployment strategies. \n
---
\nConclusion
\nBy implementing a CI/CD pipeline with GitHub Actions and Docker, you can significantly streamline your Laravel development and deployment process. This automation leads to more reliable code, faster releases, and greater confidence in your application''s stability. Continuously refine your pipeline as your project evolves, adding more automated checks and deployment safeguards.