To quote my colleague, Chad, WordPress “remained tremendously popular since its release in 2003”. For many, WordPress remains by far the Content Management System (CMS) that is easiest to adopt, and that provides a fast time to market in the majority of use cases. There is so much high-quality material out there for WordPress, be it Open Source Software (OSS) or Premium, that one can have beautiful sites powered by an easy-to-use CMS up and running in no time.
There was a time in my career when building CMS solutions for enterprise was the main thing I did. In those circles the idea that WordPress was a toy not suitable for enterprise-grade projects was common. This was especially true when I was working with teams that had already been initiated to best practices that later became known as the 12 Factor app methodology. The traditional way of “doing WordPress” couldn’t really espouse the 12-Factor way easily.
Traditionally, a WordPress project’s codebase would consist of the entire WordPress core, plus all the plugins and themes necessary for the project to work. The entire codebase would then be deployed to a hosting of choice, usually via—cough— (S)FTP. The slightly evolved model is that the very same codebase is checked into a source code management system of choice, with a CI tool moving it onto the hosting’s servers.
Automatic Background Updates were introduced in WordPress 3.7 for the WordPress core, and later extended to plugins, themes, and translation files. Whilst a degree of automation for updates is definitely desirable, the way this is implemented in WordPress (i.e., in situ updates) means that if your codebase is in a repository, it’ll quickly get out of date, and you’ll have to manage updates in the repo on the side, to avoid problems and regressions down the road. If instead, you manage everything via FTP alone, it means each update will be very risky anyway. Either way, I can guarantee that things will quickly escalate into a nightmare.
It gets more complicated. To quote Chad again from the same article:
“Many hosting solutions, Upsun included, enforce read-only filesystems at runtime. These solutions deploy a highly reproducible build image as a consequence of your codebase and its explicitly defined build process, all committed to version control. [...] External code (dependencies) [...] and ideally definitions of your infrastructure itself are committed to exact versions so that your entire DevOps process from start to finish is repeatable and reproducible.”
This is excellent also for security reasons. Yet, as you may have gathered, it clashes completely with the way WordPress handles automatic updates, and these might need to be disabled entirely where a read-only file system is employed.
In his article, Chad talks about how Bedrock leverages composer
to provide a more modern approach to WordPress development, thanks also to the great work behind WordPress Packagist. Chad shows that combining these efforts with our very own Source Operations, it’s possible to achieve a good degree of automation in updates, with greater control on versions and a greater repeatability of the process.
The goal of this article is to go one step further, and show how to combine Upsun with other tools, to achieve the best WordPress workflow—including auto-updates—that allows also for a greater degree of complexity, control, and configuration.
To do so, I’ll start by following the Deploy Composer-based WordPress on Upsun guide from the Upsun documentation.
After following the guide, I modified my experiment to host the codebase for two sites (ita
and eng
), where both:
composer
, thanks to johnpbloch/wordpress
, WordPress Packagist (for plugins and themes), and inpsyde/wp-translation-downloader
(a composer
plugin to manage WordPress translations for core, plugins, and themes)In addition to the above:
ita
site uses Elasticsearcheng
site uses AlgoliaFinally:
If you are thinking this experiment was derived from a real-life project, then you’re right!
In the remainder of this article I’ll be focussing on the points that show how this experiment is all about automating as much of your Wordpress development workflow as possible, and I’ll avoid getting into some other details. Thus, if you want more insights into composer-based WordPress development, do refer back to Chad’s article or directly to the code in my repository.
Distros or install profiles are essentially a way to have your own default installation configuration. Whilst some software have built-in support for that, WordPress does not. My goal was to completely automate the installation on the first deployment, so I decided to take matters into my own hands.
The rudimentary support I implemented for this relies on two things. First, a bespoke section in the composer.json
file:
"distro": {
"default-theme": "lovecraft",
"enable-plugins": [
"academic-bloggers-toolkit",
"akismet",
"contextual-category-widget",
"elasticpress",
"jetpack",
"really-simple-ssl",
"redis-cache",
"series",
"social-pug",
"wp-cloudflare-page-cache",
"wpforms-lite"
]
},
Second, a script that uses the information in that section to perform some initial setup:
#!/usr/bin/env bash
cd wordpress
if ! wp core is-installed; then
WP_URL=$(echo $PLATFORM_ROUTES | base64 --decode | jq -r 'keys[]' | grep $PLATFORM_APPLICATION_NAME | grep https | grep www)
wp core install --url="${WP_URL}" --title="Modern WordPress" --admin_user=admin --admin_password=changeme --admin_email=change@me.com
DEFAULT_THEME=$( jq -r '.[ "distro" ][ "default-theme" ]' ../composer.json )
wp theme activate ${DEFAULT_THEME}
jq -r '.[ "distro" ][ "enable-plugins" ][]' ../composer.json |
while read PLUGIN; do
wp plugin activate ${PLUGIN}
done
else
wp core update-db
fi
The script is then executed as part of the deploy
hook in Upsun, i.e. after the build phase has downloaded and built WordPress and the other dependencies via composer
. The result will be a fully installed WordPress application, avoiding you from having to go through the default manual (albeit simple) WordPress setup wizard.
If you are wondering why we didn't simply use the scripts.postbuild
section in composer.json
to run such a script, the primary reason is that during the build
phase (when composer
is executed) the database service is not yet available, since the build phase is designed only to create a deployable artifact.
We’ll talk about code updates later, but since we have referenced the script above, I wanted to highlight that the same script takes care of updating the database in case there have been code updates that demand such operation to take place.
If you have worked with WordPress before, you know that at the very least, when WordPress core is upgraded, you might be required to run a very simple routine that upgrades the database schema accordingly. This can be done through the administrative UI, which will present you with a simple button to click; or it can be done via the wp-cli
. The latter is what this script is using (see how the wp-cli
is installed): if WordPress is already installed, on each deploy it will simply run a database update routine, which will only do something if there is something to do.
Automated dependency updates
Part of the GitHub family for quite some time now, Dependabot provides a free plan for public repositories, and it supports PHP+Composer. You can configure it to decide what kind of upgrades you want to perform on your dependencies, and the bot will issue pull requests periodically, avoiding stale dependencies. Our configuration looks like this:
version: 2
updates:
- package-ecosystem: composer
directory: "/eng"
schedule:
interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: wpackagist-plugin/really-simple-ssl
versions:
- 4.0.10
- package-ecosystem: composer
directory: "/ita"
schedule:
interval: daily
open-pull-requests-limit: 10
For both apps, we have a daily check on dependencies (remember, WordPress core itself is a dependency!), with a limit of maximum 10 open pull requests at any one time. You can also do clever things like ignoring specific versions of a dependency, if you know they are buggy, for example.
You might ask: ok, but how does opening PRs automatically actually achieve automatic dependency updates? Well, it does not :)
Opening loads of PRs in an automated fashion is hardly going to make your life easier and keep your dependencies up to date effortlessly. Thus, a tool like Dependabot must be combined with another one that provides merge queues and the ability to auto-merge pull requests that meet certain criteria. I used Mergify, but it is likely that in the near future you might be able to use GitHub merge queues if they decide to implement a system similar to Mergify, where you can define a pattern for the PRs that must be auto-merged:
pull_request_rules:
- name: automatic merge for Dependabot pull requests
conditions:
- author~=^dependabot(|-preview)\[bot\]$
- status-success=upsun
actions:
merge:
method: squash
The configuration above essentially instructs Mergify to automatically squash-merge all PRs issued by Dependabot and that have been built successfully on Upsun.
As I mentioned already, this experiment is meant to be used via GitHub integration with Upsun. This makes it possible for every PR raised by Dependabot to have an associated preview environment where the build and deployment of the application with the updated dependencies will take place. Thanks to the integration with GitHub, this process is added as a status check for the PRs on the repository. That status check is what Mergify uses here to make sure that everything is fine with the PR; if it is, the PR will be merged.
This, of course, is a very basic configuration; nothing is stopping us from using additional status-success
conditions in the configuration, which could be unit tests run by a CI and other things like this. The more automation and status checks you have, the more confident you can be that the PR can be automatically merged.
Combining tools like Dependabot, Mergify and Upsun gives you a much greater degree of control and assurance over your process of automated updates for WordPress core, plugins, themes, and translation files. This is because for each update you can be certain that a production-like environment will be built with the changes, and additional tests can be run to make sure that everything still works just fine. You can decide whether you want to auto-update just minor versions or also major versions. You can exclude specific versions or specific items (a particular plugin or theme, for instance). And much more. You have fine-grained control, more power, and a higher degree of assurance.
The WordPress development workflow I’ve shown you here can be applied to extended scenarios. For instance, you can configure Mergify to auto-merge other or even all types of PRs, given the right conditions (remember that even manual peer reviews and other manual steps can be added as status checks). You can also extract this workflow and apply it to other types of applications, not just WordPress.
However, one way one could really take this to another level is by automating infrastructure upgrades! All you’d need is a way to check if there’s a new release of one of the Upsun managed services you use in your project, and then issue a new PR with the update (just like Dependabot does with application dependencies). Once you have that component (which could be implemented within your external CI tool, such as GitHub Actions), then you could configure Mergify to auto-merge those PRs, again, given the right conditions.
Whilst you wait for the ability to auto-upgrade your infrastructure (in fact, I promise I’ll be updating my template with that very feature as soon as it becomes possible to do so), you can still start using this template and this workflow right now, and begin saving precious time to your development team:
And as usual, you know where to find us, if you need us!