Moderators Gani Posted January 14 Moderators Share Posted January 14 📌 Complete Architecture Quote PHP Application (Laravel/CodeIgniter/Custom PHP) ├── Frontend: HTML, CSS, JavaScript ├── Backend: PHP ├── Database: MySQL └── CI/CD: GitHub Actions + Deployment Scripts Step 1: Project Structure 1.1 Sample Project Layout Quote my-php-app/ ├── public/ │ ├── index.php │ ├── css/ │ ├── js/ │ └── images/ ├── src/ │ ├── controllers/ │ ├── models/ │ ├── views/ │ └── lib/ ├── database/ │ ├── migrations/ │ └── seeds/ ├── tests/ │ ├── unit/ │ └── integration/ ├── .env.example ├── composer.json ├── .htaccess ├── .github/ │ └── workflows/ │ └── php-ci-cd.yml ├── scripts/ │ ├── deploy.sh │ └── database.sh └── README.md 1.2 Sample Files public/index.php: Quote <?php require_once __DIR__ . '/../vendor/autoload.php'; // Database connection $host = getenv('DB_HOST') ?: 'localhost'; $dbname = getenv('DB_NAME') ?: 'myapp'; $username = getenv('DB_USER') ?: 'root'; $password = getenv('DB_PASSWORD') ?: ''; try { $pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password); echo "Database connected successfully!"; } catch (PDOException $e) { echo "Connection failed: " . $e->getMessage(); } // Your application logic here ?> composer.json: Quote { "name": "yourname/my-php-app", "description": "PHP Application with MySQL", "require": { "php": ">=7.4", "ext-pdo": "*" }, "require-dev": { "phpunit/phpunit": "^9.0", "squizlabs/php_codesniffer": "^3.0" }, "autoload": { "psr-4": { "App\\": "src/" } }, "scripts": { "test": "phpunit", "lint": "phpcs --standard=PSR12 src/ tests/", "fix": "phpcbf --standard=PSR12 src/ tests/" } } Step 2: Complete CI/CD Pipeline with GitHub Actions 2.1 Full Pipeline Configuration Create .github/workflows/php-ci-cd.yml: Quote name: PHP CI/CD Pipeline on: push: branches: [ main, master, develop ] pull_request: branches: [ main, master ] jobs: # Job 1: Continuous Integration (Testing) ci: runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: test_db ports: - 3306:3306 options: >- --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.1' extensions: mbstring, xml, mysql, pdo, pdo_mysql coverage: xdebug - name: Validate composer.json run: composer validate - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest - name: Copy environment file run: cp .env.example .env - name: Setup database run: | mysql --host 127.0.0.1 --port 3306 -uroot -proot -e "CREATE DATABASE IF NOT EXISTS test_db;" mysql --host 127.0.0.1 --port 3306 -uroot -proot -e "CREATE DATABASE IF NOT EXISTS app_db;" - name: Run database migrations run: | php scripts/database.sh migrate test - name: Run PHP Code Sniffer run: composer lint - name: Run PHPUnit tests run: composer test env: DB_HOST: 127.0.0.1 DB_PORT: 3306 DB_DATABASE: test_db DB_USERNAME: root DB_PASSWORD: root - name: Run security check uses: symfonycorp/security-checker-action@v5 - name: Upload test results uses: actions/upload-artifact@v3 if: always() with: name: test-results path: tests/report/ # Job 2: Build and Package build: runs-on: ubuntu-latest needs: ci if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.1' - name: Install dependencies (no dev) run: composer install --no-dev --prefer-dist --optimize-autoloader - name: Create deployment package run: | mkdir -p deployment_package # Copy necessary files (exclude development files) rsync -av --exclude='.git' \ --exclude='.github' \ --exclude='tests' \ --exclude='*.md' \ --exclude='.env' \ --exclude='composer.json' \ --exclude='composer.lock' \ --exclude='phpunit.xml' \ . deployment_package/ - name: Upload deployment package uses: actions/upload-artifact@v3 with: name: php-deployment-package path: deployment_package/ # Job 3: Deploy to Staging deploy-staging: runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/develop' environment: staging steps: - name: Download deployment package uses: actions/download-artifact@v3 with: name: php-deployment-package - name: Deploy to staging server uses: appleboy/scp-action@master with: host: ${{ secrets.STAGING_HOST }} username: ${{ secrets.STAGING_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} source: "*" target: "/var/www/staging.myapp.com" strip_components: 1 - name: Run deployment script on staging uses: appleboy/ssh-action@master with: host: ${{ secrets.STAGING_HOST }} username: ${{ secrets.STAGING_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /var/www/staging.myapp.com chmod +x scripts/deploy.sh ./scripts/deploy.sh staging - name: Run smoke test on staging uses: jawnsy/action-http-request@v1 with: url: "https://staging.myapp.com" method: "GET" # Job 4: Deploy to Production (with approval) deploy-production: runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' environment: production steps: - name: Download deployment package uses: actions/download-artifact@v3 with: name: php-deployment-package - name: Wait for manual approval uses: trstringer/manual-approval@v1 with: secret: ${{ github.TOKEN }} approvers: ${{ secrets.APPROVERS }} minimum-approvals: 1 - name: Backup production database uses: appleboy/ssh-action@master with: host: ${{ secrets.PRODUCTION_HOST }} username: ${{ secrets.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /var/www/myapp.com ./scripts/database.sh backup - name: Deploy to production server uses: appleboy/scp-action@master with: host: ${{ secrets.PRODUCTION_HOST }} username: ${{ secrets.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} source: "*" target: "/var/www/myapp.com" strip_components: 1 - name: Run deployment script on production uses: appleboy/ssh-action@master with: host: ${{ secrets.PRODUCTION_HOST }} username: ${{ secrets.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /var/www/myapp.com chmod +x scripts/deploy.sh ./scripts/deploy.sh production - name: Run smoke test on production uses: jawnsy/action-http-request@v1 with: url: "https://myapp.com" method: "GET" - name: Notify deployment success if: success() uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} SLACK_CHANNEL: deployments SLACK_MESSAGE: "✅ Production deployment successful! ${{ github.repository }}" - name: Rollback on failure if: failure() uses: appleboy/ssh-action@master with: host: ${{ secrets.PRODUCTION_HOST }} username: ${{ secrets.PRODUCTION_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /var/www/myapp.com ./scripts/deploy.sh rollback Step 3: Deployment Scripts 3.1 Main Deployment Script (scripts/deploy.sh) Quote #!/bin/bash # scripts/deploy.sh set -e # Exit on any error ENVIRONMENT=$1 TIMESTAMP=$(date +%Y%m%d%H%M%S) BACKUP_DIR="/var/backups/myapp" DEPLOY_DIR="/var/www/myapp.com" echo "🚀 Starting deployment to $ENVIRONMENT..." # Load environment-specific configuration if [ "$ENVIRONMENT" = "production" ]; then CONFIG_FILE=".env.production" SITE_URL="https://myapp.com" elif [ "$ENVIRONMENT" = "staging" ]; then CONFIG_FILE=".env.staging" SITE_URL="https://staging.myapp.com" else echo "❌ Unknown environment: $ENVIRONMENT" exit 1 fi # Function for rollback rollback() { echo "⚠️ Rolling back deployment..." if [ -d "$DEPLOY_DIR/previous" ]; then rm -rf "$DEPLOY_DIR/current.broken" mv "$DEPLOY_DIR/current" "$DEPLOY_DIR/current.broken" mv "$DEPLOY_DIR/previous" "$DEPLOY_DIR/current" sudo systemctl restart php8.1-fpm sudo systemctl restart nginx echo "✅ Rollback completed" else echo "❌ No previous version to rollback to" exit 1 fi } # Create backup directory mkdir -p "$BACKUP_DIR" # Step 1: Backup current version if [ -d "$DEPLOY_DIR/current" ]; then echo "📦 Backing up current version..." cp -r "$DEPLOY_DIR/current" "$BACKUP_DIR/backup_$TIMESTAMP" mv "$DEPLOY_DIR/current" "$DEPLOY_DIR/previous" fi # Step 2: Prepare new version echo "🛠️ Preparing new version..." mkdir -p "$DEPLOY_DIR/current" # Copy all files (excluding development files) rsync -av --exclude='.git' \ --exclude='.github' \ --exclude='tests' \ --exclude='*.md' \ --exclude='composer.json' \ --exclude='composer.lock' \ --exclude='phpunit.xml' \ . "$DEPLOY_DIR/current/" # Step 3: Set up environment echo "⚙️ Setting up environment..." if [ -f "$CONFIG_FILE" ]; then cp "$CONFIG_FILE" "$DEPLOY_DIR/current/.env" else echo "⚠️ Config file $CONFIG_FILE not found, using default .env" if [ -f ".env" ]; then cp ".env" "$DEPLOY_DIR/current/.env" fi fi # Step 4: Set permissions echo "🔐 Setting permissions..." chmod -R 755 "$DEPLOY_DIR/current" chmod -R 775 "$DEPLOY_DIR/current/storage" 2>/dev/null || true chmod -R 775 "$DEPLOY_DIR/current/cache" 2>/dev/null || true chmod 644 "$DEPLOY_DIR/current/.env" # Change ownership to web server user chown -R www-data:www-data "$DEPLOY_DIR/current" # Step 5: Database migrations echo "🗄️ Running database migrations..." cd "$DEPLOY_DIR/current" php scripts/database.sh migrate $ENVIRONMENT # Step 6: Composer install (if needed) if [ -f "composer.json" ]; then echo "📦 Installing PHP dependencies..." composer install --no-dev --optimize-autoloader fi # Step 7: Clear caches echo "🧹 Clearing caches..." # Clear opcache sudo service php8.1-fpm reload # Clear application caches rm -rf storage/cache/* 2>/dev/null || true rm -rf cache/* 2>/dev/null || true # Step 8: Update symlinks echo "🔗 Updating symlinks..." ln -sfn "$DEPLOY_DIR/current" "$DEPLOY_DIR/live" # Step 9: Restart services echo "🔄 Restarting services..." sudo systemctl restart php8.1-fpm sudo systemctl restart nginx # Step 10: Run health check echo "🏥 Running health check..." sleep 5 # Wait for services to start HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$SITE_URL/health") if [ "$HTTP_STATUS" = "200" ]; then echo "✅ Deployment successful! Health check passed." # Clean up old backups (keep last 5) ls -dt $BACKUP_DIR/* | tail -n +6 | xargs rm -rf # Remove previous version if everything is OK rm -rf "$DEPLOY_DIR/previous" else echo "❌ Health check failed! HTTP Status: $HTTP_STATUS" rollback exit 1 fi echo "🎉 Deployment to $ENVIRONMENT completed successfully!" 3.2 Database Management Script (scripts/database.sh) Quote #!/bin/bash # scripts/database.sh ACTION=$1 ENVIRONMENT=$2 # Database configurations case $ENVIRONMENT in production) DB_HOST="localhost" DB_NAME="myapp_prod" DB_USER="myapp_user" DB_PASS_FILE="/etc/mysql/myapp_prod.pass" ;; staging) DB_HOST="localhost" DB_NAME="myapp_staging" DB_USER="myapp_staging_user" DB_PASS_FILE="/etc/mysql/myapp_staging.pass" ;; test) DB_HOST="localhost" DB_NAME="test_db" DB_USER="root" DB_PASS="root" ;; *) echo "Unknown environment" exit 1 ;; esac # Read password from file if not test if [ "$ENVIRONMENT" != "test" ]; then if [ -f "$DB_PASS_FILE" ]; then DB_PASS=$(cat "$DB_PASS_FILE") else echo "Password file not found: $DB_PASS_FILE" exit 1 fi fi # Function to run MySQL command mysql_cmd() { mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$1" } case $ACTION in migrate) echo "Running database migrations..." # Create migrations table if not exists mysql_cmd "CREATE TABLE IF NOT EXISTS migrations ( id INT AUTO_INCREMENT PRIMARY KEY, migration VARCHAR(255) NOT NULL, batch INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );" # Run each migration file for file in database/migrations/*.sql; do if [ -f "$file" ]; then MIGRATION_NAME=$(basename "$file" .sql) # Check if migration already run RESULT=$(mysql_cmd "SELECT COUNT(*) FROM migrations WHERE migration = '$MIGRATION_NAME'" | tail -1) if [ "$RESULT" -eq 0 ]; then echo "Running migration: $MIGRATION_NAME" mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < "$file" if [ $? -eq 0 ]; then mysql_cmd "INSERT INTO migrations (migration, batch) VALUES ('$MIGRATION_NAME', 1);" echo "✅ Migration completed: $MIGRATION_NAME" else echo "❌ Migration failed: $MIGRATION_NAME" exit 1 fi else echo "⏭️ Migration already applied: $MIGRATION_NAME" fi fi done ;; backup) echo "Backing up database..." BACKUP_FILE="/var/backups/myapp/db_backup_$(date +%Y%m%d%H%M%S).sql" mysqldump -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BACKUP_FILE" gzip "$BACKUP_FILE" echo "✅ Backup created: ${BACKUP_FILE}.gz" # Clean old backups (keep last 10) ls -t /var/backups/myapp/db_backup_*.sql.gz | tail -n +11 | xargs rm -f ;; seed) echo "Seeding database..." for file in database/seeds/*.sql; do if [ -f "$file" ]; then echo "Running seed: $(basename $file)" mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < "$file" fi done ;; status) echo "Database status:" mysql_cmd "SELECT migration, created_at FROM migrations ORDER BY id DESC LIMIT 5;" mysql_cmd "SELECT TABLE_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '$DB_NAME';" ;; *) echo "Usage: $0 {migrate|backup|seed|status} {production|staging|test}" exit 1 ;; esac 3.3 Sample Database Migration File Quote -- database/migrations/001_create_users_table.sql CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS posts ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; Step 4: Server Setup Scripts 4.1 Server Provisioning Script (scripts/setup-server.sh) Quote #!/bin/bash # scripts/setup-server.sh - Run this on new server set -e echo "🖥️ Setting up PHP/MySQL server..." # Update system apt-get update apt-get upgrade -y # Install PHP and extensions apt-get install -y \ php8.1 \ php8.1-fpm \ php8.1-mysql \ php8.1-mbstring \ php8.1-xml \ php8.1-curl \ php8.1-zip \ php8.1-gd \ php8.1-opcache # Install MySQL apt-get install -y mysql-server # Install Nginx apt-get install -y nginx # Install Composer curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer chmod +x /usr/local/bin/composer # Configure MySQL mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_secure_password';" mysql -e "FLUSH PRIVILEGES;" # Create database users mysql -e "CREATE DATABASE IF NOT EXISTS myapp_prod;" mysql -e "CREATE DATABASE IF NOT EXISTS myapp_staging;" mysql -e "CREATE USER IF NOT EXISTS 'myapp_user'@'localhost' IDENTIFIED BY '$(openssl rand -base64 32)';" mysql -e "CREATE USER IF NOT EXISTS 'myapp_staging_user'@'localhost' IDENTIFIED BY '$(openssl rand -base64 32)';" mysql -e "GRANT ALL PRIVILEGES ON myapp_prod.* TO 'myapp_user'@'localhost';" mysql -e "GRANT ALL PRIVILEGES ON myapp_staging.* TO 'myapp_staging_user'@'localhost';" mysql -e "FLUSH PRIVILEGES;" # Save passwords to files mkdir -p /etc/mysql echo "password_here" > /etc/mysql/myapp_prod.pass echo "password_here" > /etc/mysql/myapp_staging.pass chmod 600 /etc/mysql/*.pass # Configure Nginx cat > /etc/nginx/sites-available/myapp << 'EOF' server { listen 80; server_name myapp.com www.myapp.com; root /var/www/myapp.com/live/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.ht { deny all; } location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; } } EOF ln -sf /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/ nginx -t systemctl restart nginx # Create directory structure mkdir -p /var/www/myapp.com mkdir -p /var/backups/myapp chown -R www-data:www-data /var/www/myapp.com chown -R www-data:www-data /var/backups/myapp # Configure PHP-FPM sed -i 's/^pm.max_children = .*/pm.max_children = 50/' /etc/php/8.1/fpm/pool.d/www.conf sed -i 's/^pm.start_servers = .*/pm.start_servers = 5/' /etc/php/8.1/fpm/pool.d/www.conf sed -i 's/^pm.min_spare_servers = .*/pm.min_spare_servers = 5/' /etc/php/8.1/fpm/pool.d/www.conf sed -i 's/^pm.max_spare_servers = .*/pm.max_spare_servers = 10/' /etc/php/8.1/fpm/pool.d/www.conf systemctl restart php8.1-fpm # Set up firewall ufw allow ssh ufw allow 'Nginx Full' ufw --force enable echo "✅ Server setup complete!" 4.2 Health Check Endpoint Quote <?php // public/health.php header('Content-Type: application/json'); $health = [ 'status' => 'healthy', 'timestamp' => date('c'), 'services' => [] ]; // Check database try { $pdo = new PDO( "mysql:host=" . getenv('DB_HOST') . ";dbname=" . getenv('DB_NAME'), getenv('DB_USER'), getenv('DB_PASSWORD') ); $pdo->query('SELECT 1'); $health['services']['database'] = 'healthy'; } catch (Exception $e) { $health['status'] = 'unhealthy'; $health['services']['database'] = 'unhealthy: ' . $e->getMessage(); } // Check disk space $diskFree = disk_free_space('/'); $diskTotal = disk_total_space('/'); $diskPercent = round(($diskFree / $diskTotal) * 100, 2); $health['services']['disk'] = [ 'free_gb' => round($diskFree / 1024 / 1024 / 1024, 2), 'total_gb' => round($diskTotal / 1024 / 1024 / 1024, 2), 'free_percent' => $diskPercent ]; if ($diskPercent < 10) { $health['status'] = 'warning'; $health['services']['disk']['status'] = 'low_space'; } // Check PHP version $health['services']['php'] = [ 'version' => PHP_VERSION, 'memory_limit' => ini_get('memory_limit') ]; http_response_code($health['status'] === 'healthy' ? 200 : 503); echo json_encode($health, JSON_PRETTY_PRINT); Step 5: GitHub Repository Setup 5.1 Required GitHub Secrets Quote STAGING_HOST: staging-server-ip STAGING_USER: deploy-user PRODUCTION_HOST: production-server-ip PRODUCTION_USER: deploy-user SSH_PRIVATE_KEY: -----BEGIN RSA PRIVATE KEY-----\n... SLACK_WEBHOOK: https://hooks.slack.com/services/... APPROVERS: user1,user2,user3 5.2 .gitignore for PHP Quote # .gitignore .env .env.production .env.staging /vendor/ /node_modules/ /storage/logs/ /storage/framework/ /public/storage composer.lock *.log *.cache .DS_Store .idea/ .vscode/ .phpunit.result.cache Step 6: Complete Pipeline Workflow 6.1 Visual Pipeline Flow Quote Developer Push → GitHub → Trigger Workflow ↓ Run CI Pipeline: • Setup PHP + MySQL • Install Dependencies • Run Code Sniffer • Run Unit Tests • Integration Tests ↓ If Tests Pass → Build Package ↓ For develop branch → Auto Deploy to Staging ↓ For main branch → Wait for Manual Approval ↓ If Approved → Backup Production → Deploy → Health Check ↓ If Health Check Fails → Auto Rollback 6.2 Sample PHPUnit Test Quote <?php // tests/unit/DatabaseTest.php use PHPUnit\Framework\TestCase; class DatabaseTest extends TestCase { private $pdo; protected function setUp(): void { $host = getenv('DB_HOST') ?: 'localhost'; $dbname = getenv('DB_NAME') ?: 'test_db'; $username = getenv('DB_USER') ?: 'root'; $password = getenv('DB_PASSWORD') ?: 'root'; $this->pdo = new PDO( "mysql:host=$host;dbname=$dbname", $username, $password ); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } public function testDatabaseConnection() { $this->assertInstanceOf(PDO::class, $this->pdo); } public function testCanCreateTable() { $this->pdo->exec("CREATE TABLE IF NOT EXISTS test_table ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) )"); $tables = $this->pdo->query("SHOW TABLES LIKE 'test_table'")->fetchAll(); $this->assertCount(1, $tables); } } Step 7: Monitoring and Maintenance 7.1 Monitoring Script (scripts/monitor.sh) Quote #!/bin/bash # Cron job to monitor application LOG_FILE="/var/log/myapp/monitor.log" SITE_URL="https://myapp.com" check_health() { RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$SITE_URL/health" --max-time 10) if [ "$RESPONSE" != "200" ]; then echo "$(date): Health check failed! Status: $RESPONSE" >> "$LOG_FILE" # Send alert curl -X POST -H 'Content-type: application/json' \ --data "{\"text\":\"❌ Health check failed for $SITE_URL\"}" \ "$SLACK_WEBHOOK" return 1 fi return 0 } check_disk() { USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$USAGE" -gt 90 ]; then echo "$(date): Disk usage above 90%: $USAGE%" >> "$LOG_FILE" fi } check_database() { if ! mysql -e "SELECT 1" >/dev/null 2>&1; then echo "$(date): Database connection failed!" >> "$LOG_FILE" return 1 fi return 0 } # Run checks check_health check_disk check_database echo "$(date): All checks passed" >> "$LOG_FILE" 7.2 Cron Job Setup Quote # Add to crontab (crontab -e) */5 * * * * /var/www/myapp.com/scripts/monitor.sh 0 2 * * * /var/www/myapp.com/scripts/database.sh backup production 📊 Complete CI/CD Dashboard Setup 8.1 Status Dashboard (HTML) Quote <!-- public/dashboard.html --> <!DOCTYPE html> <html> <head> <title>Deployment Dashboard</title> <style> body { font-family: Arial; margin: 40px; } .deployment { border: 1px solid #ddd; padding: 15px; margin: 10px; } .success { background: #d4edda; } .failed { background: #f8d7da; } .running { background: #fff3cd; } </style> </head> <body> <h1>🚀 CI/CD Dashboard</h1> <div id="deployments"> <h2>Recent Deployments</h2> <!-- Filled by JavaScript --> </div> <div id="system-health"> <h2>System Health</h2> <div id="health-status">Loading...</div> </div> <script> async function loadDeployments() { const response = await fetch('/api/deployments'); const deployments = await response.json(); let html = ''; deployments.forEach(deploy => { html += ` <div class="deployment ${deploy.status}"> <h3>${deploy.environment} - ${deploy.version}</h3> <p>Time: ${deploy.timestamp}</p> <p>Status: ${deploy.status}</p> <p>Commit: ${deploy.commit}</p> </div> `; }); document.getElementById('deployments').innerHTML += html; } async function checkHealth() { const response = await fetch('/health.php'); const health = await response.json(); document.getElementById('health-status').innerHTML = `Status: ${health.status}<br>Database: ${health.services.database}`; } // Refresh every 30 seconds setInterval(checkHealth, 30000); loadDeployments(); checkHealth(); </script> </body> </html> 🎯 Quick Start Checklist Pre-Deployment: Server provisioned with PHP 8.1, MySQL, Nginx Database created with users SSH key pair generated for GitHub Actions GitHub repository created Secrets added to GitHub repository Environment files created (.env.production, .env.staging) First Deployment: Push code to develop branch Watch CI pipeline in GitHub Actions Verify staging deployment Test staging environment Merge to main branch Approve production deployment Verify production deployment Check health endpoint 🔧 Troubleshooting Guide Common Issues: Database Connection Fails Quote # Check MySQL is running sudo systemctl status mysql # Check user permissions mysql -u root -p -e "SHOW GRANTS FOR 'myapp_user'@'localhost';" 2. PHP File Permissions Quote sudo chown -R www-data:www-data /var/www/myapp.com sudo chmod -R 755 /var/www/myapp.com sudo chmod -R 775 /var/www/myapp.com/storage 3. Nginx 502 Bad Gateway Quote # Check PHP-FPM sudo systemctl status php8.1-fpm sudo tail -f /var/log/php8.1-fpm.log # Check socket permissions ls -la /var/run/php/php8.1-fpm.sock 4. GitHub Actions SSH Failure Quote # Test SSH manually ssh -i private_key deploy@server "echo test" # Check authorized_keys cat ~/.ssh/authorized_keys This complete CI/CD pipeline for PHP + MySQL + HTML will: ✅ Automatically test your code ✅ Check code quality ✅ Deploy to staging automatically ✅ Require approval for production ✅ Rollback on failure ✅ Monitor application health ✅ Backup databases automatically The pipeline is production-ready and can be customized based on your specific needs! Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.