<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>wp-cli devops Archives - WP-CLI Mastery</title>
	<atom:link href="https://wpclimastery.com/blog/tag/wp-cli-devops/feed/" rel="self" type="application/rss+xml" />
	<link>https://wpclimastery.com/blog/tag/wp-cli-devops/</link>
	<description>Automate WordPress Like a DevOps Pro.</description>
	<lastBuildDate>Mon, 24 Nov 2025 11:16:47 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wpclimastery.com/wp-content/uploads/2025/11/cropped-favicon-32x32.webp</url>
	<title>wp-cli devops Archives - WP-CLI Mastery</title>
	<link>https://wpclimastery.com/blog/tag/wp-cli-devops/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>WordPress CI/CD Pipeline with GitHub Actions and WP-CLI</title>
		<link>https://wpclimastery.com/blog/wordpress-ci-cd-pipeline-with-github-actions-and-wp-cli/</link>
		
		<dc:creator><![CDATA[Krasen]]></dc:creator>
		<pubDate>Tue, 20 Jan 2026 09:00:00 +0000</pubDate>
				<category><![CDATA[WordPress DevOps]]></category>
		<category><![CDATA[continuous integration wordpress]]></category>
		<category><![CDATA[github actions wordpress]]></category>
		<category><![CDATA[wordpress ci cd]]></category>
		<category><![CDATA[wordpress deployment automation]]></category>
		<category><![CDATA[wp-cli devops]]></category>
		<guid isPermaLink="false">https://wpclimastery.com/?p=15</guid>

					<description><![CDATA[<p>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,...</p>
<p>The post <a href="https://wpclimastery.com/blog/wordpress-ci-cd-pipeline-with-github-actions-and-wp-cli/">WordPress CI/CD Pipeline with GitHub Actions and WP-CLI</a> appeared first on <a href="https://wpclimastery.com">WP-CLI Mastery</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>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&#8217;s no easy rollback.</p>



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



<p>In this advanced guide, you&#8217;ll learn to build a production-ready WordPress CI/CD pipeline using GitHub Actions and WP-CLI. You&#8217;ll implement automated testing, staging deployments, database migrations, and zero-downtime production deployments—all triggered automatically by Git pushes.</p>



<p>By the end, you&#8217;ll have a professional WordPress DevOps workflow that eliminates deployment fear.</p>



<h3 class="wp-block-heading" id="what-is-cicd-for-wordpress-what-is-cicd">What is CI/CD for WordPress?</h3>



<h4 class="wp-block-heading" id="cicd-defined">CI/CD Defined</h4>



<p><strong>Continuous Integration (CI):</strong></p>



<ul class="wp-block-list">
<li>Automatically test code changes</li>



<li>Verify WordPress functionality</li>



<li>Run security checks</li>



<li>Ensure code quality</li>
</ul>



<p><strong>Continuous Deployment (CD):</strong></p>



<ul class="wp-block-list">
<li>Automatically deploy to staging</li>



<li>Deploy to production on approval</li>



<li>Run database migrations</li>



<li>Zero-downtime deployments</li>
</ul>



<h4 class="wp-block-heading" id="the-manual-deployment-problem">The Manual Deployment Problem</h4>



<p><strong>Traditional workflow:</strong></p>



<ol class="wp-block-list">
<li>Make changes locally</li>



<li>FTP files to server (hope you don&#8217;t overwrite something)</li>



<li>SSH and run SQL updates manually</li>



<li>Clear caches</li>



<li>Hope nothing is broken</li>



<li>If broken, scramble to fix or revert</li>
</ol>



<p><strong>Problems:</strong></p>



<ul class="wp-block-list">
<li>Downtime during deployment</li>



<li>Human error (forgot to run migration, uploaded wrong file)</li>



<li>No testing before production</li>



<li>Difficult rollback</li>



<li>No audit trail</li>
</ul>



<h4 class="wp-block-heading" id="cicd-workflow">CI/CD Workflow</h4>



<p><strong>Automated workflow:</strong></p>



<ol class="wp-block-list">
<li>Push code to GitHub</li>



<li><strong>CI</strong>: Automated tests run</li>



<li><strong>CD</strong>: Deploy to staging automatically</li>



<li>QA tests staging environment</li>



<li>Approve production deployment</li>



<li><strong>CD</strong>: Deploy to production with zero downtime</li>



<li>Automatic rollback if issues detected</li>
</ol>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>No manual deployment steps</li>



<li>Every deployment is tested</li>



<li>Instant rollback capability</li>



<li>Complete audit trail in Git</li>



<li>Confidence to deploy frequently</li>
</ul>



<h3 class="wp-block-heading" id="prerequisites-prerequisites">Prerequisites</h3>



<p>Before building your CI/CD pipeline:</p>



<h4 class="wp-block-heading" id="required-accounts">Required Accounts</h4>



<ul class="wp-block-list">
<li><strong>GitHub Account</strong>&nbsp;&#8211; For repository and Actions</li>



<li><strong>WordPress Server(s)</strong>&nbsp;&#8211; Staging and production with SSH access</li>



<li><strong>WP-CLI Access</strong>&nbsp;&#8211; On all WordPress servers</li>
</ul>



<h4 class="wp-block-heading" id="server-requirements">Server Requirements</h4>



<p><strong>Both staging and production need:</strong></p>



<ul class="wp-block-list">
<li>SSH access</li>



<li>WP-CLI installed &#8211; <a href="#">Installation guide</a></li>



<li>Git installed</li>



<li>Web server (Apache/Nginx)</li>



<li>MySQL/MariaDB database</li>
</ul>



<h4 class="wp-block-heading" id="local-development">Local Development</h4>



<ul class="wp-block-list">
<li>Git installed</li>



<li>Basic understanding of WordPress development</li>



<li><a href="#">Bash scripting knowledge</a></li>
</ul>



<h4 class="wp-block-heading" id="knowledge-requirements">Knowledge Requirements</h4>



<p>Helpful background:</p>



<ul class="wp-block-list">
<li>Git basics (branches, commits, pull requests)</li>



<li>GitHub fundamentals</li>



<li>WordPress theme/plugin development</li>



<li>SSH and server administration</li>
</ul>



<h3 class="wp-block-heading" id="github-actions-basics-github-actions-basics">GitHub Actions Basics</h3>



<h4 class="wp-block-heading" id="what-are-github-actions">What are GitHub Actions?</h4>



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



<p><strong>Workflow anatomy:</strong></p>



<pre class="wp-block-code"><code>name: WordPress CI/CD

on:
  push:
    branches: &#91;main, develop]

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



<p><strong>Key concepts:</strong></p>



<ul class="wp-block-list">
<li><strong>Workflow</strong>: YAML file defining automation</li>



<li><strong>Event</strong>: Trigger (push, pull_request, schedule)</li>



<li><strong>Job</strong>: Set of steps that run on a runner</li>



<li><strong>Step</strong>: Individual task (run command, use action)</li>



<li><strong>Runner</strong>: Server that runs workflows (GitHub-hosted or self-hosted)</li>
</ul>



<h4 class="wp-block-heading" id="why-github-actions-for-wordpress">Why GitHub Actions for WordPress?</h4>



<p><strong>Advantages:</strong></p>



<ul class="wp-block-list">
<li>Free for public repos (2,000 minutes/month for private)</li>



<li>Integrated with GitHub (no external CI service)</li>



<li>Large marketplace of pre-built actions</li>



<li>Supports matrix builds (test multiple PHP versions)</li>



<li>Secret management built-in</li>
</ul>



<p>Learn more:&nbsp;<a href="https://docs.github.com/en/actions">GitHub Actions Documentation</a></p>



<h3 class="wp-block-heading" id="setting-up-your-repository-repository-setup">Setting Up Your Repository</h3>



<h4 class="wp-block-heading" id="repository-structure">Repository Structure</h4>



<p>Organize your WordPress project for CI/CD:</p>



<pre class="wp-block-code"><code>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
</code></pre>



<p><strong>What to version control:</strong></p>



<ul class="wp-block-list">
<li>✅ Custom themes</li>



<li>✅ Custom plugins</li>



<li>✅ mu-plugins</li>



<li>✅ Configuration files</li>



<li>✅ Deployment scripts</li>
</ul>



<p><strong>What NOT to version:</strong></p>



<ul class="wp-block-list">
<li>❌ WordPress core (use Composer or WP-CLI)</li>



<li>❌ Third-party plugins (manage separately)</li>



<li>❌&nbsp;<code>wp-config.php</code>&nbsp;(use environment-specific configs)</li>



<li>❌&nbsp;<code>uploads/</code>&nbsp;directory</li>



<li>❌&nbsp;<code>.env</code>&nbsp;files with secrets</li>
</ul>



<h4 class="wp-block-heading" id="create-gitignore">Create .gitignore</h4>



<pre class="wp-block-code"><code><em># .gitignore for WordPress</em>

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

<em># Configuration</em>
wp-config.php
.env
.env.*

<em># Uploads</em>
wp-content/uploads/

<em># Cache</em>
wp-content/cache/
wp-content/backup*/

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

<em># IDE</em>
.vscode/
.idea/

<em># OS</em>
.DS_Store
Thumbs.db

<em># Dependencies</em>
/vendor/
node_modules/
</code></pre>



<h4 class="wp-block-heading" id="initialize-repository">Initialize Repository</h4>



<pre class="wp-block-code"><code><em># Create repository</em>
git init
git add .
git commit -m "Initial commit"

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

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



<h3 class="wp-block-heading" id="wordpress-testing-environment-testing-environment">WordPress Testing Environment</h3>



<h4 class="wp-block-heading" id="set-up-wordpress-in-github-actions">Set Up WordPress in GitHub Actions</h4>



<p>Use the&nbsp;<code>setup-wordpress</code>&nbsp;action to create a test environment:</p>



<pre class="wp-block-code"><code><em># .github/workflows/ci.yml</em>

name: WordPress CI

on:
  push:
    branches: &#91;main, develop]
  pull_request:
    branches: &#91;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
</code></pre>



<h4 class="wp-block-heading" id="create-test-script">Create Test Script</h4>



<pre class="wp-block-code"><code>#!/bin/bash
<em># scripts/test.sh</em>

set -e

WP_PATH="/tmp/wordpress"

echo "Running WordPress tests..."

<em># Test 1: WordPress is installed</em>
wp core is-installed --path=$WP_PATH

<em># Test 2: Database is accessible</em>
wp db check --path=$WP_PATH

<em># Test 3: Plugin is active</em>
wp plugin is-active my-plugin --path=$WP_PATH

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

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

echo "✓ All tests passed!"
</code></pre>



<p>Make it executable:</p>



<pre class="wp-block-code"><code>chmod +x scripts/test.sh
git add scripts/test.sh
git commit -m "Add test script"
</code></pre>



<h3 class="wp-block-heading" id="automated-testing-with-wp-cli-automated-testing">Automated Testing with WP-CLI</h3>



<h4 class="wp-block-heading" id="test-categories">Test Categories</h4>



<p><strong>1. Sanity Tests</strong></p>



<ul class="wp-block-list">
<li>WordPress is installed</li>



<li>Database connection works</li>



<li>Required plugins are active</li>
</ul>



<p><strong>2. Functionality Tests</strong></p>



<ul class="wp-block-list">
<li>Custom plugin functions work</li>



<li>Theme renders correctly</li>



<li>Custom post types exist</li>
</ul>



<p><strong>3. Security Tests</strong></p>



<ul class="wp-block-list">
<li>No vulnerable plugins</li>



<li>WordPress core integrity</li>



<li>File permissions</li>
</ul>



<p><strong>4. Performance Tests</strong></p>



<ul class="wp-block-list">
<li>Database query count</li>



<li>Page load time</li>



<li>Cache functionality</li>
</ul>



<h4 class="wp-block-heading" id="comprehensive-test-script">Comprehensive Test Script</h4>



<pre class="wp-block-code"><code>#!/bin/bash
<em># scripts/test.sh - Comprehensive WordPress testing</em>

set -e

WP_PATH=${WP_PATH:-/tmp/wordpress}
RED='\033&#91;0;31m'
GREEN='\033&#91;0;32m'
NC='\033&#91;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" &amp;&gt;/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 "========================================="

<em># Sanity tests</em>
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"

<em># Plugin tests</em>
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"

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

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

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

<em># Database tests</em>
run_test "Database optimized" "wp db optimize --path=$WP_PATH"

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

if &#91; $failed -gt 0 ]; then
    exit 1
fi

exit 0
</code></pre>



<h4 class="wp-block-heading" id="matrix-testing-multiple-php-versions">Matrix Testing (Multiple PHP Versions)</h4>



<pre class="wp-block-code"><code>jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        php-version: &#91;'7.4', '8.0', '8.1']
        wordpress-version: &#91;'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
</code></pre>



<p>This tests across 9 combinations (3 PHP × 3 WP versions).</p>



<h3 class="wp-block-heading" id="building-the-deployment-workflow-deployment-workflow">Building the Deployment Workflow</h3>



<h4 class="wp-block-heading" id="deployment-script-structure">Deployment Script Structure</h4>



<pre class="wp-block-code"><code>#!/bin/bash
<em># scripts/deploy.sh</em>

set -euo pipefail

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

echo "Deploying to $ENVIRONMENT ($SERVER)"

<em># 1. Create backup</em>
ssh $SERVER "cd $WP_PATH &amp;&amp; wp db export backup-$(date +%s).sql"

<em># 2. Pull latest code</em>
ssh $SERVER "cd $WP_PATH &amp;&amp; git pull origin main"

<em># 3. Install dependencies</em>
ssh $SERVER "cd $WP_PATH &amp;&amp; composer install --no-dev"

<em># 4. Run database migrations</em>
ssh $SERVER "cd $WP_PATH &amp;&amp; wp cli migrate"

<em># 5. Clear caches</em>
ssh $SERVER "cd $WP_PATH &amp;&amp; wp cache flush"
ssh $SERVER "cd $WP_PATH &amp;&amp; wp rewrite flush"

<em># 6. Verify deployment</em>
ssh $SERVER "cd $WP_PATH &amp;&amp; wp core is-installed"

echo "✓ Deployment complete!"
</code></pre>



<h4 class="wp-block-heading" id="ssh-key-setup">SSH Key Setup</h4>



<p>Add SSH private key to GitHub Secrets:</p>



<ol class="wp-block-list">
<li>Generate SSH key (if needed):</li>
</ol>



<pre class="wp-block-code"><code>ssh-keygen -t ed25519 -C "github-actions@example.com" -f ~/.ssh/github-actions
</code></pre>



<ol start="2" class="wp-block-list">
<li>Add public key to server:</li>
</ol>



<pre class="wp-block-code"><code>ssh-copy-id -i ~/.ssh/github-actions.pub user@your-server.com
</code></pre>



<ol start="3" class="wp-block-list">
<li>Add private key to GitHub:
<ul class="wp-block-list">
<li>Go to repo Settings → Secrets → Actions</li>



<li>New repository secret</li>



<li>Name:&nbsp;<code>SSH_PRIVATE_KEY</code></li>



<li>Value: Contents of&nbsp;<code>~/.ssh/github-actions</code>&nbsp;(private key)</li>
</ul>
</li>
</ol>



<h3 class="wp-block-heading" id="environment-secrets-management-secrets-management">Environment Secrets Management</h3>



<h4 class="wp-block-heading" id="github-secrets">GitHub Secrets</h4>



<p>Store sensitive data in GitHub Secrets:</p>



<p><strong>Required secrets:</strong></p>



<ul class="wp-block-list">
<li><code>SSH_PRIVATE_KEY</code>&nbsp;&#8211; SSH key for server access</li>



<li><code>SSH_HOST_STAGING</code>&nbsp;&#8211; Staging server hostname</li>



<li><code>SSH_HOST_PRODUCTION</code>&nbsp;&#8211; Production server hostname</li>



<li><code>SSH_USER</code>&nbsp;&#8211; SSH username</li>



<li><code>WP_PATH_STAGING</code>&nbsp;&#8211; WordPress path on staging</li>



<li><code>WP_PATH_PRODUCTION</code>&nbsp;&#8211; WordPress path on production</li>
</ul>



<h4 class="wp-block-heading" id="using-secrets-in-workflows">Using Secrets in Workflows</h4>



<pre class="wp-block-code"><code>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 }}
</code></pre>



<h4 class="wp-block-heading" id="environment-variables">Environment Variables</h4>



<p>Create environment-specific configurations:</p>



<pre class="wp-block-code"><code>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 }}
</code></pre>



<h3 class="wp-block-heading" id="staging-deployment-staging-deployment">Staging Deployment</h3>



<h4 class="wp-block-heading" id="auto-deploy-to-staging">Auto-Deploy to Staging</h4>



<p>Deploy automatically on push to&nbsp;<code>develop</code>&nbsp;branch:</p>



<pre class="wp-block-code"><code><em># .github/workflows/deploy-staging.yml</em>

name: Deploy to Staging

on:
  push:
    branches: &#91;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 }} &lt;&lt; 'ENDSSH'
            set -e
            cd ${{ secrets.WP_PATH_STAGING }}

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

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

            <em># Install dependencies</em>
            composer install --no-dev --optimize-autoloader

            <em># Database migrations</em>
            if &#91; -f scripts/migrate.sh ]; then
              ./scripts/migrate.sh
            fi

            <em># Clear caches</em>
            wp cache flush
            wp rewrite flush

            <em># Verify</em>
            wp core is-installed &amp;&amp; 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 }}
</code></pre>



<h3 class="wp-block-heading" id="production-deployment-production-deployment">Production Deployment</h3>



<h4 class="wp-block-heading" id="manual-approval-required">Manual Approval Required</h4>



<p>Require manual approval before production deployment:</p>



<pre class="wp-block-code"><code><em># .github/workflows/deploy-production.yml</em>

name: Deploy to Production

on:
  workflow_dispatch:  <em># Manual trigger only</em>
  release:
    types: &#91;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 }} &amp;&amp; wp db export backups/pre-deploy-$(date +%s).sql"

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

            <em># Enable maintenance mode</em>
            wp maintenance-mode activate

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

            <em># Install dependencies</em>
            composer install --no-dev --optimize-autoloader

            <em># Database migrations</em>
            if &#91; -f scripts/migrate.sh ]; then
              ./scripts/migrate.sh
            fi

            <em># Clear caches</em>
            wp cache flush
            wp rewrite flush
            wp transient delete --all

            <em># Disable maintenance mode</em>
            wp maintenance-mode deactivate

            <em># Verify</em>
            wp core is-installed &amp;&amp; echo "Production deployment successful!"
          ENDSSH

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

          <em># Test critical pages</em>
          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 }} &amp;&amp; git reset --hard HEAD~1"
</code></pre>



<h4 class="wp-block-heading" id="protected-branches">Protected Branches</h4>



<p>Configure branch protection in GitHub:</p>



<ol class="wp-block-list">
<li>Go to Settings → Branches</li>



<li>Add rule for&nbsp;<code>main</code>&nbsp;branch</li>



<li>Enable:
<ul class="wp-block-list">
<li>Require pull request reviews (1+ approvals)</li>



<li>Require status checks to pass (CI tests)</li>



<li>Require branches to be up to date</li>
</ul>
</li>
</ol>



<p>This ensures production only receives tested, reviewed code.</p>



<h3 class="wp-block-heading" id="database-migrations-database-migrations">Database Migrations</h3>



<h4 class="wp-block-heading" id="migration-script-pattern">Migration Script Pattern</h4>



<pre class="wp-block-code"><code>#!/bin/bash
<em># scripts/migrate.sh</em>

set -e

WP_PATH=${WP_PATH:-.}

echo "Running database migrations..."

<em># Migration 001: Add custom table</em>
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

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

<em># Migration 003: Create custom taxonomy</em>
wp taxonomy register custom_taxonomy --path=$WP_PATH

echo "✓ Migrations complete!"
</code></pre>



<h4 class="wp-block-heading" id="version-controlled-migrations">Version-Controlled Migrations</h4>



<p>Use migration files:</p>



<pre class="wp-block-code"><code><em># migrations/001-create-custom-table.sql</em>
CREATE TABLE IF NOT EXISTS wp_custom_data (
  id bigint(20) NOT NULL AUTO_INCREMENT,
  data longtext NOT NULL,
  PRIMARY KEY (id)
);

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



<p><strong>Migration runner:</strong></p>



<pre class="wp-block-code"><code>#!/bin/bash
<em># scripts/migrate.sh</em>

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

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

    if &#91;&#91; $migration == *.sql ]]; then
        wp db query &lt; $migration --path=$WP_PATH
    elif &#91;&#91; $migration == *.sh ]]; then
        bash $migration
    fi

    echo "✓ Completed: $(basename $migration)"
done
</code></pre>



<h3 class="wp-block-heading" id="rollback-strategy-rollback">Rollback Strategy</h3>



<h4 class="wp-block-heading" id="automated-rollback-on-failure">Automated Rollback on Failure</h4>



<pre class="wp-block-code"><code>- name: Deploy with rollback
  id: deploy
  run: |
    # Get current commit for rollback
    CURRENT_COMMIT=$(ssh $SERVER "cd $WP_PATH &amp;&amp; git rev-parse HEAD")
    echo "current_commit=$CURRENT_COMMIT" &gt;&gt; $GITHUB_OUTPUT

    <em># Deploy</em>
    ./scripts/deploy.sh

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

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

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

    <em># Restore code</em>
    ssh $SERVER "cd $WP_PATH &amp;&amp; git reset --hard ${{ steps.deploy.outputs.current_commit }}"

    <em># Clear caches</em>
    ssh $SERVER "cd $WP_PATH &amp;&amp; wp cache flush"

    echo "✓ Rollback complete"
    exit 1
</code></pre>



<h4 class="wp-block-heading" id="manual-rollback-workflow">Manual Rollback Workflow</h4>



<pre class="wp-block-code"><code><em># .github/workflows/rollback.yml</em>

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 }} &amp;&amp; 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/"
</code></pre>



<h3 class="wp-block-heading" id="complete-github-actions-workflow-complete-workflow">Complete GitHub Actions Workflow</h3>



<h4 class="wp-block-heading" id="production-ready-cicd">Production-Ready CI/CD</h4>



<pre class="wp-block-code"><code><em># .github/workflows/wordpress-cicd.yml</em>

name: WordPress CI/CD

on:
  push:
    branches: &#91;main, develop]
  pull_request:
    branches: &#91;main]
  workflow_dispatch:

env:
  PHP_VERSION: '8.1'

jobs:
  <em># Job 1: Run tests</em>
  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

  <em># Job 2: Deploy to staging</em>
  deploy-staging:
    name: Deploy to Staging
    needs: test
    if: github.ref == 'refs/heads/develop' &amp;&amp; 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 }}

  <em># Job 3: Deploy to production</em>
  deploy-production:
    name: Deploy to Production
    needs: test
    if: github.ref == 'refs/heads/main' &amp;&amp; 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 }}
</code></pre>



<h2 class="wp-block-heading" id="best-practices-best-practices">Best Practices</h2>



<h4 class="wp-block-heading" id="security">Security</h4>



<ol class="wp-block-list">
<li><strong>Never commit secrets</strong>&nbsp;&#8211; Use GitHub Secrets</li>



<li><strong>Restrict SSH access</strong>&nbsp;&#8211; Use dedicated deploy keys</li>



<li><strong>Audit deployments</strong>&nbsp;&#8211; Enable GitHub audit log</li>



<li><strong>Scan for vulnerabilities</strong>&nbsp;&#8211; Use GitHub&#8217;s Dependabot</li>
</ol>



<h4 class="wp-block-heading" id="deployment">Deployment</h4>



<ol class="wp-block-list">
<li><strong>Always test first</strong>&nbsp;&#8211; Run CI tests before deploy</li>



<li><strong>Use staging</strong>&nbsp;&#8211; Test on staging before production</li>



<li><strong>Backup before deploy</strong>&nbsp;&#8211; Automatic database backups</li>



<li><strong>Enable maintenance mode</strong>&nbsp;&#8211; During production deployments</li>



<li><strong>Monitor after deploy</strong>&nbsp;&#8211; Check logs and metrics</li>
</ol>



<h4 class="wp-block-heading" id="code-quality">Code Quality</h4>



<ol class="wp-block-list">
<li><strong>Use linters</strong>&nbsp;&#8211; PHP CodeSniffer, ESLint</li>



<li><strong>Run static analysis</strong>&nbsp;&#8211; PHPStan, Psalm</li>



<li><strong>Code coverage</strong>&nbsp;&#8211; Track test coverage</li>



<li><strong>Peer review</strong>&nbsp;&#8211; Require PR approvals</li>
</ol>



<h4 class="wp-block-heading" id="performance">Performance</h4>



<ol class="wp-block-list">
<li><strong>Cache workflows</strong>&nbsp;&#8211; Cache Composer dependencies</li>



<li><strong>Parallel jobs</strong>&nbsp;&#8211; Run tests in parallel</li>



<li><strong>Self-hosted runners</strong>&nbsp;&#8211; For faster deploys</li>
</ol>



<h3 class="wp-block-heading" id="troubleshooting-troubleshooting">Troubleshooting</h3>



<h4 class="wp-block-heading" id="issue-1-ssh-connection-failed">Issue 1: SSH Connection Failed</h4>



<p><strong>Problem</strong>:&nbsp;<code>Permission denied (publickey)</code></p>



<p><strong>Solutions</strong>:</p>



<ol class="wp-block-list">
<li>Verify SSH key is added to GitHub Secrets correctly</li>



<li>Test SSH connection:</li>
</ol>



<pre class="wp-block-code"><code>ssh -i ~/.ssh/github-actions user@server.com
</code></pre>



<ol start="3" class="wp-block-list">
<li>Check server&#8217;s&nbsp;<code>~/.ssh/authorized_keys</code></li>



<li>Verify SSH agent setup in workflow</li>
</ol>



<h4 class="wp-block-heading" id="issue-2-deployment-timeout">Issue 2: Deployment Timeout</h4>



<p><strong>Problem</strong>: Workflow times out during deployment</p>



<p><strong>Solutions</strong>:</p>



<ol class="wp-block-list">
<li>Increase timeout:</li>
</ol>



<pre class="wp-block-code"><code>jobs:
  deploy:
    timeout-minutes: 30  <em># Default is 360</em>
</code></pre>



<ol start="2" class="wp-block-list">
<li>Optimize deployment script</li>



<li>Use caching for dependencies</li>
</ol>



<h4 class="wp-block-heading" id="issue-3-database-migration-failed">Issue 3: Database Migration Failed</h4>



<p><strong>Problem</strong>: Migration fails, breaks site</p>



<p><strong>Solutions</strong>:</p>



<ol class="wp-block-list">
<li>Always backup before migrations</li>



<li>Test migrations on staging first</li>



<li>Use transactions when possible:</li>
</ol>



<pre class="wp-block-code"><code>START TRANSACTION;
<em>-- migration SQL</em>
COMMIT;
</code></pre>



<ol start="4" class="wp-block-list">
<li>Implement rollback in migration script</li>
</ol>



<h4 class="wp-block-heading" id="issue-4-cache-not-cleared">Issue 4: Cache Not Cleared</h4>



<p><strong>Problem</strong>: Changes don&#8217;t appear after deployment</p>



<p><strong>Solutions</strong>:</p>



<ol class="wp-block-list">
<li>Add cache clearing to deployment:</li>
</ol>



<pre class="wp-block-code"><code>wp cache flush
wp rewrite flush
wp transient delete --all
</code></pre>



<ol start="2" class="wp-block-list">
<li>Clear CDN cache if using one</li>



<li>Verify opcache is reset</li>
</ol>



<h4 class="wp-block-heading" id="issue-5-workflow-fails-silently">Issue 5: Workflow Fails Silently</h4>



<p><strong>Problem</strong>: Workflow completes but deployment didn&#8217;t work</p>



<p><strong>Solutions</strong>:</p>



<ol class="wp-block-list">
<li>Remove&nbsp;<code>set -e</code>&nbsp;temporarily to see errors</li>



<li>Add verbose logging:</li>
</ol>



<pre class="wp-block-code"><code>set -x  <em># Print commands</em>
</code></pre>



<ol start="3" class="wp-block-list">
<li>Check server logs:</li>
</ol>



<pre class="wp-block-code"><code>ssh server "tail -f /var/log/apache2/error.log"
</code></pre>



<h3 class="wp-block-heading" id="next-steps-next-steps">Next Steps</h3>



<p>Congratulations! You&#8217;ve built a production-ready WordPress CI/CD pipeline.</p>



<h4 class="wp-block-heading" id="enhance-your-pipeline">Enhance Your Pipeline</h4>



<ol class="wp-block-list">
<li><strong><a href="https://file+.vscode-resource.vscode-cdn.net/blog/multisite-bulk-operations-wpcli">WordPress Multisite CI/CD</a></strong>&nbsp;&#8211; Deploy to network</li>



<li><strong><a href="https://file+.vscode-resource.vscode-cdn.net/blog/automate-wordpress-backups-wpcli">Automated Backups</a></strong>&nbsp;&#8211; Integrate with deployment</li>



<li><strong><a href="https://file+.vscode-resource.vscode-cdn.net/blog/optimize-wordpress-performance-wpcli">Performance Testing</a></strong>&nbsp;&#8211; Add to CI</li>
</ol>



<h4 class="wp-block-heading" id="related-guides">Related Guides</h4>



<ul class="wp-block-list">
<li><strong><a href="#">Custom WP-CLI Commands</a></strong> &#8211; Build deployment commands</li>



<li><strong><a href="#">Environment Management</a></strong> &#8211; Advanced configs</li>



<li><strong><a href="#">Bash Functions</a></strong> &#8211; Improve scripts</li>
</ul>



<h4 class="wp-block-heading" id="advanced-topics">Advanced Topics</h4>



<p><strong>Add to your pipeline:</strong></p>



<ul class="wp-block-list">
<li>Visual regression testing (BackstopJS, Percy)</li>



<li>Load testing (k6, Apache JBench)</li>



<li>Security scanning (OWASP ZAP)</li>



<li>Accessibility testing (pa11y)</li>



<li>SEO validation</li>



<li>Automated changelogs</li>
</ul>



<h4 class="wp-block-heading" id="master-wordpress-devops">Master WordPress DevOps</h4>



<p>Want to build enterprise-grade WordPress automation?</p>



<p><strong><a href="/#get-started">Join WPCLI Mastery</a></strong> and learn:</p>



<ul class="wp-block-list">
<li>Complete CI/CD pipeline templates</li>



<li>Multi-environment WordPress architecture</li>



<li>Blue-green deployments</li>



<li>Automated rollbacks and disaster recovery</li>



<li>Early bird course pricing ($99 vs $199)</li>
</ul>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



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



<ul class="wp-block-list">
<li>Automated testing on every push</li>



<li>Safe staging deployments</li>



<li>Controlled production releases</li>



<li>Instant rollback capability</li>



<li>Complete audit trail</li>
</ul>



<p><strong>Key takeaways:</strong></p>



<ul class="wp-block-list">
<li>CI/CD catches errors before production</li>



<li>GitHub Actions integrates seamlessly with WordPress</li>



<li>WP-CLI makes WordPress automation possible</li>



<li>Staging → Production workflow ensures safety</li>



<li>Automated rollbacks prevent disasters</li>



<li>Database migrations need careful handling</li>
</ul>



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



<p><strong>Ready to implement?</strong>&nbsp;Start with the complete workflow and adapt to your needs.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>Questions about WordPress CI/CD?</strong>&nbsp;Drop a comment below!</p>



<p><strong>Found this helpful?</strong>&nbsp;Share with WordPress developers building automation.</p>



<p><strong>Next:</strong> Explore <a href="#">advanced WordPress deployment strategies</a> and zero-downtime techniques.</p>
<p>The post <a href="https://wpclimastery.com/blog/wordpress-ci-cd-pipeline-with-github-actions-and-wp-cli/">WordPress CI/CD Pipeline with GitHub Actions and WP-CLI</a> appeared first on <a href="https://wpclimastery.com">WP-CLI Mastery</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
