# Upgrade Open edX from any old release to Verawood with Tutor, one release at a time

https://cubite.io/blogs/upgrade-open-edx-with-tutor

**By:** Amir Tadrisi
**Updated:** 2026-10-27

The Open edX upgrade path from Lilac or any later release to Verawood with Tutor: the Tutor version and what breaks at each hop, the row-count check and the rollback.

You cannot skip named releases. Cubite runs managed Open edX from $1,260 a month for up to 1,000 concurrent learners, and named-release upgrades are included in every managed tier. This runbook is for teams that upgrade their own Tutor installation: it takes a site from Lilac, or any later release, to Verawood one release at a time, with the Tutor version for each hop, what breaks, the checks to run after each hop and the way back.

Short answer (updated October 26, 2026): each Open edX named release has its own Tutor major version, from Tutor 12 for Lilac to Tutor 22 for Verawood, so going from Lilac to Verawood takes ten hops, one release at a time. For each hop, back up the platform, install the next Tutor major, run the launch command, then check that your counts of users, enrollments and certificates have not dropped. Tutor does not support downgrading, so the backup you take before each hop is your rollback.

As of October 26, 2026, Verawood (released July 30, 2026, on Tutor 22) is the only supported Open edX release. Willow, the next one, is planned for a release cut in early January 2027 and a release in late February 2027, as the Build-Test-Release working group [confirmed on October 5, 2026](https://discuss.openedx.org/t/willow-release-delay/19461). The [Open edX release table](https://cubite.io/open-edx/releases) lists every release with its Tutor version and support status, and Cubite's free [upgrade-path checker](https://cubite.io/services/open-edx-security-audit) lists the hops from your release.

## Why you cannot skip a named release

Open edX periodically squashes its Django database migrations, so a database has to pass through each release in turn, and Tutor's upgrade steps, such as the MongoDB version upgrades, are written per release. Tutor's [upgrade guide](https://docs.tutor.edly.io/upgrading.html) describes moving "from one major version to the next" and warns that "any upgrade is final because downgrading is not supported". When Aulasneo took a client from Lilac to Ulmo, nine releases in one maintenance window, the team built a minimal Docker image for each release from Koa onward just to run the migrations ([their write-up on the Open edX forum](https://discuss.openedx.org/t/nine-releases-in-one-maintenance-window-our-lilac-ulmo-migration/19509), September 1, 2026).

## Before the first hop

- Find your release: tutor --version gives your Tutor major, and tutor config printvalue OPENEDX_COMMON_VERSION gives the Open edX tag you run
- List what you added: plugins (tutor plugins list), custom images, your theme, and any fork set in EDX_PLATFORM_REPOSITORY
- Note what runs outside Tutor: with RUN_MYSQL or RUN_MONGODB set to false, you back up and upgrade those databases yourself, because Tutor's upgrade steps only touch its own containers
- Measure the data, MySQL and MongoDB both: Studio's course file uploads live in MongoDB's GridFS, and in the #19509 upgrade MongoDB held 500 GB, almost all of it video
- Copy production to a staging server and run every hop there first, as Tutor's upgrade guide recommends
- Record the row counts below before the first hop, on staging and again in production
- Check the host against the newest Tutor you will run: Tutor 16 and later need Docker Compose V2 (docker compose, not docker-compose), and Tutor 22 needs Python 3.10 or later when installed with pip
- Plan the window: in #19509 the production upgrade took one hour, after two months of preparation

## One hop, step by step

1. Stop the platform and back up the whole Tutor root, the single folder where Tutor keeps the configuration, the environment and the data: tutor local stop, then sudo rsync -avr "$(tutor config printroot)/" /var/backups/tutor-before-maple/. Name the folder after the release you are moving to, and keep a copy off the server.
2. If MySQL or MongoDB run outside Tutor, back them up with your provider's tools as well: they are not in the Tutor root.
3. Install the next Tutor major and nothing newer, for example pip install "tutor[full]~=13.0" for Maple, the same range as Tutor's own pins (the table below gives the major for each hop). Upgrade your own plugins to releases built for that major.
4. Run tutor local launch (tutor local quickstart on Tutor 14 and earlier). Tutor sees that the environment was generated by the older release and runs that release's upgrade steps once.
5. If you build custom images, those steps are skipped once you have run tutor config save, so run them yourself: tutor config save, tutor images build openedx (plus any other image you customize), tutor local upgrade --from=lilac with the release you are leaving, then tutor local launch (quickstart on Tutor 14 and earlier).
6. Run the row-count check and the checks for that hop in the table, then start the next hop.

## Every hop from Koa to Verawood

Each row is one hop. The Tutor column is the major to install for it: pip install "tutor[full]~=N.0" picks that major's latest patch, the same range that Tutor's [table of older releases](https://docs.tutor.edly.io/gettingstarted/installation.html) pins. The middle column is a selection of the changes in [Tutor's changelog](https://github.com/overhangio/tutor/blob/release/CHANGELOG.md), most of them marked as breaking, plus what Tutor's upgrade step does for that release and, where noted, what changes in the release itself; read the full list against your own plugins and customizations.

| Hop | Tutor | What changes or breaks (Tutor, unless noted) | Check after the hop |
| --- | --- | --- | --- |
| Koa to Lilac | v12 | MongoDB goes to 4.0 in Tutor's upgrade step; Python-evaluated problem input is off by default, as Tutor does not run codejail; the Android app and the web UI leave core Tutor for plugins | MongoDB runs 4.0; courses open; problems that run Python still grade |
| Lilac to Maple | v13 | Caddy replaces the Nginx container, so custom nginx patches become caddyfile patches; every container runs unprivileged; the forum moves to the tutor-forum plugin; the legacy courseware is no longer supported, so Tutor's upgrade step enables the mfe plugin and learners move to the Learning MFE | Discussions load with the forum plugin enabled; courseware opens in the Learning MFE; custom web-server rules moved to Caddy |
| Maple to Nutmeg | v14 | lms.env.json and cms.env.json become YAML files, so plugin patches to them must be rewritten as YAML; persistent grades are on by default | Plugins load; course tabs and outlines appear |
| Nutmeg to Olive | v15 | quickstart becomes launch and init becomes do init; plugins that reached edx-platform through the openedx-dockerfile-post-python-requirements patch must move to openedx-dockerfile-pre-assets | Custom images build; grades show on the progress page |
| Olive to Palm | v16 | Docker Compose V2 is required; MySQL goes from 5.7 to 8 and MongoDB to 4.4; the hooks API changes break older custom plugins; the ORA2 upload folder is renamed. The version range installs the latest 16.x, which runs MySQL 8.1 and carries the 16.1.0 fix for data broken during the Olive to Palm migration (a utf8mb3 and utf8mb4 mismatch) | Accented text and emoji read back correctly; ORA2 uploads open |
| Palm to Quince | v17 | Images build with BuildKit only; Python packages added through private.txt are no longer supported (use tutor mounts); the Indigo theme is switched on when no other theme is set | Your own theme is still the active one; extra Python packages installed |
| Quince to Redwood | v18 | MongoDB goes from 4.4 to 7.0 by way of 5.0 and 6.0, which Tutor's upgrade step runs (on Kubernetes it prints the commands instead); MySQL goes to 8.4; openedx-assets gives way to npm run; translations come from atlas, which ends custom openedx-i18n locales; a fork set in EDX_PLATFORM_REPOSITORY must end in .git | MongoDB runs 7.0; theme assets compile; your languages still load |
| Redwood to Sumac | v19 | Meilisearch replaces Elasticsearch; tutor local do convert-mysql-utf8mb4-charset converts existing tables to utf8mb4, which Tutor warns is potentially irreversible and may take a long time (the single longest step in #19509); forum v2 keeps existing forum data in MongoDB until you migrate it to MySQL | Course, courseware and Studio search return results; every table converted to utf8mb4 |
| Sumac to Teak | v20 | Before this hop, move forum data from MongoDB to MySQL with tutor local run lms ./manage.py lms forum_migrate_course_from_mongodb_to_mysql all, which the tutor-forum README calls non-destructive and safe to re-run (openedx/forum 0.4.3 removed the MongoDB backend). The site-configuration script is removed in favor of the create_or_update_site_configuration command. From #19509: the forum migration after Sumac needed FORUM_MONGODB_DATABASE patched back in, and Teak's Meilisearch reindex stalled init on a small instance | Forum threads survived the move to MySQL; the search index is complete |
| Teak to Ulmo | v21 | uv replaces pip in image builds (VCS editable requirements need a MANIFEST.in, or PIP_COMMAND=pip); the WebUI plugin is removed in favor of Tutor Deck; the preview page setting is removed now that the Learning MFE serves it; on Kubernetes, patchesStrategicMerge becomes patches | Custom requirements install; course preview opens |
| Ulmo to Verawood | v22 | Tutor needs Python 3.10 or later; Meilisearch 1.36 cannot read the old index, so it is deleted and rebuilt (below). Beyond Tutor, in Verawood itself: the new Instructor Dashboard replaces the legacy one, and sections injected through InstructorDashboardRenderStarted no longer appear; two migrations can stop on old data, and notifications are on by default (below) | Search works after the reindex; instructor-dashboard customizations still present; notifications set the way you want |

On the Ulmo to Verawood hop, Meilisearch 1.36 restart-loops on the old index ("database version (1.8.4) is incompatible with your current engine version (1.36.0)"), so rebuild the index during that hop, as Tutor's changelog gives it: stop the platform, delete the old index, start it, re-run init, then reindex Studio and the active courses: tutor local stop, rm -rf "$(tutor config printroot)/data/meilisearch/data.ms", tutor local start -d, tutor local do init, tutor local exec cms ./manage.py cms reindex_studio and tutor local exec cms ./manage.py cms reindex_course --active. On a large site the reindex takes a while, and search stays incomplete until it finishes. For Verawood's own changes beyond Tutor, see [what is new and what breaks in Verawood](https://cubite.io/blogs/open-edx-verawood-features).

Verawood's [Developer and Operator Release Notes](https://docs.openedx.org/en/latest/community/release_notes/verawood/dev_op_release_notes.html) also list two migrations that can stop on old data, and a default that changed:

- The split_modulestore_django migration 0004_courseid_unique_ci fails with an IntegrityError if two course IDs differ only in letter case: delete the invalid course record as the notes describe, then re-run the migrations.
- The new catalog backfill fails with a ValueError if a course's organization short code is missing from the Organizations table: create the missing Organization record in Django admin, then re-run the migrations.
- Notifications and notification emails are on by default; to keep them off, enable the notifications.disable_notifications or notifications.disable_email_notifications flag.

On a release older than Koa: Tutor's table of older releases starts at Koa (Tutor 11), and its upgrade command carries steps back to Ironwood, so the same one-release-at-a-time rule applies from there. A site that was installed without Tutor has to move onto Tutor first, which is a project of its own.

## After every hop: count users, enrollments and certificates

An upgrade should not remove accounts, enrollments or certificates, so three counts are a quick check that a hop kept your data. Take them before the first hop, on the staging copy and again at the start of the production window, and after every hop: none of the three may drop. Tutor's init can add a service account, so a small rise in users is expected: at the Lilac to Maple hop, Tutor 13's init creates one called cms. The counts come from the auth_user, student_courseenrollment and certificates_generatedcertificate tables, in the database Tutor names in OPENEDX_MYSQL_DATABASE (openedx by default). With Tutor's own MySQL, this command runs the count in the mysql container, in the same pattern as [Tutor's database-dump command](https://docs.tutor.edly.io/sysadmin/datamigration.html):

tutor local exec -e USERNAME="$(tutor config printvalue MYSQL_ROOT_USERNAME)" -e PASSWORD="$(tutor config printvalue MYSQL_ROOT_PASSWORD)" -e DB="$(tutor config printvalue OPENEDX_MYSQL_DATABASE)" mysql sh -c 'mysql --user=$USERNAME --password=$PASSWORD $DB -e "SELECT (SELECT COUNT(*) FROM auth_user) AS users, (SELECT COUNT(*) FROM student_courseenrollment) AS enrollments, (SELECT COUNT(*) FROM certificates_generatedcertificate) AS certificates"'

If a number drops, stop: restore the backup from before that hop, and find the cause on staging before you try the hop again.

## Rollback

Tutor does not support downgrading, so a failed hop is rolled back from the backup you took before it: stop the platform and restore the backed-up Tutor root with --delete, so that files the newer release wrote, such as a MySQL 8 data directory or a Meilisearch 1.36 index, do not survive: sudo rsync -a --delete /var/backups/tutor-before-maple/ "$(tutor config printroot)/". Then reinstall the Tutor major you were on and start the platform with tutor local start -d. Restore any MySQL or MongoDB that runs outside Tutor from its own backup at the same time. Coming back from Verawood, the old search index has to come from that backup too: Tutor's changelog notes that Meilisearch 1.8.4 cannot read an index written by 1.36.0. Rehearse the restore on staging before the production window, so you know how long it takes.

## Lessons from a nine-release upgrade

A detailed recent account on the Open edX forum is Aulasneo's Lilac to Ulmo upgrade ([discuss #19509](https://discuss.openedx.org/t/nine-releases-in-one-maintenance-window-our-lilac-ulmo-migration/19509), September 1, 2026), done in a one-hour window after two months of preparation. What cost them the most time:

- Squashed migrations: they could not skip releases, so they built a minimal Docker image per release from Koa onward just to run the migrations.
- Converting every table to utf8mb4 was the single longest step.
- MongoDB held 500 GB, almost all of it video in GridFS, because that is where Studio's course file uploads go.
- After Sumac, the forum migration needed FORUM_MONGODB_DATABASE patched back in.
- Teak's Meilisearch reindex stalled init on a small instance.

## Do it yourself, or have it managed

| Your situation | Upgrade it yourself | Have it managed |
| --- | --- | --- |
| One release behind, standard plugins, and someone who has run a Tutor upgrade | Good fit | Optional |
| Several releases behind, with custom plugins, a custom theme or a fork | Possible: rehearse every hop on staging | Worth pricing |
| No one on staff who knows Tutor, Docker and Django migrations | Risky | Good fit |

Cubite is one of the managed options and publishes this runbook. Named-release upgrades are included in every managed Open edX tier (Lean $1,260 a month, Resilient $1,850 a month, Scale from $3,400 a month), each rehearsed on a UAT environment before production. See [managed Open edX](https://cubite.io/services/managed-open-edx). A platform Cubite takes over starts on the release it already runs, and bringing it up from an unsupported release is a fixed quote agreed before the managed term starts. If you keep the upgrade in-house but need a plugin, theme or XBlock ported to a new release, see [Open edX development](https://cubite.io/services/open-edx-development).

## Upgrade done for you: managed Open edX from $1,260 a month

Named-release upgrades are included in every managed Open edX tier, each rehearsed on a UAT environment before production. Running an unsupported release? Bringing it current is a fixed quote, agreed before the managed term starts.

[Book a 30-minute call](https://cubite.io/go/book?src=open-edx-upgrade)

## FAQ

### Can I upgrade Open edX from Lilac straight to Verawood?

No. You cannot skip named releases: a Lilac site passes through Maple, Nutmeg, Olive, Palm, Quince, Redwood, Sumac, Teak and Ulmo on the way, ten hops with one Tutor major each. Cubite, which runs managed Open edX from $1,260 a month for up to 1,000 concurrent learners, includes named-release upgrades in every managed tier.

### Which Tutor version goes with each Open edX release?

Tutor's major version follows the release: Koa 11, Lilac 12, Maple 13, Nutmeg 14, Olive 15, Palm 16, Quince 17, Redwood 18, Sumac 19, Teak 20, Ulmo 21 and Verawood 22. For each hop, install the latest patch of the next major, as Tutor's table of older releases pins it.

### How long does an Open edX upgrade from an old release take?

It depends more on data size and customizations than on the number of hops. In the Lilac to Ulmo upgrade described on the Open edX forum (discuss #19509), preparation took two months and the production window one hour, and the longest single step was converting every MySQL table to utf8mb4.

### Can I roll back an Open edX upgrade?

Only from a backup. Tutor does not support downgrading, so back up the Tutor root, and any MySQL or MongoDB that runs outside Tutor, before every hop, and rehearse the restore on staging.

### What is the next Open edX release after Verawood?

Willow. The Build-Test-Release working group confirmed on October 5, 2026 that Willow will be cut in early January 2027 and released in late February 2027, with exact dates still to be announced. Once it ships, Willow becomes the only supported release, and the next hop is Verawood to Willow. If your forum data still sits in MongoDB, move it to MySQL before that hop: openedx/forum removed its MongoDB backend in 0.4.3, and Verawood still pins 0.4.2.

### Are upgrades included in Cubite's managed Open edX?

Yes. Named-release upgrades are included in every managed Open edX tier (Lean $1,260 a month, Resilient $1,850 a month, Scale from $3,400 a month), each rehearsed on a UAT environment before production. A new managed Open edX platform from Cubite, on every tier from $1,260 a month, starts on the current named release (Verawood on Tutor 22, as of October 2026); a platform Cubite takes over starts on the release it already runs, and bringing it current is scoped before the managed term starts, as a fixed quote.
