Understanding the Deployment Landscape for WordPress
WordPress developers have long wrestled with the challenge of moving code changes from local environments to production servers without breaking live sites. Traditional FTP workflows create version control gaps, introduce human error, and make rollbacks painful. WP Engine recognized this friction early and built a platform that treats Git as a first-class citizen, but the real power emerges when you connect their Git push endpoints to a continuous integration pipeline.
The managed WordPress host provides two distinct Git remotes per environment: one for production and one for staging. Each remote accepts pushes to a specific branch (typically main or master) and triggers an automated deployment process that includes dependency installation, cache clearing, and health checks. This native capability works well for straightforward projects, yet teams adopting modern development practices quickly outgrow simple push-to-deploy workflows.
Why Add a CI/CD Layer?
WP Engine’s built-in deployment handles the basics competently. Code arrives, Composer runs, the cache flushes, and the site reloads. However, serious development teams need more than file synchronization. They need automated testing, code quality gates, database migration safety nets, and the ability to deploy conditionally based on branch or tag patterns.
A continuous integration pipeline adds these capabilities without replacing WP Engine’s deployment mechanism. Instead, the pipeline becomes the orchestrator: it runs tests, builds assets, validates configuration, and only then pushes to the appropriate WP Engine remote. This separation of concerns means your deployment logic lives in version-controlled YAML files rather than tribal knowledge or fragile shell scripts.
Consider a typical scenario: a developer opens a pull request. The pipeline spins up, runs PHPUnit and PHPStan against the codebase, compiles frontend assets with Vite or Webpack, and reports results back to the pull request. Only after human approval and a merge to main does the pipeline push to WP Engine’s production remote. This workflow catches syntax errors, failing tests, and missing dependencies before they reach a live environment.
Choosing Your Pipeline Platform
GitHub Actions, GitLab CI/CD, Bitbucket Pipelines, and CircleCI all integrate cleanly with WP Engine’s Git remotes. The choice often comes down to where your repositories already live. GitHub Actions offers generous free minutes for public repositories and deep integration with the GitHub ecosystem. GitLab CI/CD excels if you self-host GitLab or rely on its built-in container registry. Bitbucket Pipelines makes sense for teams anchored in the Atlassian stack.
All four platforms share the same core concept: define jobs in YAML, run them in isolated containers, and use SSH keys or deploy tokens to authenticate with WP Engine. The authentication step deserves attention. WP Engine supports SSH key pairs added through their user portal, but deploy tokens (generated per environment) often simplify pipeline configuration because they avoid SSH agent forwarding complexities in containerized runners.
Setting Up Authentication Safely
Never hardcode credentials in pipeline configuration files. Every major CI platform provides encrypted secret storage. In GitHub Actions, navigate to Settings > Secrets and variables > Actions and add two repository secrets: WPE_SSH_KEY_PRIVATE containing your private key and WPE_HOST containing the Git remote hostname (something like git.wpengine.com). For deploy tokens, store the token as WPE_DEPLOY_TOKEN and the environment name as WPE_ENV.
Generate an SSH key pair specifically for CI use. Run ssh-keygen -t ed25519 -f wpengine_ci -N "" locally, add the public key to your WP Engine user profile under SSH Keys, and store the private key in your CI platform’s secret store. Restrict the key to deployment-only access if your team uses WP Engine’s role-based access controls. This limits blast radius if the key ever leaks.
A Practical GitHub Actions Workflow
The following workflow demonstrates a production-ready pattern. It triggers on pushes to main, runs static analysis and tests, builds production assets, and deploys to WP Engine only when everything passes.
name: Deploy to WP Engine Production
on:
push:
branches: [main]
workflow_dispatch:
jobs:
test-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, xml, curl, zip, gd
coverage: none
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run PHPStan
run: vendor/bin/phpstan analyse --memory-limit=256M
- name: Run PHPUnit
run: vendor/bin/phpunit --configuration phpunit.xml.dist
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install and build frontend assets
run: |
npm ci
npm run build
- name: Prepare deployment archive
run: |
rsync -av --exclude='.git' --exclude='node_modules' --exclude='.github' --exclude='tests' --exclude='phpunit.xml*' --exclude='phpstan.neon*' ./ ./deploy-package/
- uses: actions/upload-artifact@v4
with:
name: deploy-package
path: deploy-package/
retention-days: 1
deploy:
needs: test-and-build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/download-artifact@v4
with:
name: deploy-package
path: deploy-package
- name: Configure Git
run: |
git config --global user.email "ci-bot@example.com"
git config --global user.name "CI Bot"
- name: Add WP Engine remote
run: |
git remote add production ssh://git@${{ secrets.WPE_HOST }}/production/${{ secrets.WPE_ENV }}.git
- name: Deploy to WP Engine
env:
WPE_SSH_KEY: ${{ secrets.WPE_SSH_KEY_PRIVATE }}
run: |
mkdir -p ~/.ssh
echo "$WPE_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.WPE_HOST }} >> ~/.ssh/known_hosts
cd deploy-package
git init
git add .
git commit -m "Deploy $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
git push --force production HEAD:main
This workflow separates testing from deployment. The test-and-build job produces a clean artifact containing only production-ready files. The deploy job downloads that artifact, initializes a fresh Git repository, and force-pushes to WP Engine. Force pushing is necessary because each deployment creates a new commit history unrelated to previous ones. WP Engine’s deployment process handles this gracefully.
Handling WordPress-Specific Concerns
WordPress deployments involve more than PHP files. The wp-content directory contains plugins, themes, and uploads. Your repository should track custom themes and plugins, but never the uploads folder or core WordPress files. Use .gitignore to exclude wp-content/uploads, wp-includes, and wp-admin. WP Engine manages core updates separately through their dashboard or automated update settings.
Database changes present a separate challenge. WP Engine does not run database migrations automatically. If your deployment includes schema changes, you need a migration strategy. Some teams use WP-CLI commands in a post-deploy hook. Others maintain a separate migration script repository and run migrations manually or through a dedicated pipeline stage. WP Engine’s SSH gateway allows WP-CLI execution, so you can add a migration step to your pipeline:
- name: Run database migrations
env:
WPE_SSH_KEY: ${{ secrets.WPE_SSH_KEY_PRIVATE }}
WPE_ENV: ${{ secrets.WPE_ENV }}
run: |
mkdir -p ~/.ssh
echo "$WPE_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.WPE_HOST }} >> ~/.ssh/known_hosts
ssh -o StrictHostKeyChecking=no ${{ secrets.WPE_ENV }}@${{ secrets.WPE_HOST }} "wp db import /path/to/migration.sql --path=/sites/${{ secrets.WPE_ENV }}"
Exercise caution with automated database operations. A failed migration on production can leave the site in a broken state. Many teams prefer running migrations manually after verifying the deployment succeeded, or they implement a blue-green deployment pattern using WP Engine’s staging environment as a validation step.
Leveraging Staging Environments
WP Engine provides staging environments that mirror production infrastructure. Use them. A robust pipeline deploys to staging first, runs smoke tests or end-to-end tests against the staging URL, and only promotes to production after validation. GitHub Environments make this elegant: define a staging environment with protection rules requiring approval, then a production environment with additional gates.
Your staging deployment job might look nearly identical to production, just targeting a different remote. The staging environment name differs (often staging or staging-environment-name), and you might skip certain production-only steps like CDN cache purging or search index rebuilding.
WP Engine’s staging environment also supports “copy to production” through their dashboard, but doing so bypasses your pipeline’s validation. Keep deployment authority in your CI/CD system. Treat the dashboard as a viewing tool, not a deployment trigger.
Managing Dependencies and Build Artifacts
Composer and npm dependencies belong in your repository’s lock files (composer.lock and package-lock.json), not in the repository itself. Your pipeline installs them fresh on every run. This guarantees reproducibility and avoids committing thousands of vendor files.
WP Engine runs composer install --no-dev --optimize-autoloader on their side when they receive a push. Your pipeline should run the same command during the build phase so tests execute against production-like autoloaded classes. However, you need development dependencies (PHPUnit, PHPStan, testing libraries) for the test phase. Install dependencies twice: once with dev dependencies for testing, then again without for the deployment artifact.
Frontend assets deserve special attention. If your theme or plugin uses a build step (Sass compilation, TypeScript transpilation, JavaScript bundling), run it in the pipeline and commit only the built files to the deployment artifact. WP Engine’s servers lack Node.js and build tools, so they cannot compile assets post-push.
Cache Invalidation and Post-Deploy Steps
WP Engine uses a sophisticated caching layer (EverCache) that serves cached HTML to anonymous visitors. After deployment, you must clear this cache so visitors see updated content. WP Engine automatically flushes object cache and page cache on Git push, but CDN cache (if you use their Global Edge Security or a third-party CDN) may require explicit purging.
Add a post-deploy step to your pipeline that calls WP Engine’s API or your CDN’s purge endpoint. For WP Engine’s built-in CDN, the cache clears automatically on deploy. For Cloudflare, add a step using their API:
- name: Purge Cloudflare cache
if: success()
env:
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
Run cache purging only after a successful deployment. If the deploy fails, the old code remains live and its cache stays valid.
Monitoring and Rollback Strategies
Automated deployments reduce human error but increase deployment frequency. You need visibility into deployment health. WP Engine’s dashboard shows deployment history with timestamps and commit hashes. Augment this with pipeline notifications: configure Slack, Microsoft Teams, or email alerts on deployment success and failure.
Rollbacks on WP Engine work by force-pushing a previous commit. Your pipeline can support this with a manual rollback workflow that accepts a commit SHA or tag as input. Alternatively, maintain a production-previous tag that updates on each successful deployment. A rollback workflow pushes that tag to the production remote.
Database rollbacks remain manual. WP Engine’s backup system creates daily snapshots and on-demand backups. Before deploying risky changes, trigger an on-demand backup through the dashboard or API. If a migration breaks production, restore the backup and roll back the code.
Common Pitfalls and How to Avoid Them
One frequent issue: file permission mismatches. WP Engine’s deployment process sets specific permissions on uploaded files. If your pipeline creates files with different ownership or modes, you may see 500 errors or missing assets. The rsync approach in the example workflow preserves permissions, but if you use a different packaging method, verify the resulting archive matches WP Engine’s expectations.
Another trap: environment-specific configuration. WP Engine recommends using environment variables for sensitive values (database credentials, API keys, salts). Define these in the WP Engine dashboard per environment. Your code reads them via getenv() or $_ENV. Never commit .env files or wp-config.php with hardcoded production credentials.
Large repositories slow down deployments. WP Engine has a soft limit on repository size and push time. If your repository includes years of history, consider a shallow clone in the pipeline (fetch-depth: 1 in GitHub Actions) and only push the current tree. The example workflow uses git init in the deploy step, which creates a fresh history with a single commit. This keeps pushes fast and avoids hitting size limits.
Advanced: Multi-Site and Multi-Environment Deployments
Agencies managing multiple client sites on WP Engine face additional complexity. Each site needs its own pipeline or a parameterized pipeline that accepts environment variables. GitHub Actions supports matrix builds and reusable workflows. Define a reusable deployment workflow that takes WPE_ENV, WPE_HOST, and SSH key secrets as inputs. Each client repository calls the reusable workflow with its specific values.
For WordPress multisite networks, the deployment process is similar but the stakes are higher. A bad deploy affects every site in the network. Implement stricter gates: require multiple approvals, run extended test suites, and deploy to a staging multisite first. WP Engine’s multisite support includes domain mapping, so your staging environment should mirror the production domain structure as closely as possible.
Security Considerations
CI/CD pipelines expand your attack surface. A compromised pipeline can deploy malicious code to production. Mitigate this by following least-privilege principles. The SSH key or deploy token used by the pipeline should only have push access to the specific WP Engine environment. Rotate keys quarterly. Enable two-factor authentication on all accounts with pipeline configuration access.
Scan dependencies for vulnerabilities as part of your pipeline. GitHub’s Dependabot, Snyk, or OWASP Dependency Check can run on schedule or per push. Fail the build on high-severity findings. This prevents deploying known-vulnerable packages even if tests pass.
WP Engine’s platform includes a web application firewall (WAF) and malware scanning. These operate independently of your deployment pipeline. Do not disable them to “speed up” deployments. The WAF inspects incoming traffic, not your deployment process.
Cost and Resource Planning
CI/CD minutes cost money on private repositories. GitHub Actions provides 2,000 free minutes per month for private repos on the Free plan. A typical WordPress deployment pipeline consumes 3-5 minutes per run. With multiple developers pushing several times daily, you can exceed the free tier quickly. Monitor usage in the GitHub Settings > Billing section. Consider self-hosted runners for heavy workloads; they run on your infrastructure and don’t count against cloud minutes.
WP Engine’s plans include a set number of environments. Each pipeline typically targets one staging and one production environment. If you need feature branch environments (ephemeral environments per pull request), you’ll need additional WP Engine environments or a different strategy. Some teams use a single staging environment and deploy feature branches there with unique subdomains, but WP Engine’s Git remotes map one-to-one with environments, making this awkward.
Frequently Asked Questions (FAQs)
Can I deploy to WP Engine without using their Git remotes?
Yes. WP Engine supports SFTP and SSH access, so you can use tools like rsync or Deployer.php from your pipeline. However, Git push deployment integrates with their cache clearing, health checks, and deployment logging automatically. The Git remote method is the recommended path for most teams.
Does WP Engine run Composer automatically on every push?
WP Engine runs composer install --no-dev --optimize-autoloader when they detect a composer.json file in the repository root. They do not run Composer for subdirectories. If your project structure places composer.json in a subfolder, you must handle dependency installation in your pipeline and commit the vendor directory (not recommended) or restructure your repository.
How do I handle environment-specific wp-config.php values?
Do not commit wp-config.php with environment-specific values. Instead, commit a wp-config.php that reads from environment variables using getenv(). Set those variables in the WP Engine dashboard under Environment Variables for each environment. This keeps secrets out of version control while allowing each environment its own configuration.
Can I use WP Engine’s Git deployment with a monorepo containing multiple WordPress sites?
WP Engine’s Git remotes expect a single WordPress installation at the repository root. Monorepos require a pipeline that extracts the relevant subdirectory for each site and pushes to separate WP Engine remotes. This adds complexity. Most teams find separate repositories per site simpler to manage.
What happens if a deployment fails halfway through?
WP Engine’s deployment process is atomic at the filesystem level. If the Git push succeeds but Composer fails, the previous code remains active. The dashboard shows the failed deployment with error logs. Your pipeline will also report failure. Fix the issue and push again. No manual rollback is needed because the live site never switched to the broken code.
How do I debug a deployment that succeeds in the pipeline but breaks the site?
Check WP Engine’s deployment logs in the dashboard first. They show Composer output, file counts, and any errors during the deploy hook. Enable WP_DEBUG in the environment variables temporarily to see PHP errors. Use the SSH gateway to run WP-CLI commands like wp option get active_plugins to verify plugin state. Compare the deployed commit hash in the dashboard with your expected commit.
Is it possible to deploy only a subset of files (e.g., just a theme) to WP Engine?
WP Engine’s Git deployment replaces the entire remote repository content with what you push. You cannot selectively update only a theme or plugin via Git push. If you need granular updates, use SFTP or a tool like Deployer.php that syncs specific directories. However, full-repository deployment is safer because it guarantees the production state matches the repository exactly.
How do I manage WordPress core updates in a CI/CD workflow?
WordPress core updates are managed outside your repository. WP Engine offers automated core updates (minor only, or minor and major) in the dashboard. Disable automatic updates if you want full control, then test core upgrades in staging before applying to production. Core updates do not go through your Git repository or CI/CD pipeline.