Andrew Welch · Insights · #craftcms #updating #frontend

Published , updated · 5 min read ·


Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.

Updating Craft CMS Without Headaches

Updat­ing Craft CMS to the lat­est ver­sion does­n’t have to cause headaches. Here’s our pre­scrip­tion for how to update Craft CMS with­out pain

Prescription for updating craft cms

Craft CMS has been regaled as a high­ly flex­i­ble, high­ly cus­tomiz­able Con­tent Man­age­ment Sys­tem that is used when pre­fab canned” solu­tions just won’t cut it.

One of the things that made Craft CMS stand out from day 1 is the built-in updat­ing sys­tem that shipped with Craft CMS 1.0.0.

Using their home-brewed update sys­tem, you could click a but­ton, every­thing would be updat­ed, and you’d go on your mer­ry way.

Original install craft cms

To main­tain its edge, Craft CMS mod­ern­ized the plat­form with the release of Craft CMS 3 in April 2018, and ditched its home-brew updat­ing sys­tem in favor of using the PHP pack­age man­ag­er Com­pos­er.

Since ver­sion 3.0, Craft CMS is just a PHP Pack­age that you install via Com­pos­er, and all of the plu­g­ins for Craft CMS are just PHP pack­ages, too.

As with any change, there are adjustments that need to be made

While mov­ing to Com­pos­er was 100% the cor­rect choice, it has added a few wrin­kles to updat­ing Craft CMS.

But nev­er fear, this arti­cle will pro­vide you with a pre­scrip­tion for updat­ing Craft CMS with­out headaches.

As always, the canon­i­cal source for updat­ing Craft CMS can be found in the offi­cial doc­u­men­ta­tion under Updat­ing Instruc­tions… but we have a few point­ers we can add.

So lets have a look at some max­ims to live by when updat­ing Craft CMS:

Link 1. Update Often

The fre­quen­cy at which you update Craft CMS is one of the most impor­tant things you can control.

You should update often, because more fre­quent updates means you’re updat­ing few­er things each time, which means the web of inter­de­pen­den­cies is smaller.

Dependency graph

This also means that if some­thing goes wrong dur­ing an update, it’s a small thing that can more eas­i­ly be rolled back.

One of the worst sit­u­a­tions you can get into is depen­den­cy hell” where you have so many dif­fer­ent parts that need updat­ing that it becomes nigh impos­si­ble to untan­gle what went wrong.

Updating frequently reduces the code surface involved in any change, which reduces the potential for problems

How often? Pix­el & Ton­ic tends to release updates to Craft CMS every week, usu­al­ly on Tuesdays.

I think updat­ing once per month is a rea­son­able fre­quen­cy for updat­ing the CMS, and any plu­g­ins the site may be using.

Update Craft CMS & plugins once per month

This effec­tive­ly requires that you have a main­te­nance con­tract with your clients, but an ounce of pre­ven­tion is worth a pound of cure.

Ship­ping a high­ly cus­tom web­site built with Craft CMS with­out some kind of main­te­nance agree­ment is tan­ta­mount to buy­ing a brand new car, and leav­ing it out­side to rust.

You can’t main­tain with­out maintenance.

Inci­den­tal­ly, this update fre­quent­ly” mantra applies to the VPS where Craft CMS is host­ed, too. More on that lat­er on.

For the secu­ri­ty ben­e­fits of updat­ing often, check out Ben Cro­ker’s excel­lent Secur­ing 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 devel­op­ment envi­ron­ment. This allows you to:

  1. Install the updates in a safe, local envi­ron­ment that only you access
  2. Test the updates to ensure they func­tion as expected
  3. Deploy the exact updates you test­ed to pro­duc­tion for the world to see
Testing code local dev

While installing updates direct­ly in pro­duc­tion is con­ve­nient, it can also be fraught with per­il. If any­thing goes wrong, you’re left with a mess that far out­weighs any convenience.

To this end, we rec­om­mend turn­ing the allowAd­min­Changes set­ting OFF in stag­ing and pro­duc­tion envi­ron­ments. This ensures that no one makes changes in pro­duc­tion that should be done locally.

For the actu­al deploy­ment from local devel­op­ment to pro­duc­tion, there are many ways to do it. Check out the Atom­ic Deploy­ments With­out Tears arti­cle for one.

There are three pri­ma­ry things that need to be per­formed when doing an update locally:

  1. The updates to your Com­pos­er pack­ages (which includes Craft CMS itself) are done local­ly 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 ver­sions in the composer.lock file in each environment.
  2. Migra­tions that need to be run on the data­base are done in each envi­ron­ment, via php craft migrate/all
  3. Schema changes are stored in Project Con­fig, and applied in each envi­ron­ment via php craft project-config/apply

More on the nuts & bolts of how to do this below.

If some­thing goes wrong dur­ing an update, check out the Trou­bleshoot­ing Failed Updates knowl­edge base arti­cle. And be glad you did the update local­ly, where the fail­ure affect­ed no one.

Link 3. Update via CLI

We rec­om­mend that you do not update any­thing in Craft CMS via the GUI, but rather use the Com­mand Line Inter­face (CLI) to do it.

I know, I know. The lit­tle burnt umber Update all but­ton is incred­i­bly seduc­tive. But there are very good rea­sons to eschew it.

Craft cms update all

Fron­tend web requests are meant to be short-lived. Some­one requests a web page, and the serv­er returns it to them. A clean, sim­ple transaction.

For this rea­son, the PHP process­es that han­dle web requests are lim­it­ed in terms of the amount of time they can run, and the amount of mem­o­ry 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 extra­or­di­nar­i­ly bad for doing some­thing com­plex, mem­o­ry inten­sive, and CPU inten­sive like updat­ing soft­ware pack­ages 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 typ­i­cal­ly no lim­its on the amount of mem­o­ry 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 con­sole com­mand line inter­face (CLI) uses a sep­a­rate php.ini file than the web-ori­ent­ed php-fpm. Here’s an exam­ple from my server:

/etc/php/7.1/fpm/php.ini
/etc/php/7.1/cli/php.ini

The rea­son this is impor­tant is that while the web-based php-fpm might have rea­son­able set­tings for memory_​limit and max_​execution_​time, the CLI-based PHP will typ­i­cal­ly 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 lim­it to the amount of mem­o­ry or CPU PHP can con­sume when run via the (trust­ed) 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 process­es are exter­nal­ly trig­gered and there­fore untrust­ed, where­as things run­ning via the CLI is inter­nal and trusted.

Indeed, Pix­el & Ton­ic’s Bran­don Kel­ly stat­ed We will most like­ly dis­able CP updat­ing in 4.0 for the rea­sons described above.

For more infor­ma­tion on the Craft CLI, check out the Explor­ing the Craft CMS 3 Con­sole Com­mand Line Inter­face (CLI) article.

Link 4. Update Consistently

So okay, we should be installing our updates via the CLI. But how should we do them exactly?

There are two ways:

  1. Using the PHP pack­age man­ag­er composer update
  2. Using Craft’s built in CLI com­mand php craft update
Command line interface

Which should you use? Either is total­ly fine!

Choose composer if you are com­fort­able with using the Com­pos­er CLI, and you have Com­pos­er installed on your serv­er or CI setup.

Choose php craft update if you’d pre­fer to use the built-in Craft CMS updater (which ulti­mate­ly uses Com­pos­er anyway).

Just pick one, and stick with it.

You don’t want to mix and match composer and php craft update

The rea­son you don’t want to mix and match them is the way the ver­sion num­bers get writ­ten out to the composer.json file if you use php craft update.

Craft CMS’s update com­mand will write out the exact ver­sion of Craft CMS or a plu­g­in that it’s installed, so you might have a composer.json that looks some­thing 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 prob­lem is that Com­pos­er relies on some­thing called Seman­tic Ver­sion­ing (semver for short). It’s expect­ing ver­sion num­bers to be pre­fixed with a ^ (minor updates) or ~ (patch updates) to indi­cate how a pack­age should be updated.

Ver­sion num­bers that have no pre­fix are assumed to mean that exact ver­sion. That means that in the exam­ple above, noth­ing oth­er than exact­ly craftcms/cms ver­sion 3.5.15 will ever be installed.

That means it’s locked at that ver­sion, and will nev­er be updat­ed beyond that… which usu­al­ly isn’t desirable.

If you want to learn more about semver, check out the Pack­ag­ist Semver Check­er where you can enter dif­fer­ent pack­ages and semvers and see what they match.

Typ­i­cal­ly, though, you’ll want to pre­fix each ver­sion with a ^ which indi­cates that you want all patch & minor updates. Here’s a quick run­down 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 com­mand is con­ve­nient, because it does­n’t require that Com­pos­er is installed, it will back­up the data­base for you auto­mat­i­cal­ly before updat­ing, and it will auto­mat­i­cal­ly run any pend­ing migrations.

The down­side is that Craft itself (since it’s just a PHP pack­age) must be installed along with the Com­pos­er PHP pack­age before it’ll work. So at some point, some­where, some­how, Com­pos­er will need to be used via the CLI anyway.

Which method you choose does­n’t mat­ter much (I per­son­al­ly use composer direct­ly), what mat­ters is that you pick one, and stick with it.

Check out the Com­pos­er 2.0 sec­tion below to see how you can auto­mat­i­cal­ly run migra­tions, apply Project Con­fig, and oth­er need­ed tasks when doing an update.

If you run into issues with Com­pos­er, check out the Trou­bleshoot­ing Com­pos­er Errors knowl­edge base article.

Link Composer 2.0

As of this writ­ing, Com­pos­er 2.0 was recent­ly released. The good news is, Com­pos­er 2.0 is sig­nif­i­cant­ly faster than Com­pos­er 1.x, and addi­tion­al­ly the composer.lock file that it writes out is back­ward and for­ward compatible.

This is awesome!

Composer 2 speed

com­pos­er install tim­ing, Com­pos­er 1.0 vs. Com­pos­er 2.0 — Com­pos­er 2.0 is > 4x faster

The only bad news is that the craftcms/plugin-installer pack­age needs to be updat­ed to ver­sion 1.5.6 or lat­er. This pack­age enables the instal­la­tion of Craft plu­g­ins (which are just Com­pos­er pack­ages of the "type": "craft-plugin").

This is a pack­age that Craft CMS itself requires, so the only rub is that if it’s already installed, you may end up see­ing 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 eas­i­ly fixed by delet­ing the vendor/craftcms/plugin-installer/ pack­age, and updat­ing Craft again, or you can just use the handy Com­pos­er Nuke” com­mand from the pro­jec­t’s root:

rm -rf vendor/ && rm composer.lock && composer clear-cache && composer update

Then if you do a composer update, it’ll down­load the lat­est Craft CMS & Plu­g­in Installer pack­ages, and away you go!

You might also run into some old­er plu­g­ins that Com­pos­er 2.0 com­plains about PSR4 name­space errors when installing.

For­tu­nate­ly, this should be an easy fix (it’s just the name­space cas­ing not being right), and Pix­el & Ton­ic’s Bran­don Kel­ly filed a Fix Com­pos­er 2 com­pat­i­bil­i­ty issue for every sin­gle Craft CMS plu­g­in that had an issue.

Com­pos­er 2.0 offers some fan­tas­tic per­for­mance gains, and it’s going to be required as of Craft CMS 3.6 any­way so… might as well get on board now.

Get on board with Composer 2.0 now!

If you use the composer CLI as I do for updates, a nice hint is you can lever­age Com­pos­er Scripts to ensure that any time a composer update or composer install is done, migra­tions are also run, caches are cleared, etc.

Just add some­thing like this to your composer.json:

  "scripts": {
    "craft-update": [
      "@pre-craft-update",
      "@post-craft-update"
    ],
    "pre-craft-update": [
    ],
    "post-craft-update": [
      "@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 || return 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 list­ed in pre-craft-update will be run. Cur­rent­ly there are none, but it’s there for future expan­sion in case you want to add some­thing like "@php craft backup/db" to back­up the data­base before each update or install.

Then the actu­al composer update or composer install exe­cutes, and after­wards the scripts in post-craft-update are run, which:

  1. php craft clear-caches/all — clears all caches
  2. php craft migrate/all — runs all migrations
  3. php craft project-config/apply — applies any changes from Project Con­fig

The --interactive=0 is a flag to force the com­mands to run in non-inter­ac­tive mode (ie, they won’t wait for you to answers prompts). This should only affect things when the shell is inter­ac­tive to begin with (which should­n’t be the case for CI com­mands), but best to be explic­it about it.

If you’re won­der­ing what the @php craft install/check is here, it’s a way for the com­mands to fail grace­ful­ly if Craft CMS isn’t installed yet.

This ensures that no mat­ter what envi­ron­ment we’re in, the appro­pri­ate migra­tions, etc. are run to keep the project in sync.

You can add your own com­mands here too, if you like, to do things like back­up the data­base, for example.

If as part of a Con­tin­u­ous Inte­gra­tion (CI) pipeline you need to do a composer install but you don’t want to run the scripts (per­haps there’s no data­base avail­able), you can use the --no-scripts flag.

Also when installing on pro­duc­tion, you often want to use the --no-dev flag with composer install so that you do not install any­thing in require-dev: (which are pack­ages for local devel­op­ment only).

Link Update your VPS

If you’re man­ag­ing your own VPS for your client projects as dis­cussed in the How Agen­cies & Free­lancers Should Do Web Host­ing arti­cle, you should update the VPS fre­quent­ly, too.

If you’re using man­aged host­ing like servd.host, ArcusTech, or for­tra­b­bit, they take care of the updates and such for you, so rest easy.

Web Host Server

While pro­vi­sion­ers like Forge and Server­Pi­lot will install secu­ri­ty updates for you, you will need to install any oth­er updates… and in either case, you’ll need to reboot the serv­er for the updates to take effect.

If you’re run­ning an Ubun­tu-based VPS (as both Forge & Server­Pi­lot do), here’s an update every­thing” com­mand 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 some­thing 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 pack­ages can be updat­ed are non-secu­ri­ty, non-crit­i­cal updates that can be updat­ed via the update every­thing” com­mand men­tioned above.

The *** Sys­tem restart required *** mes­sage means that secu­ri­ty or crit­i­cal updates have been auto­mat­i­cal­ly installed, but they won’t be in effect until the serv­er is rebooted.

I’ve seen sit­u­a­tions where peo­ple are afraid to update or reboot their serv­er because they are scared it won’t come back up.

As dis­cussed in the Mit­i­gat­ing Dis­as­ter via Web­site Back­ups arti­cle, servers should be disposable.

Update without fear

We should­n’t fear updates. The worst time to be installing updates is after you’ve neglect­ed serv­er updates for years, and a major secu­ri­ty vul­ner­a­bil­i­ty has been disclosed.

For the same rea­sons men­tioned before, we want to update the serv­er often so that the num­ber of things that have changed are small.

And we want to be con­fi­dent about doing it.

Update & reboot your VPSs about once a month, too.

So update the serv­er about once a month, too, and reboot it each time.

Final­ly, resist the urge to cre­ate your own lit­tle shared host­ing setups on your VPS. You real­ly want to have one VPS per client project.

If you want to put stag­ing & pro­duc­tion on the same VPS, that’s usu­al­ly fine, but more than that can be counter-productive.

In addi­tion to recre­at­ing every­thing that’s bad about shared host­ing your­self, you also will make it dif­fi­cult to update the VPS, because you’re adding to the depen­den­cy graph with each addi­tion­al site.

I’ve seen it many times where a VPS does­n’t get updat­ed, because of one old site on it that holds the rest back.

You can cre­ate small VPS instances eas­i­ly, so take advan­tage of that and do so!

Link Updating to Craft CMS 3.6

A new wrin­kle has been intro­duced when updat­ing to Craft CMS 3.6, because it requires PHP ver­sion 7.2.5 or later.

His­tor­i­cal­ly, Craft CMS has worked on PHP 7.0.0 or lat­er, so this is new. And even if you have PHP 7.2.5 or lat­er 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 com­mand you’ll see some­thing like this:

Craft update 3 6 error

It’s also pos­si­ble that just noth­ing will hap­pen if you do composer update, it just will nev­er update you to Craft CMS 3.6 or later.

This hap­pens if you just have a semver like ^3.0.0 — Com­pos­er will deter­mine that you can’t update to Craft CMS 3.6, and noth­ing happens.

So what’s going on here? Well, in your composer.json for your Craft CMS project, you’ll prob­a­bly see some­thing like this:

  "config": {
    "sort-packages": true,
    "optimize-autoloader": true,
    "platform": {
      "php": "7.0"
    }
  },

The plat­form spec­i­fied here ensures that no pack­age will be installed that requires a ver­sion of PHP that is greater than 7.0… and since Craft CMS 3.6 needs PHP 7.2.5 or lat­er, Com­pos­er throws its hands up and says you shall not pass!”

So you have two choic­es here. The first is to edit your composer.json to raise the plat­form requirements:

  "config": {
    "sort-packages": true,
    "optimize-autoloader": true,
    "platform": {
      "php": "7.2.5"
    }
  },

And then do you composer update.

And sec­ond, while you could tell Com­pos­er that you don’t care about the PHP plat­form require­ment via the --ignore-platform-req=php flag, you’d need to do that both when you do composer update local­ly and as part of your CI sys­tem via composer install deploy­ment script.

So prob­a­bly 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 Resolv­ing PHP Require­ment Con­flicts knowl­edge base article.

One oth­er small 3.6 wrin­kle is that some peo­ple are report­ing that the php craft com­mands are now run­ning inter­ac­tive­ly, i.e., ask­ing for user input.

This is obvi­ous­ly bad if they are part of an auto­mat­ed CI process, so we can mit­i­gate this by adding --interactive=0 to our commands.

Even if this does end up get­ting fixed in Craft core, I think it’s best to be explic­it about com­mands that are intend­ed to run in an auto­mat­ed, non-inter­ac­tive CI process by adding --interactive=0 to them.

Link Updated

Fol­low some of the tips here, and I think you’ll find that updat­ing isn’t so scary, and nei­ther is it much has­sle either.

Craft cms update contentment

Then sit back and enjoy the rest of your day. You won’t even need to use that cof­fee to wash down any pain killers.

Hap­py updating!