WordPress CI/CD Pipeline with GitHub Actions and WP-CLI

Manually deploying WordPress changes is risky, time-consuming, and prone to human error. Push to FTP, run SQL updates, clear caches, pray nothing breaks. When something goes wrong at 2 AM, there’s no easy rollback.

Professional WordPress development requires automated CI/CD (Continuous Integration/Continuous Deployment) pipelines that test, build, and deploy code safely and consistently.

In this advanced guide, you’ll learn to build a production-ready WordPress CI/CD pipeline using GitHub Actions and WP-CLI. You’ll implement automated testing, staging deployments, database migrations, and zero-downtime production deploymentsβ€”all triggered automatically by Git pushes.

By the end, you’ll have a professional WordPress DevOps workflow that eliminates deployment fear.

What is CI/CD for WordPress?

CI/CD Defined

Continuous Integration (CI):

  • Automatically test code changes
  • Verify WordPress functionality
  • Run security checks
  • Ensure code quality

Continuous Deployment (CD):

  • Automatically deploy to staging
  • Deploy to production on approval
  • Run database migrations
  • Zero-downtime deployments

The Manual Deployment Problem

Traditional workflow:

  1. Make changes locally
  2. FTP files to server (hope you don’t overwrite something)
  3. SSH and run SQL updates manually
  4. Clear caches
  5. Hope nothing is broken
  6. If broken, scramble to fix or revert

Problems:

  • Downtime during deployment
  • Human error (forgot to run migration, uploaded wrong file)
  • No testing before production
  • Difficult rollback
  • No audit trail

CI/CD Workflow

Automated workflow:

  1. Push code to GitHub
  2. CI: Automated tests run
  3. CD: Deploy to staging automatically
  4. QA tests staging environment
  5. Approve production deployment
  6. CD: Deploy to production with zero downtime
  7. Automatic rollback if issues detected

Benefits:

  • No manual deployment steps
  • Every deployment is tested
  • Instant rollback capability
  • Complete audit trail in Git
  • Confidence to deploy frequently

Prerequisites

Before building your CI/CD pipeline:

Required Accounts

  • GitHub Account – For repository and Actions
  • WordPress Server(s) – Staging and production with SSH access
  • WP-CLI Access – On all WordPress servers

Server Requirements

Both staging and production need:

  • SSH access
  • WP-CLI installed –Β Installation guide
  • Git installed
  • Web server (Apache/Nginx)
  • MySQL/MariaDB database

Local Development

Knowledge Requirements

Helpful background:

  • Git basics (branches, commits, pull requests)
  • GitHub fundamentals
  • WordPress theme/plugin development
  • SSH and server administration

GitHub Actions Basics

What are GitHub Actions?

GitHub Actions are automated workflows that run when specific GitHub events occur (push, pull request, release, etc.).

Workflow anatomy:

name: WordPress CI/CD

on:
  push:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run tests
        run: ./run-tests.sh

Key concepts:

  • Workflow: YAML file defining automation
  • Event: Trigger (push, pull_request, schedule)
  • Job: Set of steps that run on a runner
  • Step: Individual task (run command, use action)
  • Runner: Server that runs workflows (GitHub-hosted or self-hosted)

Why GitHub Actions for WordPress?

Advantages:

  • Free for public repos (2,000 minutes/month for private)
  • Integrated with GitHub (no external CI service)
  • Large marketplace of pre-built actions
  • Supports matrix builds (test multiple PHP versions)
  • Secret management built-in

Learn more: GitHub Actions Documentation

Setting Up Your Repository

Repository Structure

Organize your WordPress project for CI/CD:

wordpress-project/
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       β”œβ”€β”€ ci.yml           # Continuous Integration
β”‚       └── deploy.yml       # Continuous Deployment
β”œβ”€β”€ wp-content/
β”‚   β”œβ”€β”€ themes/
β”‚   β”‚   └── my-theme/
β”‚   β”œβ”€β”€ plugins/
β”‚   β”‚   └── my-plugin/
β”‚   └── mu-plugins/
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ deploy.sh            # Deployment script
β”‚   β”œβ”€β”€ test.sh              # Testing script
β”‚   └── migrate.sh           # Database migration
β”œβ”€β”€ tests/
β”‚   └── test-wordpress.php
β”œβ”€β”€ composer.json
└── README.md

What to version control:

  • βœ… Custom themes
  • βœ… Custom plugins
  • βœ… mu-plugins
  • βœ… Configuration files
  • βœ… Deployment scripts

What NOT to version:

  • ❌ WordPress core (use Composer or WP-CLI)
  • ❌ Third-party plugins (manage separately)
  • ❌ wp-config.php (use environment-specific configs)
  • ❌ uploads/ directory
  • ❌ .env files with secrets

Create .gitignore

# .gitignore for WordPress

# WordPress core
/wp-admin/
/wp-includes/
/wp-*.php
/xmlrpc.php
/license.txt
/readme.html

# Configuration
wp-config.php
.env
.env.*

# Uploads
wp-content/uploads/

# Cache
wp-content/cache/
wp-content/backup*/

# Third-party plugins (manage via Composer)
wp-content/plugins/*
!wp-content/plugins/my-custom-plugin/

# IDE
.vscode/
.idea/

# OS
.DS_Store
Thumbs.db

# Dependencies
/vendor/
node_modules/

Initialize Repository

# Create repository
git init
git add .
git commit -m "Initial commit"

# Create and push to GitHub
gh repo create my-wordpress-site --public --source=. --remote=origin --push

# Or manually
git remote add origin git@github.com:username/my-wordpress-site.git
git push -u origin main

WordPress Testing Environment

Set Up WordPress in GitHub Actions

Use the setup-wordpress action to create a test environment:

# .github/workflows/ci.yml

name: WordPress CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:5.7
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: wordpress_test
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

    steps:
      - uses: actions/checkout@v3

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
          extensions: mysql, mbstring, xml
          tools: wp-cli

      - name: Install WordPress
        run: |
          wp core download --path=/tmp/wordpress
          wp config create --dbname=wordpress_test --dbuser=root --dbpass=root --dbhost=127.0.0.1 --path=/tmp/wordpress
          wp core install --url=http://localhost --title="Test Site" --admin_user=admin --admin_password=admin --admin_email=admin@test.com --path=/tmp/wordpress

      - name: Activate plugin/theme
        run: |
          # Copy your custom code
          cp -r $GITHUB_WORKSPACE/wp-content/plugins/my-plugin /tmp/wordpress/wp-content/plugins/
          wp plugin activate my-plugin --path=/tmp/wordpress

      - name: Run tests
        run: |
          ./scripts/test.sh

Create Test Script

#!/bin/bash
# scripts/test.sh

set -e

WP_PATH="/tmp/wordpress"

echo "Running WordPress tests..."

# Test 1: WordPress is installed
wp core is-installed --path=$WP_PATH

# Test 2: Database is accessible
wp db check --path=$WP_PATH

# Test 3: Plugin is active
wp plugin is-active my-plugin --path=$WP_PATH

# Test 4: WordPress version
version=$(wp core version --path=$WP_PATH)
echo "WordPress version: $version"

# Test 5: Run PHP CodeSniffer (if configured)
if command -v phpcs &> /dev/null; then
    phpcs --standard=WordPress wp-content/plugins/my-plugin/
fi

echo "βœ“ All tests passed!"

Make it executable:

chmod +x scripts/test.sh
git add scripts/test.sh
git commit -m "Add test script"

Automated Testing with WP-CLI

Test Categories

1. Sanity Tests

  • WordPress is installed
  • Database connection works
  • Required plugins are active

2. Functionality Tests

  • Custom plugin functions work
  • Theme renders correctly
  • Custom post types exist

3. Security Tests

  • No vulnerable plugins
  • WordPress core integrity
  • File permissions

4. Performance Tests

  • Database query count
  • Page load time
  • Cache functionality

Comprehensive Test Script

#!/bin/bash
# scripts/test.sh - Comprehensive WordPress testing

set -e

WP_PATH=${WP_PATH:-/tmp/wordpress}
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'

test_count=0
passed=0
failed=0

run_test() {
    local test_name=$1
    local test_command=$2

    ((test_count++))
    echo -n "Test $test_count: $test_name... "

    if eval "$test_command" &>/dev/null; then
        echo -e "${GREEN}PASS${NC}"
        ((passed++))
        return 0
    else
        echo -e "${RED}FAIL${NC}"
        ((failed++))
        return 1
    fi
}

echo "========================================="
echo "WordPress Automated Testing"
echo "========================================="

# Sanity tests
run_test "WordPress installed" "wp core is-installed --path=$WP_PATH"
run_test "Database accessible" "wp db check --path=$WP_PATH"
run_test "Core files verified" "wp core verify-checksums --path=$WP_PATH"

# Plugin tests
run_test "Custom plugin active" "wp plugin is-active my-plugin --path=$WP_PATH"
run_test "No plugin errors" "wp plugin list --path=$WP_PATH --status=active --format=count"

# Theme tests
run_test "Theme activated" "wp theme is-active my-theme --path=$WP_PATH"

# Security tests
run_test "No vulnerable plugins" "! wp plugin list --path=$WP_PATH --field=update | grep -q available"

# Performance tests
run_test "Cache object working" "wp cache flush --path=$WP_PATH"

# Database tests
run_test "Database optimized" "wp db optimize --path=$WP_PATH"

echo "========================================="
echo "Test Results: $passed passed, $failed failed"
echo "========================================="

if [ $failed -gt 0 ]; then
    exit 1
fi

exit 0

Matrix Testing (Multiple PHP Versions)

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        php-version: ['7.4', '8.0', '8.1']
        wordpress-version: ['6.2', '6.3', '6.4']

    name: PHP ${{ matrix.php-version }} / WP ${{ matrix.wordpress-version }}

    steps:
      - uses: actions/checkout@v3

      - name: Setup PHP ${{ matrix.php-version }}
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-version }}

      - name: Install WordPress ${{ matrix.wordpress-version }}
        run: |
          wp core download --version=${{ matrix.wordpress-version }} --path=/tmp/wordpress
          # ... rest of setup

      - name: Run tests
        run: ./scripts/test.sh

This tests across 9 combinations (3 PHP Γ— 3 WP versions).

Building the Deployment Workflow

Deployment Script Structure

#!/bin/bash
# scripts/deploy.sh

set -euo pipefail

ENVIRONMENT=$1  # staging or production
SERVER=$2       # server hostname
WP_PATH=$3      # WordPress path on server

echo "Deploying to $ENVIRONMENT ($SERVER)"

# 1. Create backup
ssh $SERVER "cd $WP_PATH && wp db export backup-$(date +%s).sql"

# 2. Pull latest code
ssh $SERVER "cd $WP_PATH && git pull origin main"

# 3. Install dependencies
ssh $SERVER "cd $WP_PATH && composer install --no-dev"

# 4. Run database migrations
ssh $SERVER "cd $WP_PATH && wp cli migrate"

# 5. Clear caches
ssh $SERVER "cd $WP_PATH && wp cache flush"
ssh $SERVER "cd $WP_PATH && wp rewrite flush"

# 6. Verify deployment
ssh $SERVER "cd $WP_PATH && wp core is-installed"

echo "βœ“ Deployment complete!"

SSH Key Setup

Add SSH private key to GitHub Secrets:

  1. Generate SSH key (if needed):
ssh-keygen -t ed25519 -C "github-actions@example.com" -f ~/.ssh/github-actions
  1. Add public key to server:
ssh-copy-id -i ~/.ssh/github-actions.pub user@your-server.com
  1. Add private key to GitHub:
    • Go to repo Settings β†’ Secrets β†’ Actions
    • New repository secret
    • Name: SSH_PRIVATE_KEY
    • Value: Contents of ~/.ssh/github-actions (private key)

Environment Secrets Management

GitHub Secrets

Store sensitive data in GitHub Secrets:

Required secrets:

  • SSH_PRIVATE_KEY – SSH key for server access
  • SSH_HOST_STAGING – Staging server hostname
  • SSH_HOST_PRODUCTION – Production server hostname
  • SSH_USER – SSH username
  • WP_PATH_STAGING – WordPress path on staging
  • WP_PATH_PRODUCTION – WordPress path on production

Using Secrets in Workflows

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup SSH
        uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Deploy to staging
        run: |
          ./scripts/deploy.sh \
            staging \
            ${{ secrets.SSH_HOST_STAGING }} \
            ${{ secrets.WP_PATH_STAGING }}

Environment Variables

Create environment-specific configurations:

jobs:
  deploy-staging:
    environment: staging
    env:
      WP_ENV: staging
      SSH_HOST: ${{ secrets.SSH_HOST_STAGING }}
      WP_PATH: ${{ secrets.WP_PATH_STAGING }}

  deploy-production:
    environment: production
    env:
      WP_ENV: production
      SSH_HOST: ${{ secrets.SSH_HOST_PRODUCTION }}
      WP_PATH: ${{ secrets.WP_PATH_PRODUCTION }}

Staging Deployment

Auto-Deploy to Staging

Deploy automatically on push to develop branch:

# .github/workflows/deploy-staging.yml

name: Deploy to Staging

on:
  push:
    branches: [develop]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: staging

    steps:
      - uses: actions/checkout@v3

      - name: Setup SSH
        uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Deploy to staging server
        run: |
          ssh -o StrictHostKeyChecking=no ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST_STAGING }} << 'ENDSSH'
            set -e
            cd ${{ secrets.WP_PATH_STAGING }}

            # Backup database
            wp db export backup-$(date +%Y%m%d-%H%M%S).sql

            # Pull latest code
            git fetch origin
            git reset --hard origin/develop

            # Install dependencies
            composer install --no-dev --optimize-autoloader

            # Database migrations
            if [ -f scripts/migrate.sh ]; then
              ./scripts/migrate.sh
            fi

            # Clear caches
            wp cache flush
            wp rewrite flush

            # Verify
            wp core is-installed && echo "Deployment successful!"
          ENDSSH

      - name: Notify on Slack
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Staging deployment: ${{ job.status }}'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Production Deployment

Manual Approval Required

Require manual approval before production deployment:

# .github/workflows/deploy-production.yml

name: Deploy to Production

on:
  workflow_dispatch:  # Manual trigger only
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://yoursite.com

    steps:
      - uses: actions/checkout@v3

      - name: Setup SSH
        uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Pre-deployment checks
        run: |
          # Verify staging is working
          curl -f https://staging.yoursite.com || exit 1

      - name: Create backup
        run: |
          ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST_PRODUCTION }} \
            "cd ${{ secrets.WP_PATH_PRODUCTION }} && wp db export backups/pre-deploy-$(date +%s).sql"

      - name: Deploy to production
        run: |
          ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST_PRODUCTION }} << 'ENDSSH'
            set -e
            cd ${{ secrets.WP_PATH_PRODUCTION }}

            # Enable maintenance mode
            wp maintenance-mode activate

            # Pull latest code
            git fetch origin
            git reset --hard origin/main

            # Install dependencies
            composer install --no-dev --optimize-autoloader

            # Database migrations
            if [ -f scripts/migrate.sh ]; then
              ./scripts/migrate.sh
            fi

            # Clear caches
            wp cache flush
            wp rewrite flush
            wp transient delete --all

            # Disable maintenance mode
            wp maintenance-mode deactivate

            # Verify
            wp core is-installed && echo "Production deployment successful!"
          ENDSSH

      - name: Smoke tests
        run: |
          # Test homepage
          curl -f https://yoursite.com

          # Test critical pages
          curl -f https://yoursite.com/about
          curl -f https://yoursite.com/contact

      - name: Rollback on failure
        if: failure()
        run: |
          echo "Deployment failed, initiating rollback..."
          ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST_PRODUCTION }} \
            "cd ${{ secrets.WP_PATH_PRODUCTION }} && git reset --hard HEAD~1"

Protected Branches

Configure branch protection in GitHub:

  1. Go to Settings β†’ Branches
  2. Add rule for main branch
  3. Enable:
    • Require pull request reviews (1+ approvals)
    • Require status checks to pass (CI tests)
    • Require branches to be up to date

This ensures production only receives tested, reviewed code.

Database Migrations

Migration Script Pattern

#!/bin/bash
# scripts/migrate.sh

set -e

WP_PATH=${WP_PATH:-.}

echo "Running database migrations..."

# Migration 001: Add custom table
wp db query "
  CREATE TABLE IF NOT EXISTS wp_custom_data (
    id bigint(20) NOT NULL AUTO_INCREMENT,
    user_id bigint(20) NOT NULL,
    data longtext NOT NULL,
    created_at datetime NOT NULL,
    PRIMARY KEY (id)
  );
" --path=$WP_PATH

# Migration 002: Update post meta
wp post meta update-all custom_field new_value --from=old_value --path=$WP_PATH

# Migration 003: Create custom taxonomy
wp taxonomy register custom_taxonomy --path=$WP_PATH

echo "βœ“ Migrations complete!"

Version-Controlled Migrations

Use migration files:

# migrations/001-create-custom-table.sql
CREATE TABLE IF NOT EXISTS wp_custom_data (
  id bigint(20) NOT NULL AUTO_INCREMENT,
  data longtext NOT NULL,
  PRIMARY KEY (id)
);

# migrations/002-add-meta-field.sh
#!/bin/bash
wp post list --format=ids | xargs -I {} wp post meta update {} new_field default_value

Migration runner:

#!/bin/bash
# scripts/migrate.sh

WP_PATH=${WP_PATH:-.}
MIGRATIONS_DIR="migrations"

for migration in $MIGRATIONS_DIR/*; do
    echo "Running migration: $(basename $migration)"

    if [[ $migration == *.sql ]]; then
        wp db query < $migration --path=$WP_PATH
    elif [[ $migration == *.sh ]]; then
        bash $migration
    fi

    echo "βœ“ Completed: $(basename $migration)"
done

Rollback Strategy

Automated Rollback on Failure

- name: Deploy with rollback
  id: deploy
  run: |
    # Get current commit for rollback
    CURRENT_COMMIT=$(ssh $SERVER "cd $WP_PATH && git rev-parse HEAD")
    echo "current_commit=$CURRENT_COMMIT" >> $GITHUB_OUTPUT

    # Deploy
    ./scripts/deploy.sh

- name: Verify deployment
  id: verify
  run: |
    # Run smoke tests
    curl -f https://yoursite.com || exit 1
    ssh $SERVER "cd $WP_PATH && wp core is-installed" || exit 1

- name: Rollback on failure
  if: failure() && steps.deploy.outcome == 'success'
  run: |
    echo "Deployment verification failed, rolling back..."

    # Restore database backup
    BACKUP_FILE=$(ssh $SERVER "ls -t $WP_PATH/backups/*.sql | head -1")
    ssh $SERVER "cd $WP_PATH && wp db import $BACKUP_FILE"

    # Restore code
    ssh $SERVER "cd $WP_PATH && git reset --hard ${{ steps.deploy.outputs.current_commit }}"

    # Clear caches
    ssh $SERVER "cd $WP_PATH && wp cache flush"

    echo "βœ“ Rollback complete"
    exit 1

Manual Rollback Workflow

# .github/workflows/rollback.yml

name: Rollback Production

on:
  workflow_dispatch:
    inputs:
      commit_sha:
        description: 'Commit SHA to rollback to'
        required: true

jobs:
  rollback:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Confirm rollback
        run: |
          echo "Rolling back to commit: ${{ github.event.inputs.commit_sha }}"

      - name: Rollback code
        run: |
          ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST_PRODUCTION }} \
            "cd ${{ secrets.WP_PATH_PRODUCTION }} && git reset --hard ${{ github.event.inputs.commit_sha }}"

      - name: Restore database
        run: |
          # Restore from backup (manual selection required)
          echo "Please restore database manually from backups/"

Complete GitHub Actions Workflow

Production-Ready CI/CD

# .github/workflows/wordpress-cicd.yml

name: WordPress CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  workflow_dispatch:

env:
  PHP_VERSION: '8.1'

jobs:
  # Job 1: Run tests
  test:
    name: Test
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:5.7
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: wordpress_test
        ports:
          - 3306:3306

    steps:
      - uses: actions/checkout@v3

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ env.PHP_VERSION }}
          extensions: mysql, mbstring, xml
          tools: wp-cli, composer

      - name: Install WordPress
        run: |
          wp core download --path=/tmp/wordpress
          wp config create \
            --dbname=wordpress_test \
            --dbuser=root \
            --dbpass=root \
            --dbhost=127.0.0.1 \
            --path=/tmp/wordpress
          wp core install \
            --url=http://localhost \
            --title="Test Site" \
            --admin_user=admin \
            --admin_password=admin \
            --admin_email=admin@test.com \
            --path=/tmp/wordpress

      - name: Copy custom code
        run: |
          cp -r wp-content/themes/* /tmp/wordpress/wp-content/themes/
          cp -r wp-content/plugins/* /tmp/wordpress/wp-content/plugins/

      - name: Run tests
        run: ./scripts/test.sh
        env:
          WP_PATH: /tmp/wordpress

  # Job 2: Deploy to staging
  deploy-staging:
    name: Deploy to Staging
    needs: test
    if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: staging

    steps:
      - uses: actions/checkout@v3

      - name: Setup SSH
        uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Deploy
        run: |
          ./scripts/deploy.sh \
            staging \
            ${{ secrets.SSH_HOST_STAGING }} \
            ${{ secrets.WP_PATH_STAGING }}

  # Job 3: Deploy to production
  deploy-production:
    name: Deploy to Production
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://yoursite.com

    steps:
      - uses: actions/checkout@v3

      - name: Setup SSH
        uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Deploy
        id: deploy
        run: |
          ./scripts/deploy.sh \
            production \
            ${{ secrets.SSH_HOST_PRODUCTION }} \
            ${{ secrets.WP_PATH_PRODUCTION }}

      - name: Verify
        run: |
          curl -f https://yoursite.com || exit 1

      - name: Notify
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Production deployment: ${{ job.status }}'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Best Practices

Security

  1. Never commit secrets – Use GitHub Secrets
  2. Restrict SSH access – Use dedicated deploy keys
  3. Audit deployments – Enable GitHub audit log
  4. Scan for vulnerabilities – Use GitHub’s Dependabot

Deployment

  1. Always test first – Run CI tests before deploy
  2. Use staging – Test on staging before production
  3. Backup before deploy – Automatic database backups
  4. Enable maintenance mode – During production deployments
  5. Monitor after deploy – Check logs and metrics

Code Quality

  1. Use linters – PHP CodeSniffer, ESLint
  2. Run static analysis – PHPStan, Psalm
  3. Code coverage – Track test coverage
  4. Peer review – Require PR approvals

Performance

  1. Cache workflows – Cache Composer dependencies
  2. Parallel jobs – Run tests in parallel
  3. Self-hosted runners – For faster deploys

Troubleshooting

Issue 1: SSH Connection Failed

ProblemPermission denied (publickey)

Solutions:

  1. Verify SSH key is added to GitHub Secrets correctly
  2. Test SSH connection:
ssh -i ~/.ssh/github-actions user@server.com
  1. Check server’s ~/.ssh/authorized_keys
  2. Verify SSH agent setup in workflow

Issue 2: Deployment Timeout

Problem: Workflow times out during deployment

Solutions:

  1. Increase timeout:
jobs:
  deploy:
    timeout-minutes: 30  # Default is 360
  1. Optimize deployment script
  2. Use caching for dependencies

Issue 3: Database Migration Failed

Problem: Migration fails, breaks site

Solutions:

  1. Always backup before migrations
  2. Test migrations on staging first
  3. Use transactions when possible:
START TRANSACTION;
-- migration SQL
COMMIT;
  1. Implement rollback in migration script

Issue 4: Cache Not Cleared

Problem: Changes don’t appear after deployment

Solutions:

  1. Add cache clearing to deployment:
wp cache flush
wp rewrite flush
wp transient delete --all
  1. Clear CDN cache if using one
  2. Verify opcache is reset

Issue 5: Workflow Fails Silently

Problem: Workflow completes but deployment didn’t work

Solutions:

  1. Remove set -e temporarily to see errors
  2. Add verbose logging:
set -x  # Print commands
  1. Check server logs:
ssh server "tail -f /var/log/apache2/error.log"

Next Steps

Congratulations! You’ve built a production-ready WordPress CI/CD pipeline.

Enhance Your Pipeline

  1. WordPress Multisite CI/CD – Deploy to network
  2. Automated Backups – Integrate with deployment
  3. Performance Testing – Add to CI

Advanced Topics

Add to your pipeline:

  • Visual regression testing (BackstopJS, Percy)
  • Load testing (k6, Apache JBench)
  • Security scanning (OWASP ZAP)
  • Accessibility testing (pa11y)
  • SEO validation
  • Automated changelogs

Master WordPress DevOps

Want to build enterprise-grade WordPress automation?

Join WPCLI MasteryΒ and learn:

  • Complete CI/CD pipeline templates
  • Multi-environment WordPress architecture
  • Blue-green deployments
  • Automated rollbacks and disaster recovery
  • Early bird course pricing ($99 vs $199)

Conclusion

Automated CI/CD pipelines eliminate deployment fear and enable rapid, confident WordPress development. The GitHub Actions workflow you built today provides:

  • Automated testing on every push
  • Safe staging deployments
  • Controlled production releases
  • Instant rollback capability
  • Complete audit trail

Key takeaways:

  • CI/CD catches errors before production
  • GitHub Actions integrates seamlessly with WordPress
  • WP-CLI makes WordPress automation possible
  • Staging β†’ Production workflow ensures safety
  • Automated rollbacks prevent disasters
  • Database migrations need careful handling

The CI/CD pipeline you implemented today is the foundation of professional WordPress DevOps. Deploy with confidence.

Ready to implement? Start with the complete workflow and adapt to your needs.


Questions about WordPress CI/CD? Drop a comment below!

Found this helpful? Share with WordPress developers building automation.

Next:Β ExploreΒ advanced WordPress deployment strategiesΒ and zero-downtime techniques.