Automating Laravel 11 Deployment with GitHub Actions: A Comprehensive Guide

Automating Laravel 11 Deployment with GitHub Actions: A Comprehensive Guide

Featured on Hashnode

Deploy with Github Actions

In modern web development, automated deployment pipelines are crucial for maintaining efficient and reliable software delivery. Today, we'll explore a robust GitHub Actions workflow designed specifically for Laravel 11 applications. This workflow automates testing and deployment processes, ensuring code quality and smooth deployments to development servers.

TL;DR;

Here is the gist: https://gist.github.com/sohag-pro/31d914fb56eb8a34d31eae82341571e6

Complete Workflow Code

First, let's look at the complete workflow file that should be placed in .github/workflows/deploy.yml:

name: Deploy to dev server

on:
  push:
    branches: [ develop ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

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

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: 8.2

    - name: Copy .env
      run: php -r "file_exists('.env') || copy('.env.example', '.env');"

    - name: Install Dependencies
      run: composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-req=ext-imagick

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

    - name: Directory Permissions
      run: chmod -R 777 storage bootstrap/cache

    - name: Create Database
      run: |
        mkdir -p database
        touch database/database.sqlite
    - name: Migrate
      env:
        DB_CONNECTION: sqlite
        DB_CONNECTION_LOG: sqlite
        DB_DATABASE: database/database.sqlite
        DB_LOG_DATABASE: database/database.sqlite
      run: php artisan migrate

    - name: Passport Install
      env:
        DB_CONNECTION: sqlite
        DB_DATABASE: database/database.sqlite
      run: php artisan passport:install

    - name: Clear Cache
      run: php artisan optimize:clear

    - name: Run tests
      env:
        DB_CONNECTION: sqlite
        DB_CONNECTION_LOG: sqlite
        DB_DATABASE: database/database.sqlite
        DB_LOG_DATABASE: database/database.sqlite
      run: php artisan test

  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest

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

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: 8.2

    - name: Install Dependencies
      run: composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-req=ext-imagick
    - name: Directory Permissions
      run: chmod -R 777 storage bootstrap/cache deploy

    - name: Pre Deploy
      uses: appleboy/ssh-action@master
      with:
          username: ${{ secrets.DEV_REMOTE_USER }}
          host: ${{ secrets.DEV_REMOTE_HOST }}
          key: ${{ secrets.DEV_SSH_PRIVATE_KEY }}
          script: |
              cd ${{ secrets.DEV_REMOTE_TARGET }} && php artisan down

    - name: Deploy to Server
      uses: easingthemes/ssh-deploy@main
      with:
          SSH_PRIVATE_KEY: ${{ secrets.DEV_SSH_PRIVATE_KEY }}
          ARGS: "-rlgoDzvc -i --delete"
          SOURCE: "/"
          REMOTE_HOST: ${{ secrets.DEV_REMOTE_HOST }}
          REMOTE_USER: ${{ secrets.DEV_REMOTE_USER }}
          TARGET: ${{ secrets.DEV_REMOTE_TARGET }}
          EXCLUDE: "/storage/, /bootstrap/, .env"

    - name: Post Deploy
      uses: appleboy/ssh-action@master
      with:
          username: ${{ secrets.DEV_REMOTE_USER }}
          host: ${{ secrets.DEV_REMOTE_HOST }}
          key: ${{ secrets.DEV_SSH_PRIVATE_KEY }}
          script: |
              cd ${{ secrets.DEV_REMOTE_TARGET }} && ./deploy/after.sh

Workflow Components Breakdown

1. Workflow Trigger

on:
  push:
    branches: [ develop ]

This section defines when the workflow runs - in this case, whenever code is pushed to the develop branch.

2. Build and Test Job

Environment Setup

build-and-test:
  runs-on: ubuntu-latest
  steps:
  - name: Checkout code
    uses: actions/checkout@v3
    with:
      ref: develop
  - name: Setup PHP
    uses: shivammathur/setup-php@v2
    with:
      php-version: 8.2

This sets up a fresh Ubuntu environment and configures PHP 8.2 for Laravel 11.

Application Setup

- name: Copy .env
  run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Install Dependencies
  run: composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-req=ext-imagick
- name: Generate key
  run: php artisan key:generate
- name: Directory Permissions
  run: chmod -R 777 storage bootstrap/cache

These steps prepare the Laravel application by:

  1. Creating the environment file

  2. Installing dependencies

  3. Generating the application key

  4. Setting proper directory permissions

Database Setup and Testing

- name: Create Database
  run: |
    mkdir -p database
    touch database/database.sqlite
- name: Migrate
  env:
    DB_CONNECTION: sqlite
    DB_CONNECTION_LOG: sqlite
    DB_DATABASE: database/database.sqlite
    DB_LOG_DATABASE: database/database.sqlite
  run: php artisan migrate

This section:

  1. Creates a SQLite database for testing

  2. Sets up environment variables

  3. Runs migrations

3. Deployment Job

Pre-deployment Setup

deploy:
  needs: build-and-test
  runs-on: ubuntu-latest
  steps:
  - name: Checkout code
    uses: actions/checkout@v3
    with:
      ref: develop

The deployment job only runs after successful build and testing.

Deployment Process

- name: Pre Deploy
  uses: appleboy/ssh-action@master
  with:
      username: ${{ secrets.DEV_REMOTE_USER }}
      host: ${{ secrets.DEV_REMOTE_HOST }}
      key: ${{ secrets.DEV_SSH_PRIVATE_KEY }}
      script: |
          cd ${{ secrets.DEV_REMOTE_TARGET }} && php artisan down

- name: Deploy to Server
  uses: easingthemes/ssh-deploy@main
  with:
      SSH_PRIVATE_KEY: ${{ secrets.DEV_SSH_PRIVATE_KEY }}
      ARGS: "-rlgoDzvc -i --delete"
      SOURCE: "/"
      REMOTE_HOST: ${{ secrets.DEV_REMOTE_HOST }}
      REMOTE_USER: ${{ secrets.DEV_REMOTE_USER }}
      TARGET: ${{ secrets.DEV_REMOTE_TARGET }}
      EXCLUDE: "/storage/, /bootstrap/, .env"

4. Required Server Setup

To make this workflow function properly, you'll need to set up the following on your server:

  1. Create the deploy directory with an after.sh script:
#!/bin/bash
# after.sh - Place this in your deploy directory

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

# Clear caches
php artisan cache:clear
php artisan config:clear
php artisan view:clear
php artisan route:clear

# Restart queue workers
php artisan queue:restart

# Run migrations
php artisan migrate --force

# Clear optimizations
php artisan optimize:clear

# Rebuild cache
php artisan optimize

# Bring application back online
php artisan up

5. GitHub Secrets Setup

In your GitHub repository settings, add these secrets:

DEV_REMOTE_USER=your-server-username
DEV_REMOTE_HOST=your-server-hostname
DEV_SSH_PRIVATE_KEY=your-ssh-private-key
DEV_REMOTE_TARGET=/path/to/your/application

Best Practices Implemented

  1. Separation of Concerns: Build/test and deployment are separate jobs

  2. Environment Isolation: Uses fresh Ubuntu instance for each run

  3. Security: Sensitive data stored in GitHub Secrets

  4. Zero-downtime Deployment: Uses maintenance mode during deployment

  5. Artifact Management: Proper exclusion of environment-specific files

  6. Error Prevention: Comprehensive testing before deployment

Troubleshooting Common Issues

  1. SSH Key Issues
# Generate new SSH key pair
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"

# Add public key to server's authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

# Add private key to GitHub secrets
cat ~/.ssh/id_rsa
  1. Permission Issues
# On your server, ensure proper ownership
sudo chown -R www-data:www-data /path/to/your/application
sudo chmod -R 775 /path/to/your/application/storage
sudo chmod -R 775 /path/to/your/application/bootstrap/cache

Conclusion

This GitHub Actions workflow provides a robust, automated pipeline for Laravel 11 applications. It ensures code quality through automated testing and provides a reliable deployment process to development servers. By implementing this workflow, teams can focus more on development while having confidence in their deployment process.

The workflow can be further customized based on specific needs, such as:

  • Adding additional testing stages

  • Implementing staging environments

  • Adding notification systems

  • Including code quality checks

Remember to always test your deployment workflow thoroughly in a safe environment before implementing it in production scenarios.