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 addressSSH_PRIVATE_KEY
: Your private SSH key contentGIT_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
- Always backup critical files before deployment
- Use environment-specific configurations for production
- Implement proper error handling in your deployment script
- Monitor your deployments with logging
- Use SSH keys instead of passwords for authentication
- Keep your dependencies updated regularly
- 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