Configuring Automatic Website Deployment
Automatic deployment is a process where code changes automatically reach the server after passing CI checks. It eliminates manual operations, reduces human error risk, and accelerates change delivery.
Automatic Deployment Variants
Push-based — the CI/CD system connects to the server and performs deployment itself. Pull-based — the server periodically checks and pulls changes (GitOps approach).
GitHub Actions → SSH Deployment
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm ci && npm run build
- name: Deploy via SSH
uses: appleboy/[email protected]
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/myapp
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan queue:restart
sudo systemctl reload php8.3-fpm
Deployer (PHP)
Deployer is a tool for atomic deployment with rollback capability:
// deploy.php
namespace Deployer;
require 'recipe/laravel.php';
host('production')
->set('hostname', 'your-server.com')
->set('remote_user', 'deploy')
->set('deploy_path', '/var/www/myapp')
->set('branch', 'main');
host('staging')
->set('hostname', 'staging.example.com')
->set('remote_user', 'deploy')
->set('deploy_path', '/var/www/staging')
->set('branch', 'develop');
set('shared_files', ['.env']);
set('shared_dirs', ['storage']);
set('writable_dirs', ['bootstrap/cache', 'storage']);
set('keep_releases', 5);
after('deploy:failed', 'deploy:unlock');
task('deploy:migrate', function () {
run('cd {{release_path}} && php artisan migrate --force');
});
after('deploy:vendors', 'deploy:migrate');
# Deploy
dep deploy production
# Rollback
dep rollback production
Webhook Deployment
For simple cases, use a webhook on the server:
// deploy.php (on server)
<?php
$secret = getenv('DEPLOY_SECRET');
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!hash_equals("sha256=" . hash_hmac('sha256', file_get_contents('php://input'), $secret), $signature)) {
http_response_code(403);
exit;
}
$output = shell_exec('cd /var/www/myapp && git pull && composer install --no-dev && php artisan migrate --force 2>&1');
echo $output;
GitHub → Repository Settings → Webhooks → Add webhook → URL of your deploy.php.
Zero-Downtime Deployment via Symlink
# Directory structure
/var/www/myapp/
├── current -> releases/20241115143022 # symlink to active release
├── releases/
│ ├── 20241115143022/ # current
│ ├── 20241115120000/ # previous
│ └── 20241114090000/ # old
├── shared/
│ ├── .env
│ └── storage/
#!/bin/bash
# deploy.sh
DEPLOY_DIR=/var/www/myapp
RELEASE_DIR=$DEPLOY_DIR/releases/$(date +%Y%m%d%H%M%S)
# 1. Download new code
git clone --depth=1 https://github.com/user/repo.git $RELEASE_DIR
# 2. Link shared resources
ln -s $DEPLOY_DIR/shared/.env $RELEASE_DIR/.env
ln -s $DEPLOY_DIR/shared/storage $RELEASE_DIR/storage
# 3. Install dependencies
cd $RELEASE_DIR && composer install --no-dev --optimize-autoloader
# 4. Switch symlink (atomic)
ln -sfn $RELEASE_DIR $DEPLOY_DIR/current
# 5. Reload services
sudo systemctl reload php8.3-fpm
# 6. Remove old releases (keep 5)
ls -t $DEPLOY_DIR/releases | tail -n +6 | xargs -I{} rm -rf $DEPLOY_DIR/releases/{}
Deployment Notifications
# GitHub Actions — Slack notification
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: "Deploy to production: ${{ job.status }}"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Implementation Timeline
Basic auto-deployment via SSH/GitHub Actions: 1 day. Deployer with zero-downtime and rollback: 2–3 days.







