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 storageInstallation 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_NAMEquot; | 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"
fiScheduled 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.comNext Steps
You now have a complete production-ready WordPress deployment system.
Recommended Learning Path
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
- Multi-Server Deployments – Load-balanced WordPress
- Container-Based Deployments – Docker/Kubernetes
- GitOps Workflows – Infrastructure as code
Get More Resources
Download complete system including:
- Full deployment scripts
- Configuration templates
- Documentation
- 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.
