Professional WordPress development requires managing multiple environments to test changes safely before deploying to production. WP-CLI streamlines environment management, enabling efficient synchronization, configuration control, and deployment workflows that minimize downtime and reduce errors.
Understanding WordPress Environment Architecture
A robust WordPress workflow typically includes three distinct environments: development for building new features, staging for testing changes in production-like conditions, and production where live users interact with your site.
Each environment serves a specific purpose. Development allows rapid iteration without affecting users, staging validates changes before release, and production serves actual traffic. Proper environment management ensures code flows smoothly through this pipeline while maintaining data integrity and security.
Setting Up Environment-Specific Configurations
WordPress 5.5 introduced native environment type support through the WP_ENVIRONMENT_TYPE constant. WP-CLI leverages this for environment-aware operations.
# Set environment type in wp-config.php
wp config set WP_ENVIRONMENT_TYPE 'development' --raw
# Verify current environment
wp config get WP_ENVIRONMENT_TYPE
# Different values: local, development, staging, production
Create environment-specific configuration files:
# wp-config-local.php for development
cat > wp-config-local.php << 'EOF'
<?php
// Development settings
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', true);
define('SCRIPT_DEBUG', true);
define('SAVEQUERIES', true);
// Disable caching in development
define('WP_CACHE', false);
// Development database
define('DB_NAME', 'wp_dev');
define('DB_USER', 'dev_user');
define('DB_PASSWORD', 'dev_password');
define('DB_HOST', 'localhost');
EOF
# wp-config-staging.php
cat > wp-config-staging.php << 'EOF'
<?php
// Staging settings
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
// Prevent search engine indexing
define('DISCOURAGE_SEARCH', true);
// Staging database
define('DB_NAME', 'wp_staging');
define('DB_USER', 'staging_user');
define('DB_PASSWORD', 'staging_password');
define('DB_HOST', 'localhost');
EOF
# wp-config-production.php
cat > wp-config-production.php << 'EOF'
<?php
// Production settings
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
// Security enhancements
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', false);
define('FORCE_SSL_ADMIN', true);
// Production database
define('DB_NAME', 'wp_production');
define('DB_USER', 'prod_user');
define('DB_PASSWORD', 'secure_production_password');
define('DB_HOST', 'production-db.example.com');
EOF
Database Synchronization Strategies
Syncing databases between environments requires careful handling of environment-specific data like URLs, file paths, and credentials.
#!/bin/bash
# sync-db-staging-to-dev.sh - Sync staging database to development
set -euo pipefail
STAGING_PATH="/var/www/staging"
DEV_PATH="/var/www/development"
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
echo "Starting database sync from staging to development"
# Backup development database before sync
echo "Backing up development database..."
wp --path="$DEV_PATH" db export "$BACKUP_DIR/dev-pre-sync-$TIMESTAMP.sql"
# Export staging database
echo "Exporting staging database..."
wp --path="$STAGING_PATH" db export "$BACKUP_DIR/staging-export-$TIMESTAMP.sql"
# Get staging URL
STAGING_URL=$(wp --path="$STAGING_PATH" option get siteurl)
# Get development URL
DEV_URL=$(wp --path="$DEV_PATH" option get siteurl)
# Import to development
echo "Importing to development..."
wp --path="$DEV_PATH" db import "$BACKUP_DIR/staging-export-$TIMESTAMP.sql"
# Update URLs
echo "Updating URLs from $STAGING_URL to $DEV_URL"
wp --path="$DEV_PATH" search-replace "$STAGING_URL" "$DEV_URL" --all-tables
# Update file paths if different
STAGING_PATH_ABS=$(wp --path="$STAGING_PATH" eval 'echo ABSPATH;')
DEV_PATH_ABS=$(wp --path="$DEV_PATH" eval 'echo ABSPATH;')
if [ "$STAGING_PATH_ABS" != "$DEV_PATH_ABS" ]; then
echo "Updating file paths..."
wp --path="$DEV_PATH" search-replace "$STAGING_PATH_ABS" "$DEV_PATH_ABS" --all-tables
fi
# Flush cache
wp --path="$DEV_PATH" cache flush
# Update permalinks
wp --path="$DEV_PATH" rewrite flush
echo "Database sync completed successfully"
Selective Data Synchronization
Production databases often contain sensitive user data that shouldn’t be copied to development environments. Implement selective synchronization:
#!/bin/bash
# sync-selective.sh - Sync database with data sanitization
WP_PATH="/var/www/development"
TEMP_SQL="/tmp/staging-sanitized.sql"
# Export from staging (on staging server)
ssh staging@staging.example.com "wp --path=/var/www/html db export - " > "$TEMP_SQL"
# Import to development
wp --path="$WP_PATH" db import "$TEMP_SQL"
# Sanitize sensitive data in development
echo "Sanitizing user data..."
# Anonymize user emails (except admin)
wp --path="$WP_PATH" db query "
UPDATE wp_users
SET user_email = CONCAT('user', ID, '@example.local')
WHERE ID != 1
"
# Clear user passwords
wp --path="$WP_PATH" db query "
UPDATE wp_users
SET user_pass = MD5(CONCAT('dev_password_', ID))
"
# Remove personal data from comments
wp --path="$WP_PATH" db query "
UPDATE wp_comments
SET comment_author_email = CONCAT('commenter', comment_ID, '@example.local'),
comment_author_IP = '127.0.0.1'
"
# Clear transients and cached data
wp --path="$WP_PATH" transient delete --all
# Update site URLs
wp --path="$WP_PATH" search-replace 'https://staging.example.com' 'http://dev.local' --all-tables
echo "Selective sync completed with data sanitization"
rm "$TEMP_SQL"
File Synchronization
Sync uploads and plugin/theme files between environments while excluding unnecessary files.
#!/bin/bash
# sync-files.sh - Sync WordPress files between environments
SOURCE_PATH="/var/www/staging"
DEST_PATH="/var/www/development"
# Sync uploads directory
echo "Syncing uploads..."
rsync -avz --delete \
--exclude='cache/' \
--exclude='backup/' \
"$SOURCE_PATH/wp-content/uploads/" \
"$DEST_PATH/wp-content/uploads/"
# Sync plugins (optional - usually managed by version control)
echo "Syncing plugins..."
rsync -avz --delete \
--exclude='*/cache' \
--exclude='*.log' \
"$SOURCE_PATH/wp-content/plugins/" \
"$DEST_PATH/wp-content/plugins/"
# Sync themes
echo "Syncing themes..."
rsync -avz --delete \
"$SOURCE_PATH/wp-content/themes/" \
"$DEST_PATH/wp-content/themes/"
# Set correct permissions
chown -R www-data:www-data "$DEST_PATH/wp-content"
find "$DEST_PATH/wp-content" -type d -exec chmod 755 {} \;
find "$DEST_PATH/wp-content" -type f -exec chmod 644 {} \;
echo "File sync completed"
Environment-Aware Deployment Scripts
Create intelligent deployment scripts that adapt based on target environment.
#!/bin/bash
# deploy.sh - Environment-aware deployment script
set -euo pipefail
# Configuration
ENVIRONMENTS=("development" "staging" "production")
TARGET_ENV="$1"
if [[ ! " ${ENVIRONMENTS[@]} " =~ " ${TARGET_ENV} " ]]; then
echo "Error: Invalid environment. Use: development, staging, or production"
exit 1
fi
# Load environment-specific configuration
case $TARGET_ENV in
development)
WP_PATH="/var/www/development"
BACKUP_RETENTION=7
UPDATE_CORE=true
UPDATE_PLUGINS=true
;;
staging)
WP_PATH="/var/www/staging"
BACKUP_RETENTION=14
UPDATE_CORE=true
UPDATE_PLUGINS=true
;;
production)
WP_PATH="/var/www/html"
BACKUP_RETENTION=30
UPDATE_CORE=false # Manual approval required
UPDATE_PLUGINS=false
;;
esac
echo "Deploying to: $TARGET_ENV"
# Pre-deployment checks
echo "Running pre-deployment checks..."
# Verify WordPress is installed
if ! wp --path="$WP_PATH" core is-installed; then
echo "Error: WordPress not properly installed at $WP_PATH"
exit 1
fi
# Check database connection
if ! wp --path="$WP_PATH" db check; then
echo "Error: Database connection failed"
exit 1
fi
# Create backup
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/backups/$TARGET_ENV"
mkdir -p "$BACKUP_DIR"
echo "Creating backup..."
wp --path="$WP_PATH" db export "$BACKUP_DIR/pre-deploy-$TIMESTAMP.sql"
# Clean old backups
find "$BACKUP_DIR" -name "pre-deploy-*.sql" -mtime +$BACKUP_RETENTION -delete
# Maintenance mode
if [[ "$TARGET_ENV" == "production" ]]; then
echo "Enabling maintenance mode..."
wp --path="$WP_PATH" maintenance-mode activate
fi
# Deploy code (assuming Git deployment)
echo "Pulling latest code..."
cd "$WP_PATH"
git pull origin "$(git rev-parse --abbrev-ref HEAD)"
# Install/update dependencies
if [ -f "composer.json" ]; then
composer install --no-dev --optimize-autoloader
fi
# Run database migrations if needed
if wp --path="$WP_PATH" plugin is-installed wp-migrate-db; then
wp --path="$WP_PATH" migratedb migrate
fi
# Update plugins and core if allowed
if [[ "$UPDATE_PLUGINS" == true ]]; then
echo "Updating plugins..."
wp --path="$WP_PATH" plugin update --all --exclude=custom-plugin
fi
if [[ "$UPDATE_CORE" == true ]]; then
echo "Updating WordPress core..."
wp --path="$WP_PATH" core update --minor
fi
# Clear caches
echo "Clearing caches..."
wp --path="$WP_PATH" cache flush
wp --path="$WP_PATH" transient delete --expired
if wp --path="$WP_PATH" plugin is-installed wp-super-cache --status=active; then
wp --path="$WP_PATH" cache flush
fi
# Regenerate assets if needed
if [ -f "package.json" ]; then
npm run build
fi
# Update permalinks
wp --path="$WP_PATH" rewrite flush
# Deactivate maintenance mode
if [[ "$TARGET_ENV" == "production" ]]; then
echo "Disabling maintenance mode..."
wp --path="$WP_PATH" maintenance-mode deactivate
fi
# Post-deployment verification
echo "Running post-deployment verification..."
# Check site is accessible
SITE_URL=$(wp --path="$WP_PATH" option get siteurl)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$SITE_URL")
if [ "$HTTP_CODE" != "200" ]; then
echo "Warning: Site returned HTTP $HTTP_CODE"
if [[ "$TARGET_ENV" == "production" ]]; then
echo "Rolling back..."
wp --path="$WP_PATH" db import "$BACKUP_DIR/pre-deploy-$TIMESTAMP.sql"
exit 1
fi
fi
# Verify core integrity
if ! wp --path="$WP_PATH" core verify-checksums; then
echo "Warning: Core checksum verification failed"
fi
echo "Deployment to $TARGET_ENV completed successfully"
# Send notification (production only)
if [[ "$TARGET_ENV" == "production" ]]; then
echo "Deployment completed at $(date)" | mail -s "Production Deployment Success" admin@example.com
fi
Configuration Management
Manage environment-specific settings without hardcoding sensitive values.
#!/bin/bash
# config-manager.sh - Environment configuration management
WP_PATH="/var/www/html"
ENV_FILE=".env"
# Load environment variables from .env file
if [ -f "$ENV_FILE" ]; then
export $(cat "$ENV_FILE" | grep -v '^#' | xargs)
fi
# Set configuration based on environment
set_config() {
local key="$1"
local value="$2"
local type="${3:-constant}"
if [[ "$type" == "constant" ]]; then
wp --path="$WP_PATH" config set "$key" "$value" --raw
else
wp --path="$WP_PATH" option update "$key" "$value"
fi
}
# Get current environment
CURRENT_ENV=$(wp --path="$WP_PATH" config get WP_ENVIRONMENT_TYPE 2>/dev/null || echo "production")
echo "Configuring for environment: $CURRENT_ENV"
# Apply environment-specific settings
case $CURRENT_ENV in
development|local)
set_config WP_DEBUG true constant
set_config WP_DEBUG_LOG true constant
set_config WP_DEBUG_DISPLAY true constant
set_config SCRIPT_DEBUG true constant
set_config SAVEQUERIES true constant
# Development plugins
wp --path="$WP_PATH" plugin activate query-monitor
wp --path="$WP_PATH" plugin activate debug-bar
;;
staging)
set_config WP_DEBUG true constant
set_config WP_DEBUG_LOG true constant
set_config WP_DEBUG_DISPLAY false constant
set_config SCRIPT_DEBUG false constant
# Disable search indexing
set_config blog_public 0 option
# Staging-specific plugins
wp --path="$WP_PATH" plugin deactivate google-analytics
;;
production)
set_config WP_DEBUG false constant
set_config WP_DEBUG_LOG false constant
set_config WP_DEBUG_DISPLAY false constant
set_config SCRIPT_DEBUG false constant
# Security settings
set_config DISALLOW_FILE_EDIT true constant
set_config FORCE_SSL_ADMIN true constant
# Enable search indexing
set_config blog_public 1 option
# Deactivate development plugins
wp --path="$WP_PATH" plugin deactivate query-monitor --quiet 2>/dev/null || true
wp --path="$WP_PATH" plugin deactivate debug-bar --quiet 2>/dev/null || true
;;
esac
echo "Configuration applied for $CURRENT_ENV environment"
Automated Environment Testing
Verify environment configuration and functionality before accepting deployments.
#!/bin/bash
# test-environment.sh - Automated environment testing
WP_PATH="/var/www/staging"
echo "=== Environment Testing ==="
# Test WordPress installation
echo "Testing WordPress installation..."
if wp --path="$WP_PATH" core is-installed; then
echo "✓ WordPress is installed"
else
echo "✗ WordPress installation check failed"
exit 1
fi
# Test database connection
echo "Testing database connection..."
if wp --path="$WP_PATH" db check; then
echo "✓ Database connection successful"
else
echo "✗ Database connection failed"
exit 1
fi
# Verify core files
echo "Verifying core files..."
if wp --path="$WP_PATH" core verify-checksums; then
echo "✓ Core files verified"
else
echo "⚠ Core checksum verification failed"
fi
# Check for required plugins
REQUIRED_PLUGINS=("wordpress-seo" "wordfence")
echo "Checking required plugins..."
for plugin in "${REQUIRED_PLUGINS[@]}"; do
if wp --path="$WP_PATH" plugin is-installed "$plugin"; then
STATUS=$(wp --path="$WP_PATH" plugin status "$plugin" 2>&1 | grep -o "Active" || echo "Inactive")
echo "✓ $plugin: $STATUS"
else
echo "✗ $plugin: Not installed"
fi
done
# Test URL accessibility
echo "Testing site accessibility..."
SITE_URL=$(wp --path="$WP_PATH" option get siteurl)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$SITE_URL")
if [ "$HTTP_CODE" == "200" ]; then
echo "✓ Site accessible (HTTP $HTTP_CODE)"
else
echo "✗ Site returned HTTP $HTTP_CODE"
exit 1
fi
# Check permalink structure
echo "Testing permalinks..."
wp --path="$WP_PATH" rewrite flush
if wp --path="$WP_PATH" rewrite list > /dev/null 2>&1; then
echo "✓ Permalinks configured"
else
echo "✗ Permalink configuration issue"
fi
# Performance checks
echo "Running performance checks..."
DB_SIZE=$(wp --path="$WP_PATH" db size --human-readable --format=json | jq -r '.Size')
echo "Database size: $DB_SIZE"
AUTOLOAD=$(wp --path="$WP_PATH" db query "SELECT CONCAT(ROUND(SUM(LENGTH(option_value))/1024/1024,2),'MB') as size FROM wp_options WHERE autoload='yes'" --skip-column-names)
echo "Autoload size: $AUTOLOAD"
echo "=== Environment Testing Complete ==="

Leave a Reply