Skip to main content

Upgrading a major Umbraco version is never something to rush, and moving from Umbraco 13 to Umbraco 17 is a bigger jump than most. Umbraco 17 is the new Long Term Support release and is built on .NET 10, which is itself an LTS release, so this is the version worth targeting if you want a stable foundation for the next few years rather than chasing every six monthly Standard Term Support release.

There is a supported direct upgrade path from 13 straight to 17, with the internal schema migrations applied automatically on first boot, so you do not have to hop through every version in between. That is the good news. The rest of this guide is about how we actually delivered one of these upgrades on Umbraco Cloud with as little downtime as possible.

The approach I want to walk through is a deliberate one: rather than upgrading the existing Cloud project in place, we built a brand new Umbraco 17 Cloud project, migrated everything into it locally, and left the old Umbraco 13 site running until the new one was signed off. Once tested and approved, go live was nothing more than a quick database and media refresh and a hostname swap.

This is not the only way to do it, and it will not suit every project, but it worked well for us and it keeps the risk firmly off the live site until the very last moment.

Why a Fresh Project Instead of an In Place Upgrade

The short version is that we wanted the live Umbraco 13 site kept completely out of harm's way until the new site was signed off, and we wanted go live to be low risk with minimal impact and risk on the live site.

By spinning up a separate Umbraco 17 project and doing the heavy lifting locally, the live site is never touched by the migration. If anything goes wrong during the build, the live site carries on serving visitors as normal, and we simply keep working on the new project until it is right. When the new project is a complete, tested, populated site sitting on Cloud, cutover is just a matter of pointing the hostname at it.

The trade off is more manual work, because you are copying the code, database and media from the current site into a new project rather than letting Deploy and the environment pipeline handle it. I go into that trade off properly in the comparison further down, but for a jump the size of 13 to 17 I think it is a fair one.

Before You Start

A couple of things are worth having straight before you touch anything.

Each Cloud environment has its own SQL database and its own Azure Blob Storage container. That is why, further down, you will see us moving both the database and the media across separately. One does not bring the other with it.

It is also worth doing a bit of housekeeping on the source data first. Clearing out old content versions, emptying recycle bins and removing unused media all reduce the amount of work the migration has to do and make it more predictable. Only remove what you are certain is no longer needed, and take a backup before you do.

Finally, confirm your packages and any custom code are compatible with Umbraco 17. From version 14 onwards the backoffice was rebuilt on web components, so anything relying on the old AngularJS backoffice, custom property editors, dashboards and similar will need reworking. Incompatible packages and code are one of the most common reasons an upgrade falls over, so check this early.

Weighing Up Your Options

Before getting into the steps, it is worth being clear about the two separate decisions you are actually making, because they often get mixed together. The first is where you run the migration itself. The second is how you get the upgraded site onto live. They are related but they are not the same choice.

Local Migration vs Online Migration

The first decision is whether to run the database migration locally or directly on Cloud. For a major version jump like 13 to 17, this one is largely made for you.

Umbraco's own documentation is explicit that you should not perform major version upgrades directly on Umbraco Cloud. Major upgrades involve significant database migrations, and running those directly on Cloud can cause boot failures and 503 errors, with incomplete migrations that are difficult to diagnose after the fact. The recommended approach is always to run the database upgrade locally first, confirm the backoffice loads, and only then deploy the upgraded project to Cloud.

So for a major upgrade, local migration is not really one option among equals, it is the supported path. It is worth understanding why, though, because it explains the trade offs.

Running the migration locally gives you full visibility into the process. You can watch the migration run, read the logs, catch failures immediately, and iterate as many times as you need against a throwaway copy of the database before anything touches a hosted environment. If a migration goes wrong, you delete the local database and start again from your backup. Nothing is at stake.

The cost is that it is more hands on. You are responsible for starting up the local instance, running the migration, and then getting the migrated database up to Cloud yourself. It is more manual than clicking an upgrade button and letting the platform handle it.

Running a major migration online, straight on Cloud, is the thing to avoid. The apparent convenience of letting the platform do the work is outweighed by the risk of a half completed migration taking your environment down with errors that are hard to unpick. For minor and patch upgrades the automatic and semi automatic Cloud upgrade options are fine, but for a major migration it is different.

In short, for 13 to 17: migrate locally. The rest of this guide assumes you are doing exactly that.

Upgrading a Mainline Environment vs Using a Separate Cloud Project

The second decision is the more interesting one, and it is where you genuinely have a choice. Once you have migrated locally, how do you get that upgraded site live on Cloud?

The standard route is to deploy the upgrade through your existing project's mainline environments. You push the locally upgraded project to your left most environment, let it deploy up through development and staging, test at each stage, and finally deploy to the live environment. Content and media already live in each environment, so you are shipping upgraded code and schema on top of what is already there. Umbraco's guidance for larger sites here typically involves a content freeze and a hostname rearrangement to keep downtime minimal while the live migration runs.

The alternative, and the one this guide follows, is to build a brand new Umbraco 17 Cloud project and move everything into it, leaving the old project running untouched until go live.

Here is how the two stack up.

Upgrading the existing mainline environments keeps you inside one project with one repository and one set of environments. There is far less manual work, because the content and media are already sitting in each environment. You do not need to upload a database or copy media across. Deploy and the environment pipeline handle schema changes for you, and you get a clean left to right deployment flow with rollback via backups if a step goes wrong. It is the approach the platform is designed around.

The downside is that the upgrade eventually has to run against the live environment's own database. Even with local testing first, the moment the upgraded code reaches live it has to migrate the real production data, rebuild caches and refresh indexes, and on a busy site that work competes with live traffic. For large content sets this can be extensive, which brings its own risk and complexities and can make debugging issues harder.

Using a separate Cloud project keeps the live site completely isolated from the migration. The old Umbraco 13 site carries on serving visitors as normal throughout the entire build, with no risk to it whatsoever. Best of all, go live is close to a flick of a switch: because the new project is already built, tested and populated, cutover is just a hostname change rather than a live deployment and migration under load. Testing is relaxed too, because it can happen on the new site while the old one stays live.

The price you pay is the manual work. You are moving the database and media into an empty project by hand, using a BACPAC restore and Storage Explorer, and doing a fresh restore again at go live to catch any content added during testing. You are also paying for a second Cloud project for the duration of the build, and it is more moving parts than a straight in place upgrade.

The rough rule I would offer is this. If the site is small, traffic is modest and a short maintenance window is acceptable, upgrading the existing mainline environments is simpler and involves less work. If downtime genuinely matters, the content set is large, or you want the live site kept entirely out of harm's way until the last possible moment, the separate project approach earns its extra effort. For the project this guide is based on, low downtime mattered, so we went with the separate project.

The Migration Steps

Here is the sequence we followed, start to finish. The order matters in a few places, and I have called those out where it does.

1. Pull Down a Copy of the Live Umbraco 13 Database

Take a copy of the current live Umbraco 13 database and work against that copy. Never run the migration against the live database. Keep a clean backup of this copy as well, because the migration is a one way process and you want a known good starting point to fall back to.

2. Delete the Unused Legacy Media Picker Data Types on v13

Run the v13 site against your database copy and remove the unused legacy media picker data types while you are still on version 13. Doing this on v13, before you migrate, means the data type resolvers still exist and the cleanup is straightforward. Trying to untangle it after the fact on v17 is a lot more painful.

This is also the point to deal with any other legacy cleanup you have identified, so the data going into the migration is as tidy as possible.

3. Run a Blank Umbraco 17 Instance Locally and Migrate the Database

Stand up a blank Umbraco 17 instance locally and point it at your prepared database copy. On first boot Umbraco runs the schema migrations to bring the database up to the v17 structure. This is where the direct 13 to 17 upgrade path does its work.

4. Update the Project to .NET 10 and Umbraco 17

Bring the project code up to Umbraco 17 and retarget it at .NET 10, update the NuGet packages, and fix any code issues. Expect breaking changes on a major update, so give yourself time to work through compilation errors and behavioural differences rather than assuming it will build first time.

5. Test Everything on Umbraco 17

With both the database and the code now on v17, test the site thoroughly running locally. This is your first proper look at the migrated site as a whole, so do not rush it. Check content renders correctly, the backoffice behaves, and anything custom still works.

6. Create a New Umbraco 17 Project on Umbraco Cloud

Create a brand new Umbraco 17 project on Umbraco Cloud.

7. Pull Down the Blank Cloud Project into a Separate Folder

Clone the new blank Umbraco 17 Cloud project down into a separate folder, kept apart from your migrated local project. Keeping them in separate folders makes the next step much cleaner and stops you accidentally mixing the two.

8. Copy the Migrated Files into the Cloud Project

Copy your migrated Umbraco 17 files into the cloned Cloud project, then check it runs locally against the migrated database and the current live media.

The important bit here is to not overwrite the files that are specific to the Cloud project. The umbraco-cloud.json file in particular must stay as it is, along with the other Cloud specific project files, otherwise you will break the connection to the Cloud project. Merge your files in rather than replacing wholesale. This is also the point to add your licences into appsettings.

9. Copy the Live Media to the Cloud Project Blob Storage

Copy the current live media across to the new Cloud project's Azure Blob Storage container using Azure Storage Explorer. Remember that each environment has its own blob container, so this is a distinct step from moving the database. Once the media is in place, check the media paths stored in the database resolve correctly against the container.  You can connect to the Azure Blob container using the SAS URL connection string.

10. Export the Database as a BACPAC and Restore It on Cloud

Export your migrated database as a BACPAC, then restore it onto the new Cloud project through the portal. On Cloud this is done from the Backups page, where you upload the BACPAC and then restore it to your chosen environment. Note that only Project Administrators have access to the Backups page, so make sure whoever is doing this has the right permissions.

Wait for the restore to finish completely before moving on. This is important. The restore is not instant, and the next steps assume the database is fully in place.

11. Do a Full Local Export of the Deploy Files

Run a full export of the Deploy files from the new project locally. Do this after the database restore so that the Deploy metadata reflects the restored v17 schema. If the Deploy files and the database disagree, the first deployment can end up trying to reconcile the difference, which is exactly the sort of surprise you do not want at this stage.

12. Commit the Files to the Cloud Repository

Commit and push the files to the new Umbraco Cloud project repository. Because the media and database are already in place from the previous steps, the site boots against a populated database rather than dropping into an install or upgrade state.

One sequencing point worth stressing: make sure the database restore in step 10 has completely finished before this push triggers a deployment. Both operations run asynchronously on Cloud, and if the deploy races ahead of the restore you can end up with the freshly deployed code hitting a not yet restored database.

13. Check the Site Loads on Umbraco Cloud

Finally, verify the site loads and behaves correctly on Cloud. Check content, media and the backoffice, and confirm everything you tested locally still holds true on the hosted environment.

Cutover and Go Live

At this point you have a fully migrated, tested Umbraco 17 site sitting on Cloud, and the old Umbraco 13 site still serving live traffic. That parallel running is the whole point. You can take your time checking the new site with no pressure and no risk to the live site.

Once you are happy, go live is a refresh of the migrated database and media and a hostname swap. Point the live hostname at the new Cloud project and decommission the old Umbraco 13 project. Lower the DNS TTL a day or two ahead of the swap so it propagates quickly, and keep the old site warm for a little while in case you need to fall back.

The one thing to plan for is content drift. Any changes made on the live v13 site after you took your database copy will not be in the new site. Rather than imposing a content freeze during the whole testing period, which is disruptive, I do a fresh migration and restore of the database and media on go live day. That way editors can continue to make changes to the live site right up until cutover, and the new site gets the very latest content and media just before the swap. It does mean repeating the database migration, media copy, BACPAC restore and Deploy export on the day, so factor that into your go live plan rather than treating cutover as a straight hostname change on its own.

Converting Legacy Data Types Along the Way

One handy side benefit of migrating locally is that it gives you the ability to deal with legacy data types. If your v13 site still has Nested Content or the old Grid editor, both of which have been removed, you can convert them to Block List and Block Grid as part of the migration.

The tool for this is uSync Migrations, which works alongside uSync. The conversion is an export, convert, import process: you export the uSync files, run the specific migrator to convert Nested Content to Block List and Grid to Block Grid, then import the converted files. It is not a single click operation, and it pays to check the converted content carefully afterwards, as awkward source data such as missing or duplicated item IDs can trip it up. Verify a few pages by hand rather than assuming everything came across.

Do check the current uSync Migrations compatibility with your target version before relying on it, as the well trodden migrators have historically targeted the v10 to v13 range.

One important reminder: uSync and uSync Migrations are there to help you locally. Once you have done the conversion, remember to remove uSync before you push the project back to Umbraco Cloud, otherwise you risk uSync and Cloud's own Deploy engine treading on each other.

Is This the Right Approach for You?

To be clear, this worked for us on this project, but it may not suit yours. If you have a small site, a low traffic profile and a straightforward content set, upgrading the existing Cloud project in place following Umbraco's standard guidance may well be simpler and involve less manual work.

Where the fresh project approach earns its keep is when downtime really matters and you want the live site completely isolated from the migration until the moment of cutover. You take on more manual handling of the database and media in exchange for a live site that is never at risk during the build, and a go live is a repeatable process that has been thoroughly tested.

Weigh it up against your own use case. Either way, test thoroughly, keep your backups, and never run the migration against a live database you cannot afford to lose.

Useful References