Recently we went over how to do branched deploys to WP Engine with GitHub Actions. Today, I would like to dive a bit deeper into managing plugin dependencies with Composer when deploying to WP Engine.
Table of contents
We’ll assume you have some basic familiarity with Composer and WPackagist, but if you don’t, here is an excellent resource to get you started: Managing your WordPress site with Git and Composer.
Composer deployment demo source code
Check out the full source code for the final demo project on GitHub.
Here is what we’ll be covering:
- Version control (Git) – you will only need the
wp-content/
directory to be under version control. We’ll let WP Engine maintain WordPress core automatic updates. - Composer – you will use Composer and WPackagist.org to manage WordPress plugins, including:
- Advanced Custom Fields PRO – we’ll show you how to pull in ACF PRO with Composer using the
ffraenz/private-composer-installed
package.
- Advanced Custom Fields PRO – we’ll show you how to pull in ACF PRO with Composer using the
Overview of project organization
Here is an overview of how the site is currently organized in our WordPress install’s wp-content/
.
Current WordPress wp-content/ | Available on WPackagist? | .gitignore ? |
---|---|---|
wp-content/plugins/advanced-custom-fields-pro | No, grab from URL | Yes |
wp-content/plugins/custom-plugin | No (custom plugin) | No |
wp-content/plugins/duplicate-post/ | Yes | Yes |
wp-content/plugins/svg-support | Yes | Yes |
wp-content/plugins/wordpress-seo | Yes | Yes |
wp-content/themes/custom-theme | No (custom theme) | No |
We’ll reference the table above to create our .gitignore
file. Note: we’re not putting other standard WordPress directories within the wp-content/
directory under version control, e.g. wp-content/uploads
, wp-content/upgrade
, wp-content/mu-plugins
, because we want to keep things lean.
.gitignore
(full source)
#---------------------------
# WordPress general
#---------------------------
/index.php
#---------------------------
# WordPress themes
#---------------------------
/themes/*
!/themes/custom-theme
#---------------------------
# WordPress plugins
#---------------------------
/plugins/*
!/plugins/custom-plugin
#---------------------------
# WP MU plugins: these are
# managed by the platform.
#---------------------------
/mu-plugins/
#---------------------------
# WP uploads directory
#---------------------------
/uploads/
#---------------------------
# WP upgrade files
#---------------------------
/upgrade/
#---------------------------
# Composer
#---------------------------
/vendor
.env
.env.*
!.env.example
Code language: plaintext (plaintext)
In the snippet above, we’ve added the items we’ll want to ignore that are associated with our Composer setup (last few lines). Let’s dig in on that next.
Set up Composer
First, we’ll want to pull in the pragmatic composer/installers
package. We’ll use this Composer package to set our custom paths where we want our WordPress plugins installed by Composer.
composer.json
with composer/installers
package
{
"name": "wpe/demo-project",
"type": "project",
"require": {
"composer/installers": "~1.0"
},
"extra": {
"installer-paths": {
"plugins/{$name}": [
"type:wordpress-plugin"
]
}
}
}
Code language: JSON / JSON with Comments (json)
In the code snippet above, we require composer/installers
, set installer-paths
for any Composer dependencies with the "type:wordpress-plugin"
to be installed in the plugins/
directory, and we pass the {$name}
of the plugin. We won’t see much progress if we run composer install at this point, but composer/installers
should be installed.
WPackagist set up in Composer
Next, we’ll want to set up our WPackagist integration to pull in our WordPress project’s plugins.
composer.json
with WPackagist plugins required
{
"name": "wpe/demo-project",
"type": "project",
"repositories": [
{
"type": "composer",
"url": "https://wpackagist.org"
}
],
"require": {
"composer/installers": "~1.0",
"wpackagist-plugin/duplicate-post": "^4.5",
"wpackagist-plugin/svg-support": "^2.5.1",
"wpackagist-plugin/wordpress-seo": "^19.10"
},
"extra": {
"installer-paths": {
"plugins/{$name}": [
"type:wordpress-plugin"
]
}
}
}
Code language: JSON / JSON with Comments (json)
To pull in plugins from WPackagist, we have to tell Composer to reference WPackagist as a package source, which is accomplished with the repositories
key. Then all we have to do is require all of our WPackagist plugins.
ACF PRO set up in Composer
February 2023 update
ACF has recently introduced native support for Composer integration. We highly recommend you review and utilize their recent changes.
Check out Installing ACF PRO with Composer for the latest best practices.
ACF PRO requires a license key, but that is no sweat, thanks to Fränz Friederes’ private-composer-installer
Composer package. This package allows us to reference a URL and pass along a variable for our secret license key. The emphasis here is secret. We don’t want to store our license key anywhere that is publicly available. We’ll keep our license key in a .env
file. The .env
file is a standard way to store environmental variables, which should exist outside the code.
composer.json
with newly added ACF PRO integration
{
"name": "wpe/demo-project",
"type": "project",
"repositories": [
{
"type": "package",
"package": {
"name": "advanced-custom-fields/advanced-custom-fields-pro",
"version": "6.0.5",
"type": "wordpress-plugin",
"dist": {
"type": "zip",
"url": "https://connect.advancedcustomfields.com/index.php?a=download&p=pro&k={%PLUGIN_ACF_KEY}&t={%VERSION}"
}
}
},
{
"type": "composer",
"url": "https://wpackagist.org"
}
],
"require": {
"composer/installers": "~1.0",
"ffraenz/private-composer-installer": "^5.0",
"wpackagist-plugin/duplicate-post": "^4.5",
"wpackagist-plugin/svg-support": "^2.5.1",
"wpackagist-plugin/wordpress-seo": "^19.10",
"advanced-custom-fields/advanced-custom-fields-pro": "*"
},
"extra": {
"installer-paths": {
"plugins/{$name}": [
"type:wordpress-plugin"
]
},
"private-composer-installer": {
"dotenv-path": ".",
"dotenv-name": ".env"
}
}
}
Code language: JSON / JSON with Comments (json)
As you can see, we’ve added the ACF PRO download URL as a reference point in our repositories
key. We’ve also added the ffraenz/private-composer-installer
and advanced-custom-fields/advanced-custom-fields-pro
as required packages. Last, we tell the private-composer-installer
where we want our .env
file to reside in relation to our composer.json
file within our project.
You can check out the full demo project’s composer.json
here.
Create .env
file with ACF PRO key
All that is left to do is create our .env
file and place our ACF PRO license key.
An example .env
file with ACF PRO license key secret
PLUGIN_ACF_KEY=replacewithyourkey
Code language: plaintext (plaintext)
With all of our Composer dependencies established, we should be able to run composer install
and have everything pulled into our project.
Set up deployment actions
Now that we have all our Composer dependencies set up. We’ll integrate our deployment actions. This will allow us to deploy any custom updates to our custom plugin (wp-content/plugins/custom-plugin
) and theme (wp-content/themes/custom-theme
) alongside our latest WPackagist or ACF Pro updates.
We’ll be using WP Engine’s official Deploy WordPress to WP Engine GitHub Action for this part. We’ve already covered all the steps to set this workflow up and highly recommend you have your SSH key established on WP Engine and within your GitHub repo.
Establish include and exclude rsync options
The key difference for this new set up is that we want to exclude some files and folders from the rsync process. Below is our final YAML workflow file.
.github/workflows/deploy-production.yml
name: Deploy to WP Engine production environment
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
- name: GitHub Action Deploy to WP Engine
uses: wpengine/[email protected]
with:
# Deploy vars
WPE_SSHG_KEY_PRIVATE: ${{ secrets.WPE_SSHG_KEY_PRIVATE }}
WPE_ENV: qorp
# Deploy Options
REMOTE_PATH: "wp-content/"
FLAGS: -azvr --inplace --delete --include-from config/include.txt --exclude=".*" --exclude-from config/exclude.txt
PHP_LINT: TRUE
SCRIPT: wp-content/config/post-deploy.sh
Code language: YAML (yaml)
The notable differences for this set up are highlighted above (the last four lines).
Name | Type | Usage |
---|---|---|
REMOTE_PATH | string | Optional path to specify a directory destination to deploy to. Ex. "wp-content/themes/custom-theme/" . Defaults to WordPress root directory on WP Engine. |
FLAGS | string | Set optional rsync flags such as --delete or --exclude-from . The example is excluding paths specified in a .deployignore file in the root of the repo. This action defaults to a non-destructive deploy using the flags in the example above.Caution: Setting custom rsync flags replaces the default flags provided by this action. Consider also adding the -azvr flags as needed.-a preserves symbolic links, timestamps, user permissions and ownership.-z is for compression-v is for verbose output-r is for recursive directory scanning |
PHP_LINT | boolean | Set to TRUE to execute a PHP lint on your branch pre-deployment. Default is FALSE . |
SCRIPT | string | Remote bash file to execute post-deploy. This can include WP_CLI commands for example. Path is relative to the WP root and file executes on remote. This file can be included in your repo, or be a persistent file that lives on your server. |
You’ll notice that we’re passing the FLAGS
parameter, and it is referencing a config/include.txt
file and a config/exclude.txt
file. These are key to telling our workflow what files and folders we do and don’t want to deploy.
Let’s create these:
# Including plugins/themes that we check into git so that the version in github is deployed
/plugins/custom-plugin
/themes/custom-theme
Code language: plaintext (plaintext)
# Excluding these items from being deleted each sync
plugins/*
themes/*
mu-plugins/
uploads/
blogs.dir/
upgrade/*
backup-db/*
advanced-cache.php
wp-cache-config.php
cache/*
cache/supercache/*
index.php
mysql.sql
.env
.env.*
vendor
Code language: plaintext (plaintext)
Create post-deploy script
After our GitHub Action performs all the syncing we have the option to run a script. This is how we’ll handle running Composer on the final production server. This way we’ll keep our repository small and only version control what we need to and let Composer run on the final server.
cd wp-content && composer install --optimize-autoloader --no-dev --no-progress
Code language: Bash (bash)
Bonus: PHP linting
The WP Engine GitHub action allows us to lint our code with PHP_LINT: TRUE
and checks for syntax errors. It will not test for fatal errors, which is essential to note.
Conclusion
Utilizing Composer with WPackagist to manage your WordPress plugin dependencies can help keep teams organized and facilitate consistent workflows.
Also, keep in mind that there are many different ways to organize and deploy code. Let us know how you’re maintaining your ideal workflow—tag me on Twitter @dcook.