Building a Complete WordPress Deployment System with WP-CLI (Real Project)

Professional WordPress deployments require coordinating server provisioning, WordPress installation, plugin configuration, database migrations, zero-downtime releases, automated backups, health monitoring, and rollback capabilities. Managing these manually across development, staging, and production environments is error-prone and time-consuming.

A complete WordPress deployment system automates the entire lifecycleβ€”provision servers, install WordPress, deploy code changes, run migrations, monitor health, and rollback failuresβ€”all with single commands executed by WP-CLI and Bash scripts.

In this guide, you’ll build a production-ready WordPress deployment system used by professional development teams, integrating server provisioning, automated deployments, monitoring, and disaster recovery into one cohesive system.

System Architecture Overview

Understand the complete deployment system components and workflow.

Deployment System Components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Version Control    β”‚  ← Git repository (source of truth)
β”‚      (Git)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  CI/CD Pipeline     β”‚  ← GitHub Actions/GitLab CI
β”‚   (Automated)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Deployment Scripts  β”‚  ← WP-CLI + Bash automation
β”‚    (This System)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
    β”‚             β”‚
    β–Ό             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚Staging β”‚   β”‚  Prod  β”‚  ← WordPress environments
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

System Features

Server provisioning: Automated WordPress server setup from scratch.

Zero-downtime deploys: Release code without taking site offline.

Database migrations: Automatic schema changes with rollback.

Health monitoring: Continuous uptime and performance checks.

Automatic backups: Pre-deployment snapshots with retention policies.

One-command rollback: Instantly revert failed deployments.

Learn about DevOps deployment patterns.

Project Structure and Setup

Organize the deployment system codebase.

Directory Structure

wordpress-deploy-system/
β”œβ”€β”€ bin/                      # Executable scripts
β”‚   β”œβ”€β”€ deploy                # Main deployment command
β”‚   β”œβ”€β”€ provision             # Server provisioning
β”‚   β”œβ”€β”€ backup                # Backup management
β”‚   β”œβ”€β”€ rollback              # Rollback deployments
β”‚   └── monitor               # Health monitoring
β”œβ”€β”€ lib/                      # Reusable functions
β”‚   β”œβ”€β”€ common.sh             # Shared utilities
β”‚   β”œβ”€β”€ deploy.sh             # Deployment logic
β”‚   β”œβ”€β”€ database.sh           # DB operations
β”‚   └── wordpress.sh          # WordPress operations
β”œβ”€β”€ config/                   # Configuration files
β”‚   β”œβ”€β”€ environments/         # Environment-specific configs
β”‚   β”‚   β”œβ”€β”€ development.conf
β”‚   β”‚   β”œβ”€β”€ staging.conf
β”‚   β”‚   └── production.conf
β”‚   └── deploy.conf           # Global deployment config
β”œβ”€β”€ migrations/               # Database migrations
β”‚   β”œβ”€β”€ 001-initial-schema.sql
β”‚   └── 002-add-custom-tables.sql
β”œβ”€β”€ hooks/                    # Deployment hooks
β”‚   β”œβ”€β”€ pre-deploy.sh
β”‚   β”œβ”€β”€ post-deploy.sh
β”‚   └── health-check.sh
β”œβ”€β”€ logs/                     # Deployment logs
└── backups/                  # Backup storage

Installation Script

#!/bin/bash
# install.sh - Set up deployment system

INSTALL_DIR="/opt/wordpress-deploy"

echo "Installing WordPress Deployment System"
echo "======================================="

# Create directory structure
sudo mkdir -p "$INSTALL_DIR"/{bin,lib,config/environments,migrations,hooks,logs,backups}

# Copy files
sudo cp -r bin/* "$INSTALL_DIR/bin/"
sudo cp -r lib/* "$INSTALL_DIR/lib/"
sudo cp -r config/* "$INSTALL_DIR/config/"

# Make scripts executable
sudo chmod +x "$INSTALL_DIR"/bin/*
sudo chmod +x "$INSTALL_DIR"/hooks/*

# Add to PATH
echo "export PATH=\$PATH:$INSTALL_DIR/bin" | sudo tee -a /etc/profile.d/wordpress-deploy.sh

# Install WP-CLI if not present
if ! command -v wp &> /dev/null; then
    echo "Installing WP-CLI..."
    curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
    sudo mv wp-cli.phar /usr/local/bin/wp
    sudo chmod +x /usr/local/bin/wp
fi

echo "βœ“ Installation complete"
echo "Usage: deploy <environment> <release>"

Server Provisioning Automation

Automate complete WordPress server setup.

Provisioning Script

#!/bin/bash
# bin/provision - Provision WordPress server

set -euo pipefail

source "$(dirname "$0")/../lib/common.sh"

ENVIRONMENT="${1:-staging}"
CONFIG_FILE="$CONFIG_DIR/environments/$ENVIRONMENT.conf"

if [ ! -f "$CONFIG_FILE" ]; then
    error "Configuration not found: $CONFIG_FILE"
fi

source "$CONFIG_FILE"

log "Provisioning WordPress server for: $ENVIRONMENT"

# Update system packages
log "Updating system packages..."
sudo apt-get update
sudo apt-get upgrade -y

# Install LAMP stack
log "Installing LAMP stack..."
sudo apt-get install -y \
    nginx \
    mysql-server \
    php8.1-fpm \
    php8.1-mysql \
    php8.1-xml \
    php8.1-mbstring \
    php8.1-curl \
    php8.1-zip \
    php8.1-gd \
    php8.1-intl

# Configure Nginx
log "Configuring Nginx..."
sudo tee /etc/nginx/sites-available/wordpress <<EOF
server {
    listen 80;
    server_name $SERVER_NAME;
    root $DEPLOY_PATH/current;
    index index.php;

    location / {
        try_files \$uri \$uri/ /index.php?\$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# Create deployment directories
log "Creating deployment directories..."
sudo mkdir -p "$DEPLOY_PATH"/{releases,shared}
sudo mkdir -p "$DEPLOY_PATH/shared"/{wp-content/uploads,logs}

# Set permissions
sudo chown -R www-data:www-data "$DEPLOY_PATH"
sudo chmod -R 755 "$DEPLOY_PATH"

# Create MySQL database
log "Creating WordPress database..."
sudo mysql <<EOF
CREATE DATABASE IF NOT EXISTS $DB_NAME;
CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';
GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';
FLUSH PRIVILEGES;
EOF

# Install WordPress
log "Installing WordPress..."
cd "$DEPLOY_PATH"

wp core download --path=initial-install
cd initial-install

wp config create \
    --dbname="$DB_NAME" \
    --dbuser="$DB_USER" \
    --dbpass="$DB_PASS" \
    --dbhost=localhost

wp core install \
    --url="$SITE_URL" \
    --title="$SITE_TITLE" \
    --admin_user="$ADMIN_USER" \
    --admin_password="$ADMIN_PASS" \
    --admin_email="$ADMIN_EMAIL"

# Move to shared directory
mv wp-content/uploads/* "$DEPLOY_PATH/shared/wp-content/uploads/"
mv wp-config.php "$DEPLOY_PATH/shared/"

# Install SSL certificate (Let's Encrypt)
if [ "$SSL_ENABLED" = "true" ]; then
    log "Installing SSL certificate..."
    sudo apt-get install -y certbot python3-certbot-nginx
    sudo certbot --nginx -d "$SERVER_NAME" --non-interactive --agree-tos -m "$ADMIN_EMAIL"
fi

log "βœ“ Server provisioning complete"
log "WordPress URL: $SITE_URL"
log "Admin user: $ADMIN_USER"

Environment Configuration Template

# config/environments/production.conf

# Server settings
SERVER_NAME="example.com"
SITE_URL="https://example.com"
SITE_TITLE="Production Site"

# Deployment paths
DEPLOY_PATH="/var/www/production"
RELEASE_DIR="$DEPLOY_PATH/releases"
SHARED_DIR="$DEPLOY_PATH/shared"
CURRENT_LINK="$DEPLOY_PATH/current"

# Database credentials
DB_NAME="wp_production"
DB_USER="wp_user"
DB_PASS="strong-password-here"
DB_HOST="localhost"

# WordPress admin
ADMIN_USER="admin"
ADMIN_PASS="admin-password-here"
ADMIN_EMAIL="admin@example.com"

# Git repository
GIT_REPO="git@github.com:yourcompany/wordpress-site.git"
GIT_BRANCH="main"

# Deployment settings
KEEP_RELEASES=5
BACKUP_RETENTION_DAYS=30
SSL_ENABLED=true

# Notifications
SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
EMAIL_NOTIFICATIONS="team@example.com"

Learn about WordPress server requirements.

Zero-Downtime Deployment Implementation

Deploy code changes without taking site offline.

Main Deployment Script

#!/bin/bash
# bin/deploy - Main deployment command

set -euo pipefail

source "$(dirname "$0")/../lib/common.sh"
source "$(dirname "$0")/../lib/deploy.sh"

ENVIRONMENT="${1:-staging}"
RELEASE_TAG="${2:-HEAD}"
CONFIG_FILE="$CONFIG_DIR/environments/$ENVIRONMENT.conf"

if [ ! -f "$CONFIG_FILE" ]; then
    error "Configuration not found: $CONFIG_FILE"
fi

source "$CONFIG_FILE"

log "=== WordPress Deployment Started ==="
log "Environment: $ENVIRONMENT"
log "Release: $RELEASE_TAG"
log "Time: $(date)"

# Pre-deployment checks
log "Running pre-deployment checks..."
run_hook "pre-deploy" || error "Pre-deployment checks failed"

# Create backup
log "Creating pre-deployment backup..."
"$BIN_DIR/backup" "$ENVIRONMENT" || error "Backup failed"

# Prepare new release
RELEASE_ID=$(date +%Y%m%d_%H%M%S)
RELEASE_PATH="$RELEASE_DIR/$RELEASE_ID"

log "Creating release: $RELEASE_ID"
mkdir -p "$RELEASE_PATH"

# Clone code from Git
log "Cloning repository..."
git clone --depth 1 --branch "$GIT_BRANCH" "$GIT_REPO" "$RELEASE_PATH"
cd "$RELEASE_PATH"

# Install dependencies
log "Installing dependencies..."
if [ -f composer.json ]; then
    composer install --no-dev --optimize-autoloader
fi

if [ -f package.json ]; then
    npm ci --production
    npm run build
fi

# Link shared directories
log "Linking shared resources..."
ln -nfs "$SHARED_DIR/wp-content/uploads" "$RELEASE_PATH/wp-content/uploads"
ln -nfs "$SHARED_DIR/wp-config.php" "$RELEASE_PATH/wp-config.php"

# Run database migrations
log "Running database migrations..."
run_migrations "$RELEASE_PATH" || {
    error "Database migration failed"
    rollback_deployment "$ENVIRONMENT" "$RELEASE_ID"
    exit 1
}

# Health check on new release
log "Running health checks..."
test_release "$RELEASE_PATH" || {
    error "Health checks failed"
    rollback_deployment "$ENVIRONMENT" "$RELEASE_ID"
    exit 1
}

# Atomic switch to new release
log "Switching to new release..."
ln -nfs "$RELEASE_PATH" "$CURRENT_LINK"

# Post-deployment tasks
log "Running post-deployment tasks..."
cd "$CURRENT_LINK"
wp cache flush
wp rewrite flush

run_hook "post-deploy"

# Cleanup old releases
log "Cleaning up old releases..."
cleanup_releases "$RELEASE_DIR" "$KEEP_RELEASES"

# Send notifications
notify_deployment "success" "$ENVIRONMENT" "$RELEASE_ID"

log "=== Deployment Complete ==="
log "Active release: $RELEASE_ID"
log "Site URL: $SITE_URL"

Shared Functions Library

# lib/common.sh - Common utilities

LOG_FILE="$LOG_DIR/deploy-$(date +%Y%m%d).log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $@" | tee -a "$LOG_FILE"
}

error() {
    echo "[ERROR] $@" | tee -a "$LOG_FILE" >&2
    notify_deployment "failure" "$ENVIRONMENT" "$RELEASE_ID"
    exit 1
}

run_hook() {
    local hook_name="$1"
    local hook_script="$HOOKS_DIR/${hook_name}.sh"

    if [ -f "$hook_script" ]; then
        log "Running hook: $hook_name"
        bash "$hook_script" "$ENVIRONMENT"
    fi
}

notify_deployment() {
    local status="$1"
    local environment="$2"
    local release="$3"

    # Slack notification
    if [ -n "$SLACK_WEBHOOK" ]; then
        curl -X POST "$SLACK_WEBHOOK" \
            -H 'Content-Type: application/json' \
            -d "{\"text\":\"Deployment $status: $environment ($release)\"}"
    fi

    # Email notification
    if [ -n "$EMAIL_NOTIFICATIONS" ]; then
        echo "Deployment $status on $environment (Release: $release)" | \
            mail -s "WordPress Deployment $status" "$EMAIL_NOTIFICATIONS"
    fi
}

cleanup_releases() {
    local release_dir="$1"
    local keep_count="$2"

    cd "$release_dir"
    ls -t | tail -n +$((keep_count + 1)) | xargs rm -rf
}

Database Migration System

Manage database schema changes safely.

Migration Runner

# lib/database.sh - Database migration functions

run_migrations() {
    local release_path="$1"
    local migration_dir="$MIGRATIONS_DIR"
    local applied_file="$SHARED_DIR/.applied-migrations"

    touch "$applied_file"

    log "Checking for pending migrations..."

    for migration in $(ls "$migration_dir"/*.sql | sort); do
        local migration_name=$(basename "$migration")

        # Check if already applied
        if grep -q "$migration_name" "$applied_file"; then
            log "  ⊘ Skipping: $migration_name (already applied)"
            continue
        fi

        log "  β†’ Applying: $migration_name"

        # Run migration
        if wp db query < "$migration" --path="$release_path"; then
            echo "$migration_name" >> "$applied_file"
            log "  βœ“ Applied: $migration_name"
        else
            error "Migration failed: $migration_name"
        fi
    done

    log "βœ“ All migrations applied"
}

rollback_migration() {
    local migration_name="$1"
    local rollback_file="$MIGRATIONS_DIR/${migration_name%.sql}-rollback.sql"

    if [ -f "$rollback_file" ]; then
        log "Rolling back migration: $migration_name"
        wp db query < "$rollback_file"
    else
        error "Rollback file not found: $rollback_file"
    fi
}

Example Migration Files

-- migrations/001-add-custom-tables.sql

CREATE TABLE IF NOT EXISTS wp_custom_data (
    id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    user_id BIGINT(20) UNSIGNED NOT NULL,
    data_key VARCHAR(255) NOT NULL,
    data_value LONGTEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    KEY user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- migrations/001-add-custom-tables-rollback.sql

DROP TABLE IF EXISTS wp_custom_data;

Rollback System

Instantly revert failed deployments.

Rollback Script

#!/bin/bash
# bin/rollback - Rollback to previous release

set -euo pipefail

source "$(dirname "$0")/../lib/common.sh"

ENVIRONMENT="${1:-staging}"
CONFIG_FILE="$CONFIG_DIR/environments/$ENVIRONMENT.conf"

source "$CONFIG_FILE"

log "=== Starting Rollback ==="

# Get current release
CURRENT=$(readlink "$CURRENT_LINK")
CURRENT_NAME=$(basename "$CURRENT")

log "Current release: $CURRENT_NAME"

# Get previous release
PREVIOUS=$(ls -t "$RELEASE_DIR" | grep -v "^$CURRENT_NAME
quot;
| head -n 1)
if [ -z "$PREVIOUS" ]; then error "No previous release found for rollback" fi PREVIOUS_PATH="$RELEASE_DIR/$PREVIOUS" log "Rolling back to: $PREVIOUS" # Backup current state (in case rollback fails) ROLLBACK_BACKUP="$BACKUP_DIR/pre-rollback-$(date +%Y%m%d_%H%M%S)" mkdir -p "$ROLLBACK_BACKUP" wp db export "$ROLLBACK_BACKUP/database.sql.gz" --path="$CURRENT" # Switch to previous release ln -nfs "$PREVIOUS_PATH" "$CURRENT_LINK" # Clear caches cd "$CURRENT_LINK" wp cache flush wp rewrite flush # Verify rollback if wp core is-installed --path="$CURRENT_LINK"; then log "βœ“ Rollback successful" notify_deployment "rollback-success" "$ENVIRONMENT" "$PREVIOUS" else error "Rollback verification failed" fi log "=== Rollback Complete ===" log "Active release: $PREVIOUS"

Health Monitoring System

Continuous uptime and performance monitoring.

Monitoring Script

#!/bin/bash
# bin/monitor - WordPress health monitoring

set -euo pipefail

source "$(dirname "$0")/../lib/common.sh"

ENVIRONMENT="${1:-production}"
CONFIG_FILE="$CONFIG_DIR/environments/$ENVIRONMENT.conf"

source "$CONFIG_FILE"

check_site_health() {
    local url="$1"
    local response=$(curl -s -o /dev/null -w "%{http_code}" "$url")

    if [ "$response" = "200" ]; then
        log "βœ“ Site accessible: $url"
        return 0
    else
        error "βœ— Site down: $url (HTTP $response)"
        return 1
    fi
}

check_database_connection() {
    if wp db check --path="$CURRENT_LINK"; then
        log "βœ“ Database connection OK"
        return 0
    else
        error "βœ— Database connection failed"
        return 1
    fi
}

check_disk_space() {
    local usage=$(df -h "$DEPLOY_PATH" | awk 'NR==2 {print $5}' | sed 's/%//')

    if [ "$usage" -lt 80 ]; then
        log "βœ“ Disk space OK ($usage%)"
        return 0
    else
        log "⚠ Disk space warning: $usage%"
        return 1
    fi
}

check_plugin_updates() {
    local updates=$(wp plugin list --update=available --format=count --path="$CURRENT_LINK")

    if [ "$updates" -eq 0 ]; then
        log "βœ“ All plugins up to date"
    else
        log "⚠ $updates plugin updates available"
    fi
}

# Run all checks
log "=== Health Check: $ENVIRONMENT ==="

FAILED=0

check_site_health "$SITE_URL" || FAILED=$((FAILED + 1))
check_database_connection || FAILED=$((FAILED + 1))
check_disk_space || FAILED=$((FAILED + 1))
check_plugin_updates

if [ "$FAILED" -gt 0 ]; then
    error "$FAILED health checks failed"
else
    log "βœ“ All health checks passed"
fi

Scheduled Monitoring

# Add to crontab: crontab -e

# Health check every 5 minutes
*/5 * * * * /opt/wordpress-deploy/bin/monitor production >> /var/log/wp-monitor.log 2>&1

# Daily deployment report
0 9 * * * /opt/wordpress-deploy/bin/report production | mail -s "Daily WordPress Report" team@example.com

Next Steps

You now have a complete production-ready WordPress deployment system.

Week 1: System setup

  • Install deployment system
  • Configure environments
  • Test provisioning

Week 2: Deployment workflow

  • Practice deployments
  • Test rollback procedures
  • Configure notifications

Week 3: CI/CD integration

  • Connect to GitHub Actions
  • Automate testing
  • Deploy on merge

Week 4: Production hardening

  • Implement monitoring
  • Optimize performance
  • Document procedures

Advanced Topics

  1. Multi-Server Deployments – Load-balanced WordPress
  2. Container-Based Deployments – Docker/Kubernetes
  3. GitOps Workflows – Infrastructure as code

Get More Resources

Download complete system including:

  • Full deployment scripts
  • Configuration templates
  • Documentation

Join our email course for:

  • Weekly WP-CLI tutorials
  • DevOps best practices
  • Production war stories

Conclusion

A complete WordPress deployment system automates the entire lifecycle from server provisioning to zero-downtime deployments, monitoring, and rollbackβ€”enabling professional DevOps workflows for WordPress sites.

What we covered:

βœ… Complete system architecture and project structure
βœ… Automated server provisioning from scratch
βœ… Zero-downtime deployment implementation
βœ… Database migration system with rollback
βœ… Instant rollback for failed deployments
βœ… Continuous health monitoring and alerting

Build this system, and you’ll deploy WordPress professionallyβ€”automated provisioning, zero-downtime releases, instant rollbacks, and comprehensive monitoring for production-grade WordPress operations.

Ready for more? Learn high-availability WordPress or enterprise DevOps.

Questions about WordPress deployment systems? Drop a comment below!

Found this helpful? Share with other DevOps engineers.