Andrew Welch
Published , updated · 5 min read · RSS Feed
Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.
Updating Craft CMS Without Headaches
Updating Craft CMS to the latest version doesn’t have to cause headaches. Here’s our prescription for how to update Craft CMS without pain
Craft CMS has been regaled as a highly flexible, highly customizable Content Management System that is used when prefab “canned” solutions just won’t cut it.
One of the things that made Craft CMS stand out from day 1 is the built-in updating system that shipped with Craft CMS 1.0.0.
Using their home-brewed update system, you could click a button, everything would be updated, and you’d go on your merry way.
To maintain its edge, Craft CMS modernized the platform with the release of Craft CMS 3 in April 2018, and ditched its home-brew updating system in favor of using the PHP package manager Composer.
Since version 3.0, Craft CMS is just a PHP Package that you install via Composer, and all of the plugins for Craft CMS are just PHP packages, too.
As with any change, there are adjustments that need to be made
While moving to Composer was 100% the correct choice, it has added a few wrinkles to updating Craft CMS.
But never fear, this article will provide you with a prescription for updating Craft CMS without headaches.
As always, the canonical source for updating Craft CMS can be found in the official documentation under Updating Instructions… but we have a few pointers we can add.
So lets have a look at some maxims to live by when updating Craft CMS:
Link 1. Update Often
The frequency at which you update Craft CMS is one of the most important things you can control.
You should update often, because more frequent updates means you’re updating fewer things each time, which means the web of interdependencies is smaller.
This also means that if something goes wrong during an update, it’s a small thing that can more easily be rolled back.
One of the worst situations you can get into is “dependency hell” where you have so many different parts that need updating that it becomes nigh impossible to untangle what went wrong.
Updating frequently reduces the code surface involved in any change, which reduces the potential for problems
How often? Pixel & Tonic tends to release updates to Craft CMS every week, usually on Tuesdays.
I think updating once per month is a reasonable frequency for updating the CMS, and any plugins the site may be using.
Update Craft CMS & plugins once per month
This effectively requires that you have a maintenance contract with your clients, but an ounce of prevention is worth a pound of cure.
Shipping a highly custom website built with Craft CMS without some kind of maintenance agreement is tantamount to buying a brand new car, and leaving it outside to rust.
You can’t maintain without maintenance.
Incidentally, this “update frequently” mantra applies to the VPS where Craft CMS is hosted, too. More on that later on.
For the security benefits of updating often, check out Ben Croker’s excellent Securing Your Craft Site in 2021 — Part 1 article.
Link 2. Update Locally
When you update Craft CMS, the updates should be done in a local development environment. This allows you to:
- Install the updates in a safe, local environment that only you access
- Test the updates to ensure they function as expected
- Deploy the exact updates you tested to production for the world to see
While installing updates directly in production is convenient, it can also be fraught with peril. If anything goes wrong, you’re left with a mess that far outweighs any convenience.
To this end, we recommend turning the allowAdminChanges setting OFF in staging and production environments. This ensures that no one makes changes in production that should be done locally.
For the actual deployment from local development to production, there are many ways to do it. Check out the Atomic Deployments Without Tears article for one.
There are three primary things that need to be performed when doing an update locally:
- The updates to your Composer packages (which includes Craft CMS itself) are done locally either via composer update or php craft update, and are stored in the composer.lock file, which you then deploy and run composer install to install the versions in the composer.lock file in each environment.
- Migrations that need to be run on the database are done in each environment, via php craft migrate/all
- Schema changes are stored in Project Config, and applied in each environment via php craft project-config/apply
More on the nuts & bolts of how to do this below.
If something goes wrong during an update, check out the Troubleshooting Failed Updates knowledge base article. And be glad you did the update locally, where the failure affected no one.
Link 3. Update via CLI
We recommend that you do not update anything in Craft CMS via the GUI, but rather use the Command Line Interface (CLI) to do it.
I know, I know. The little burnt umber Update all button is incredibly seductive. But there are very good reasons to eschew it.
Frontend web requests are meant to be short-lived. Someone requests a web page, and the server returns it to them. A clean, simple transaction.
For this reason, the PHP processes that handle web requests are limited in terms of the amount of time they can run, and the amount of memory they can consume.
If web processes weren’t restricted, it’d be trivial to bring any website to its knees with a DDoS attack
This makes web requests extraordinarily bad for doing something complex, memory intensive, and CPU intensive like updating software packages via Composer.
We don’t want our update to fail halfway through, so we can instead run the update process via CLI, where there are typically no limits on the amount of memory a PHP process can use (memory_limit) or the amount of time it can take (max_execution_time).
PHP code that is run via the console command line interface (CLI) uses a separate php.ini file than the web-oriented php-fpm. Here’s an example from my server:
/etc/php/7.1/fpm/php.ini
/etc/php/7.1/cli/php.ini
The reason this is important is that while the web-based php-fpm might have reasonable settings for memory_limit and max_execution_time, the CLI-based PHP will typically have no memory_limit and no max_execution_time.
You’ll want them to look like this in your CLI php.ini, so there’s no limit to the amount of memory or CPU PHP can consume when run via the (trusted) CLI:
; Maximum amount of memory a script may consume (128MB)
; http://php.net/memory-limit
memory_limit = -1
; Maximum execution time of each script, in seconds
; http://php.net/max-execution-time
; Note: This directive is hardcoded to 0 for the CLI SAPI
max_execution_time = 0
This is because web processes are externally triggered and therefore untrusted, whereas things running via the CLI is internal and trusted.
Indeed, Pixel & Tonic’s Brandon Kelly stated “We will most likely disable CP updating in 4.0” for the reasons described above.
For more information on the Craft CLI, check out the Exploring the Craft CMS 3 Console Command Line Interface (CLI) article.
Link 4. Embrace Composer
So okay, we should be installing our updates via the CLI. But how should we do them exactly?
There are two ways:
- Using the PHP package manager composer update
- Using Craft’s built in CLI command php craft update
Which should you use? We think learning Composer pays dividends down the road, and will result in fewer headaches than using the Craft CLI. However, you can use either.
Choose composer if you are comfortable with using the Composer CLI, and you have Composer installed on your server or CI setup.
Choose php craft update if you’d prefer to use the built-in Craft CMS updater (which ultimately uses Composer anyway).
Just pick one, and stick with it.
You don’t want to mix and match composer and php craft update
The reason you don’t want to mix and match them is the way the version numbers get written out to the composer.json file if you use php craft update.
Craft CMS’s update command will write out the exact version of Craft CMS or a plugin that it’s installed, so you might have a composer.json that looks something like this:
{
"require": {
"craftcms/cms": "3.5.15",
"vlucas/phpdotenv": "^3.4.0",
"yiisoft/yii2-redis": "^2.0.6",
"nystudio107/craft-imageoptimize": "1.0.0",
"nystudio107/craft-retour": "3.1.24",
"nystudio107/craft-seomatic": "3.2.0",
"nystudio107/craft-twigpack": "1.1.0"
},
"require-dev": {
"yiisoft/yii2-shell": "^2.0.3"
},
}
The problem is that Composer relies on something called Semantic Versioning (semver for short). It’s expecting version numbers to be prefixed with a ^ (minor updates) or ~ (patch updates) to indicate how a package should be updated.
Version numbers that have no prefix are assumed to mean that exact version. That means that in the example above, nothing other than exactly craftcms/cms version 3.5.15 will ever be installed.
That means it’s locked at that version, and will never be updated beyond that… which usually isn’t desirable.
If you want to learn more about semver, check out the Packagist Semver Checker where you can enter different packages and semvers and see what they match.
Typically, though, you’ll want to prefix each version with a ^ which indicates that you want all patch & minor updates. Here’s a quick rundown on semver:
1.0.0 = exactly & only this version
~1.0.0 = this version & any _patch_ release (up to 1.0.99̅)
^1.0.0 = this version & any _minor_ release (up to 1.99̅.99̅)
But which should you use?
Using Craft CMS’s built-in php craft update command is convenient, because it doesn’t require that Composer is installed, it will backup the database for you automatically before updating, and it will automatically run any pending migrations.
The downside is that Craft itself (since it’s just a PHP package) must be installed along with the Composer PHP package before it’ll work. So at some point, somewhere, somehow, Composer will need to be used via the CLI anyway.
Another downside is that the php craft update command is an additional layer on top of Composer, so more things can go wrong and may require that you use Composer directly anyway.
For these reasons, and because learning Composer is a skill that translates to other ecosystems, we recommend you learn a few Composer commands and use it directly rather than using the Craft layer.
We recommend you learn a little Composer, and use it directly
Check out the Composer 2.0 section below to see how you can automatically run migrations, apply Project Config, and other needed tasks when doing an update.
For a primer on using Composer, have a look at the Composer for the Rest of Us article.
If you run into issues with Composer, check out the Troubleshooting Composer Errors knowledge base article.
Link Composer 2.0
As of this writing, Composer 2.0 was recently released. The good news is, Composer 2.0 is significantly faster than Composer 1.x, and additionally the composer.lock file that it writes out is backward and forward compatible.
This is awesome!
The only bad news is that the craftcms/plugin-installer package needs to be updated to version 1.5.6 or later. This package enables the installation of Craft plugins (which are just Composer packages of the "type": "craft-plugin").
This is a package that Craft CMS itself requires, so the only rub is that if it’s already installed, you may end up seeing errors like this:
- Upgrading craftcms/element-api (2.6.0 33da444 => 2.6.0 ): Extracting archive
Update of craftcms/element-api failed
Failed to extract craftcms/element-api: (9) unzip -qq '/data/projects/<project>/vendor/composer/tmp-5a77fb2e0c9645eb9f66e2e0bd883fce' -d '/data/projects/<project>/vendor/composer/dcb9d0dd'
unzip: cannot find or open /data/projects/<project>/vendor/composer/tmp-5a77fb2e0c9645eb9f66e2e0bd883fce.
This most likely is due to a custom installer plugin not handling the returned Promise from the downloader
See https://github.com/composer/installers/commit/5006d0c28730ade233a8f42ec31ac68fb1c5c9bb for an example fix
This can be easily fixed by deleting the vendor/craftcms/plugin-installer/ package, and updating Craft again, or you can just use the handy “Composer Nuke” command from the project’s root:
rm -rf vendor/ && rm composer.lock && composer clear-cache && composer update
Then if you do a composer update, it’ll download the latest Craft CMS & Plugin Installer packages, and away you go!
You might also run into some older plugins that Composer 2.0 complains about PSR‑4 namespace errors when installing.
Fortunately, this should be an easy fix (it’s just the namespace casing not being right), and Pixel & Tonic’s Brandon Kelly filed a Fix Composer 2 compatibility issue for every single Craft CMS plugin that had an issue.
Composer 2.0 offers some fantastic performance gains, and it’s going to be required as of Craft CMS 3.6 anyway so… might as well get on board now.
Get on board with Composer 2.0 now!
If want to see the current installed and available versions of your plugins/packages, composer show to the rescue:
composer show -oD
-o = outdated
-D = direct dependencies only (in your composer.json)
…and you’ll see something like this:
If you use the composer CLI as I do for updates, a nice hint is you can leverage Composer Scripts to ensure that any time a composer update or composer install is done, migrations are also run, caches are cleared, etc.
Just add something like this to your composer.json:
"scripts": {
"craft-update": [
"@pre-craft-update",
"@post-craft-update"
],
"pre-craft-update": [
],
"post-craft-update": [
"Composer\\Config::disableProcessTimeout",
"@php craft install/check && php craft clear-caches/all --interactive=0 || exit 0",
"@php craft install/check && php craft migrate/all --interactive=0 || exit 0",
"@php craft install/check && php craft project-config/apply --interactive=0 || exit 0"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php craft setup/welcome"
],
"pre-update-cmd": "@pre-craft-update",
"pre-install-cmd": "@pre-craft-update",
"post-update-cmd": "@post-craft-update",
"post-install-cmd": "@post-craft-update"
}
So any time a composer update or composer install is run, the scripts listed in pre-craft-update will be run. Currently there are none, but it’s there for future expansion in case you want to add something like "@php craft backup/db" to backup the database before each update or install.
Then the actual composer update or composer install executes, and afterwards the scripts in post-craft-update are run, which:
- php craft clear-caches/all — clears all caches
- php craft migrate/all — runs all migrations
- php craft project-config/apply — applies any changes from Project Config
The --interactive=0 is a flag to force the commands to run in non-interactive mode (ie, they won’t wait for you to answers prompts). This should only affect things when the shell is interactive to begin with (which shouldn’t be the case for CI commands), but best to be explicit about it.
If you’re wondering what the @php craft install/check is here, it’s a way for the commands to fail gracefully if Craft CMS isn’t installed yet.
This ensures that no matter what environment we’re in, the appropriate migrations, etc. are run to keep the project in sync.
You can add your own commands here too, if you like, to do things like backup the database, for example.
If as part of a Continuous Integration (CI) pipeline you need to do a composer install but you don’t want to run the scripts (perhaps there’s no database available), you can use the --no-scripts flag.
Also when installing on production, you often want to use the --no-dev flag with composer install so that you do not install anything in require-dev: (which are packages for local development only).
Link Update your VPS
If you’re managing your own VPS for your client projects as discussed in the How Agencies & Freelancers Should Do Web Hosting article, you should update the VPS frequently, too.
If you’re using managed hosting like servd.host, ArcusTech, or fortrabbit, they take care of the updates and such for you, so rest easy.
While provisioners like Forge and ServerPilot will install security updates for you, you will need to install any other updates… and in either case, you’ll need to reboot the server for the updates to take effect.
If you’re running an Ubuntu-based VPS (as both Forge & ServerPilot do), here’s an “update everything” command you can use:
sudo -- sh -c 'apt update && apt upgrade -y && apt autoremove -y && apt autoclean -y'
And then just reboot the machine via:
sudo reboot
You might see something like this when you ssh into your VPS:
149 packages can be updated.
0 updates are security updates.
*** System restart required ***
Last login: Thu Nov 26 17:44:52 2020 from xxx.xxx.xxx.xxx
The 149 packages can be updated are non-security, non-critical updates that can be updated via the “update everything” command mentioned above.
The *** System restart required *** message means that security or critical updates have been automatically installed, but they won’t be in effect until the server is rebooted.
I’ve seen situations where people are afraid to update or reboot their server because they are scared it won’t come back up.
As discussed in the Mitigating Disaster via Website Backups article, servers should be disposable.
Update without fear
We shouldn’t fear updates. The worst time to be installing updates is after you’ve neglected server updates for years, and a major security vulnerability has been disclosed.
For the same reasons mentioned before, we want to update the server often so that the number of things that have changed are small.
And we want to be confident about doing it.
Update & reboot your VPSs about once a month, too.
So update the server about once a month, too, and reboot it each time.
Finally, resist the urge to create your own little shared hosting setups on your VPS. You really want to have one VPS per client project.
If you want to put staging & production on the same VPS, that’s usually fine, but more than that can be counter-productive.
In addition to recreating everything that’s bad about shared hosting yourself, you also will make it difficult to update the VPS, because you’re adding to the dependency graph with each additional site.
I’ve seen it many times where a VPS doesn’t get updated, because of one old site on it that holds the rest back.
You can create small VPS instances easily, so take advantage of that and do so!
Link Updating to Craft CMS 3.6
A new wrinkle has been introduced when updating to Craft CMS 3.6, because it requires PHP version 7.2.5 or later.
Historically, Craft CMS has worked on PHP 7.0.0 or later, so this is new. And even if you have PHP 7.2.5 or later installed, you may run into this error if you do composer update:
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires craftcms/cms 3.6.0 -> satisfiable by craftcms/cms[3.6.0].
- craftcms/cms 3.6.0requires php >=7.2.5 -> your php version (7.0; overridden via config.platform, actual: 7.4.10) does not satisfy that requirement.
Of if you use the ./craft update command you’ll see something like this:
It’s also possible that just nothing will happen if you do composer update, it just will never update you to Craft CMS 3.6 or later.
This happens if you just have a semver like ^3.0.0 — Composer will determine that you can’t update to Craft CMS 3.6, and nothing happens.
So what’s going on here? Well, in your composer.json for your Craft CMS project, you’ll probably see something like this:
"config": {
"sort-packages": true,
"optimize-autoloader": true,
"platform": {
"php": "7.0"
}
},
The platform specified here ensures that no package will be installed that requires a version of PHP that is greater than 7.0… and since Craft CMS 3.6 needs PHP 7.2.5 or later, Composer throws its hands up and says “you shall not pass!”
So you have two choices here. The first is to edit your composer.json to raise the platform requirements:
"config": {
"sort-packages": true,
"optimize-autoloader": true,
"platform": {
"php": "7.2.5"
}
},
And then do you composer update.
And second, while you could tell Composer that you don’t care about the PHP platform requirement via the --ignore-platform-req=php flag, you’d need to do that both when you do composer update locally and as part of your CI system via composer install deployment script.
So probably the best thing to do is just edit the platform in your composer.json as described above.
You should then be good to go. You can find more in the Resolving PHP Requirement Conflicts knowledge base article.
One other small 3.6 wrinkle is that some people are reporting that the php craft commands are now running interactively, i.e., asking for user input.
This is obviously bad if they are part of an automated CI process, so we can mitigate this by adding --interactive=0 to our commands.
Even if this does end up getting fixed in Craft core, I think it’s best to be explicit about commands that are intended to run in an automated, non-interactive CI process by adding --interactive=0 to them.
Link Updated
Follow some of the tips here, and I think you’ll find that updating isn’t so scary, and neither is it much hassle either.
Then sit back and enjoy the rest of your day. You won’t even need to use that coffee to wash down any pain killers.
Happy updating!