SSH-Based CI/CD for Laravel Applications on Dedicated VPS: A Complete Guide

·

·

In this guide, we'll walk through setting up a robust SSH-based CI/CD pipeline for Laravel applications on a dedicated VPS server. This approach is more reliable than FTP-based deployments and provides better security and performance.

Prerequisites

  • Ubuntu 24.04 VPS with root access
  • Laravel application hosted on the VPS
  • GitHub repository with your Laravel code
  • Basic knowledge of SSH and Linux commands

Step 1: Create SSH User with Sudo Permissions

First, create a dedicated user for CI/CD deployments:

# Create a new user
sudo adduser deployuser

# Add to sudo group
sudo usermod -aG sudo deployuser

# Switch to the new user
sudo su - deployuser

# Create SSH directory
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Generate SSH keys on your local machine:

# On your local machine (Mac/Linux)
ssh-keygen -t rsa -b 4096

# Copy the public key
cat ~/.ssh/id_rsa.pub

Add your public key to the server’s authorized_keys file:

# On the server as deployuser
nano ~/.ssh/authorized_keys
# Paste your public key and save

Step 2: Configure PostgreSQL for Remote Access

# Edit PostgreSQL configuration
sudo nano /etc/postgresql/16/main/postgresql.conf

# Set listen_addresses to accept connections
listen_addresses = '*'

# Edit authentication file
sudo nano /etc/postgresql/16/main/pg_hba.conf

# Add this line to allow connections from any IP
host    all             all             0.0.0.0/0               md5

# Restart PostgreSQL
sudo systemctl restart postgresql

Open the PostgreSQL port in the firewall:

sudo ufw allow 5432/tcp

Step 3: Set Up Laravel Environment

Create a proper .env file for your Laravel application:

cd /path/to/your/laravel/app

# Create or update .env file
nano .env

Example .env configuration:

APP_NAME=Laravel
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=your_database
DB_USERNAME=postgres
DB_PASSWORD=your_password

BROADCAST_DRIVER=log
CACHE_DRIVER=redis
QUEUE_CONNECTION=sync
SESSION_DRIVER=redis

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Step 4: Configure GitHub Actions Workflow

Create a GitHub Actions workflow file at .github/workflows/deploy.yml:

name: Deploy Application

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    name: Deploy to Production
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: deployuser
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          command_timeout: "30m"
          script: |
            set -e
            
            # Navigate to application directory
            cd /var/www/your-app
            
            # Backup critical files
            BACKUP_DIR="/home/deployuser/backup"
            mkdir -p "$BACKUP_DIR"
            
            if [ -f ".env" ]; then
              cp .env "$BACKUP_DIR/.env"
            fi
            
            if [ -d "storage" ]; then
              cp -r storage "$BACKUP_DIR/"
            fi
            
            # Update repository
            if [ -d ".git" ]; then
              git reset --hard HEAD
              git clean -fd
              git fetch origin
              git checkout main
              git pull origin main
            else
              git clone https://${{ secrets.GIT_ACCESS_TOKEN }}@github.com/username/repository.git .
              git checkout main
            fi
            
            # Restore backups
            if [ -f "$BACKUP_DIR/.env" ]; then
              cp "$BACKUP_DIR/.env" .env
            fi
            
            if [ -d "$BACKUP_DIR/storage" ]; then
              rm -rf storage
              cp -r "$BACKUP_DIR/storage" storage
            fi
            
            # Install dependencies
            composer install --no-dev --optimize-autoloader
            
            # Laravel commands
            php artisan optimize:clear
            php artisan config:cache
            php artisan view:cache
            php artisan migrate --force
            
            # Set permissions
            sudo chown -R www-data:www-data storage bootstrap/cache
            sudo chmod -R 775 storage bootstrap/cache
            
            # Clean up
            rm -rf "$BACKUP_DIR"
            
            echo "Deployment completed successfully!"

Step 5: Handle Private Composer Packages

If your Laravel application uses private Composer packages, configure authentication:

# Add this before composer install in your workflow
- name: Configure Composer for private repositories
  run: |
    composer config -g github-oauth.github.com ${{ secrets.GIT_ACCESS_TOKEN }}
    git config --global url."https://${{ secrets.GIT_ACCESS_TOKEN }}@github.com/".insteadOf "git@github.com:"

Step 6: Set Up GitHub Secrets

In your GitHub repository, go to Settings → Secrets and variables → Actions and add:

  • SERVER_IP: Your VPS IP address
  • SSH_PRIVATE_KEY: Your private SSH key content
  • GIT_ACCESS_TOKEN: GitHub personal access token with repo scope

Step 7: Optimize File Permissions

Set up proper permissions for your Laravel application:

# On the server
sudo chown -R deployuser:www-data /var/www/your-app
sudo chmod -R 775 /var/www/your-app/storage
sudo chmod -R 775 /var/www/your-app/bootstrap/cache

Troubleshooting Common Issues

1. SSH Connection Timeout

If you experience SSH timeouts, add this to your SSH config:

# In your GitHub Actions workflow
- name: Deploy via SSH
  uses: appleboy/ssh-action@master
  with:
    host: ${{ secrets.SERVER_IP }}
    username: deployuser
    key: ${{ secrets.SSH_PRIVATE_KEY }}
    port: 22
    command_timeout: "30m"
    timeout: "30m"

2. Database Connection Issues

Ensure your .env file has the correct database credentials and that PostgreSQL is configured to accept connections from your application.

3. Composer Private Repository Access

For private repositories, make sure your GitHub token has the correct permissions and that you’ve configured Composer authentication properly.

Best Practices

  1. Always backup critical files before deployment
  2. Use environment-specific configurations for production
  3. Implement proper error handling in your deployment script
  4. Monitor your deployments with logging
  5. Use SSH keys instead of passwords for authentication
  6. Keep your dependencies updated regularly
  7. Test deployments in a staging environment first

Conclusion

SSH-based CI/CD provides a more reliable and secure way to deploy Laravel applications compared to FTP-based solutions. With proper configuration and best practices, you can automate your deployment process while maintaining security and stability.

This approach gives you full control over your deployment process, allows for better error handling, and integrates seamlessly with your existing development workflow.


Leave a Reply

Your email address will not be published. Required fields are marked *