RFC: استراتيجية إصدار جديدة لـ Discourse

We’re planning to introduce a new versioning system for Discourse. Our goal is to provide more choice & predictability for community administrators, while maintaining our development velocity. We’re also adjusting some terminology to align better with other software.

This document will evolve as we receive comments, begin to implement the system, and then expand use of the new release streams.

If you have any comments/suggestions at this stage, please let us know by replying to this topic!


Goals

  1. Introduce more regular ‘releases’ for Discourse, which provide a balance between speed-of-development and stability

  2. Continue to provide approx. 6-monthly releases which are supported for an extended period

  3. Provide overlapping support for regular & extended-support releases, so that administrators have more flexibility over update timing, while continuing to receive critical security updates

  4. Keep ceremony around ‘releases’ to a bare minimum. As much as possible should be automated, and should not slow down the core developer experience. ESR releases are just the same as any other release.

  5. Naming & procedure should match industry standards, so that it’s easier to explain to developers & end-users

High-level Overview

  • Cut approximately one release per month. The ‘major’ version is the current year, and the ‘minor’ version increments with each release. The patch version number will be incremented for any backported fixes

    e.g. the first release of 2026 would be v2026.0, next would be v2026.1, etc.

    Releases will receive critical fixes for two full release cycles. e.g. support for 2026.0 would continue until 2026.2 is released.

  • Approximately every 6 months, declare one of those releases as an Extended Support Release (ESR). ESR versions remain supported for 2 releases after the next ESR is declared.

    e.g. if v2026.0 is ESR, and v2026.6 is the next ESR, then v2026.0 support will end when v2026.8 is released. Assuming a monthly cadence, that would be a 2 month overlap in ESR support.

  • Provide critical fixes for latest, the most recent release, the previous release, and any active ESR versions.

  • Rename the tests-passed branch to latest

Example graph of support periods over a year:

gantt
    title Discourse Releases and Support Periods (Jan 2026 – Jan 2027)
    dateFormat  YYYY-MM-DD
    axisFormat  %b %Y

    2026.0 (ESR) :active, 2026-01-27, 2026-09-29
    2026.1 :done, 2026-02-24, 2026-04-28
    2026.2 :done, 2026-03-31, 2026-05-26
    2026.3 :done, 2026-04-28, 2026-06-30
    2026.4 :done, 2026-05-26, 2026-07-28
    2026.5 :done, 2026-06-30, 2026-08-25
    2026.6 (ESR) :active, 2026-07-28, 2027-01-26
    2026.7 :done, 2026-08-25, 2026-10-27
    2026.8 :done, 2026-09-29, 2026-11-24
    2026.9 :done, 2026-10-27, 2026-12-29
    2026.10 :done, 2026-11-24, 2027-01-26
    2026.11 :done, 2026-12-29, 2027-01-26

Implementation

  • Each release will have a branch cut from latest. These will be namespaced, and preserved indefinitely. For example, v2026.1 would have a branch named release/2026.1

  • Each patch release will be tagged. e.g. v2026.1.0, v2026.1.1, etc.

  • The latest release will be tagged release. The latest ESR will be tagged esr.

  • Previous release will be tagged release-previous. Previous active ESR (if any) will be tagged esr-previous.

  • For backwards compatibility, tags matching the existing release streams will be aliased to the closest new equivalent. stableesr. betarelease. tests-passedlatest.

    These will be considered deprecated, and we’ll aim to drop some or all of them in the future. In particular, ‘beta’ is problematic because it gives the impression that Discourse is not production-ready.

  • On latest, the version number will be the currently-in-development version, suffixed with -latest. e.g. 2026.3.0-latest

Automated Release Process

Each month, a GitHub action will open a new PR containing a single commit which bumps the version.rb on main to the next -latest version.

Once a human has merged the PR, another GitHub action will detect main moving to the next -latest and cut a branch for the completed release. Essentially, this branch becomes a ‘release candidate’. Another automated PR will be opened against the release branch with an update to remove the -latest suffix from version.rb, and thereby ‘release’ it.

Usually, we’ll merge these two PRs in quick succession. But having separate PRs for creating and finalizing the release gives us the option to address any issues in the branch prior to finalizing.

    %%{init: { 'logLevel': 'debug', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchOrder': 2}} }%%
    gitGraph
       checkout main
       commit id:'version v2026.1-latest'
       commit id:'...'
       commit id:'....'
       branch 'release/2026.1'
       commit id:'version 2026.1'
       checkout 'main'
       commit id:'version v2026.2-latest'

Separately, another GitHub actions workflow will watch for any backported commits to release branches. When any are found, a new PR will be generated to bump the patch version on that branch. A human can decide when to merge those PRs.

All these automations will automatically keep various tags (release, release-previous, esr, esr-previous, plus backwards-compatibility aliases) up-to-date.

Security Fixes

Security fix workflow remains largely the same, except that we now need to make the fixes in two of these three new places:

  • latest

  • esr

  • esr-previous :new_button:

  • release :new_button:

  • release-previous :new_button:

(Remember, per the earlier illustration, it’s only two of the three because esr-previous is supported, the new esr is the same as release or release-previous, and we drop support for esr-previous when that’s no longer true).

When introducing a security fix to latest, a latest-security-fix tag will automatically be moved to that commit. docker_manager will be updated to monitor that tag and prompt admins to update. This allows us to release and notify about security fixes without needing to expedite a version bump.

Translations

At the moment, the stable and tests-passed branches can be translated in CrowdIn, and the results are regularly integrated. In the new system, we initially plan for latest and release to be translatable in CrowdIn.

Ideally, by the time release becomes release-previous or esr, translations will have settled. If there is demand for continuous translations of those versions, then that’s something which could be considered in future.

Plugin/Theme Compatibility

Increasing the use of non-latest streams of Discourse will increase our dependence on the discourse-compatibility system. So we need some improvements to the compat system.

Instead of using a .discourse-compatibility file on main, we could instead support implicit compatibility based on specially-named branches/tags. This should be much easier than juggling commit hashes manually. For example, a plugin could have branches like

  • d-compat/v2026.1
  • d-compat/v2026.2
  • d-compat/v2026.3
  • main (used for any Discourse version which doesn’t have its own branch)

When installing a plugin, Discourse can check for a branch matching the current version. If it exists, check that out. Otherwise, check the .discourse-compatibility file. Otherwise, check out the default branch.

We can create a public GitHub action which runs on each theme/plugin daily, checks for new Discourse releases, and automatically creates these branches. Each theme/plugin could choose to use this auto-pinning action, or they could go for a more ‘floating’ strategy.

discourse.org hosting

Initially, our hosted offering will continue to run the latest version of Discourse. In the future, we’ll be exploring options for our enterprise-tier customers to select a ‘release’ version.

Standard install defaults

Initially, the default will remain latest. Admins will be able to opt-in to the new release stream in the same way that they opt-in to stable at the moment. We may explore easier switches between release streams in future, once the system is more mature.

26 إعجابًا

I’m a bit confused. So the tests-passed aka latest (which I assume is the default) is going to be the most up-to-date (getting the most frequent updates; no change there), but the current beta branch, now the release branch will still behave like it is now, i.e. a “group” of commits/updates, then the ESR (aka stable) branch will be a bigger “group” of beta/release updates?

Does this mean that release now becomes the default option, or when you say ‘latest release’ you’re referring to 'latest update on the release branch? Would there then be a difference between that and latest?

Thanks!

beta is currently a tag, and does not receive any backported fixes.

With this proposal, each versioned release will have its own branch, and will receive security fixes as long as its “supported”. People could choose to point their install at the specific version number, and keep using it after another release is made. That isn’t possible with the current beta or stable.

release will be a tag which follows the latest release (inc. patch releases) for security fixes.

No:

‘latest release’ (or just ‘release’)= the most recent commit on the most recent release branch

‘latest’ = the new name for tests-passed

إعجابَين (2)

Thanks, that clears it up!

إعجاب واحد (1)

This looks like a positive development!

Will there be any specific testing of upgrades from esr-previous to esr at the time that esr is labelled? Such upgrades, I would argue, should be arranged to be smooth upgrades, or to have good descriptions of how to perform them as smoothly as possible.

إعجابَين (2)

Yup, upgrades between ESR versions (or indeed, any supported version) will continue to be seamless.

إعجابَين (2)

Only a small request for the sake of convenience, could the pointer (e.g. 2026.0) be rather tied to the month a release is pushed? (i.e. 2026.01 for January, 2026.02 for February etc.)

إعجابَين (2)

The problem with explicitly tying things to a month is that we’ll be unable to skip a month, or release two versions in a month. That’s why we’re planning to keep it as a simple incrementing number.

إعجابَين (2)

A project I use heavily (mailcow) does skip the month when there is no significant change to core.

And counting from 0 would be really odd. It makes perfect sense for programmers but not a lot of sense for non-technical people.

Edit:

You should be able to 2026.02.x for multiple releases in a month and easily skip from 02 to 04. So long as it stays incremental?

إعجاب واحد (1)

I was wondering about the x.0 release mentioned. I really like tying it to the month so you can tell right away when a release was released. But maybe it doesn’t matter if xx.8 was released in September, December, or June. I can hardly remember what release we’re on now, though, so being able to tell right away if someone is talking about a bug from last week or some months ago without having to go look at the commit like I do now, would really be nice.

Ubuntu has YY.04 and YY.10. It’s worked for twenty years. Skipping months doesn’t seem hard.

That seems like more of a problem, though you could do something like 22.1a or 22.01a if you did have to have two releases in a month.

4 إعجابات

Some time ago we also started to use this strategy for our platform. Exactly the same including the branching and patching. I can recommend it.

We use monthly releases. So there are 1–12. The rhythm helps everyone. There is always something to release (hello, dependabot) and nobody wants to cut the branch twice a month anyway. Also when I say “I use 2025.6” everybody knows it’s the one from before the summer holidays.

That said, I was quite happy with the current process :slight_smile:

6 إعجابات

First of all, :rocket: this is a great step!

After giving this some thought, I have two minor remarks.

  1. git branch and many other tools do not understand versioning and will sort alphabetically or numerically. In both cases, 2026.10 will end up between 2026.1 and 2026.2. Inspired by Ubuntu, I propose to introduce a leading zero for releases and patch releases when they are only one digit, so we’d have v2026.01, v2026.02 and v2026.10 and then the universe is happy again.

  2. the new plugin compatibility method seems overly complex and very fragile

So if I build a new feature in my plugin that needs v2026.3, I create a branch and put my new functionality in there. Now that the feature has been built and my client is happy, I can take some rest and enjoy my vacation :palm_tree: :wine_glass: . However, after my third glass of wine, you decide to release v2026.4 and my client decides to update. And poof, there is no v2026.4 branch in my plugin and the functionality disappears :sob:

So I would never use this and I would keep using .discourse-compatibility instead.

8 إعجابات

The intention is the reverse. The compat branches are only for ‘released’ branches of Discourse. Discourse latest will always use main of your plugin. That’s where you’ll develop new features.

So the story would be:

Discourse releases v2026.2. GitHub actions on your plugin automatically detects this and cuts a d-compat/v2026.2 branch. Now, anyone using Discourse v2026.2 will be using the d-compat/v2026.2 version of your plugin.

You release a new feature on main of your plugin. You don’t need to think about backwards-compatibility, because the main branch is only used by people running Discourse latest.

Then, while you’re sipping your third glass of wine :wine_glass:, Discourse cuts v2026.3. Initially there is no plugin branch for this version, so main will be used. Stuff keeps working as it did for people on latest.

Within hours, your github action detects the new version and freezes d-compat/v2026.3, ready for your next plugin feature to land on main with no backwards-compatibility concerns.

This is essentially the workflow we use at CDCK to handle stable compatibility of themes/plugins. After each stable release, we have a script to run through our hundreds of themes/plugins and freeze them via .discourse-compatibility. This branch-based proposal aims to be a lighter-weight version of that workflow.

12 إعجابًا

That is awesome, thank you for the detailed explanation.

Sounds like I can have a fourth glass of wine :wink:

11 إعجابًا

Seconding what others have said, I like the direction of the proposed changes. And I think the proposed branch names are a lot more intuitive than the old ones :+1:

What’s not quite clear to me yet is how the upgrade process will work for the release and esr branches (latest seems straight-forward). You mention that, at each point in time, both the current release (let’s call it n) and the previous release (n-1) will be supported, and that, as an admin, I will have a choice on when to upgrade.

Now based on my experience with other software that, when a new release version (n+1) arrives, I would be notified about the availability of version n+1. And that I could then decide to do a major upgrade (equivalent to e.g. apt dist-upgrade on Linux) or do a minor/standard update (equivalent to e.g. apt upgrade on Linux) and stay on version n. Is that something that will be worked into the Discourse launcher script?

Also, I do understand the desire to keep release ceremony/process to a minimum, but my intuition would be that both normal and esr releases receive at least a bit of extra testing, before they are released. That might be shaped by working too long in enterprise IT, though :smile:

Lastly, I am wondering if monthly releases are actually “too fast”. Now that’s admittedly also subjective, but judging from my own experience in managing IT stuff on the side as a volunteer, I might not have time to bigger updates each month. And starting from that, I was wondering if you could potentially also keep your lives as developers of Discourse a bit easier by simply releasing quarterly, and having no separate esr branches, but only release branches.

3 إعجابات

That will make the life of developers of themes and plugins harder, because in that (this) situation you have a time pressure to update your themes and plugins, and otherwise you won’t have any security updates. An ESR version will take that pressure off.

إعجابَين (2)

I’m not sure if I completely follow. My understanding based on the previous posts was that the creation of a release version branch would automatically trigger the creation of a plugin branch for that version. So my assumption would be that merging release and esr into one would also reduce effort for plugin and theme builders, because any time you need to push a fix, it would need to be pushed to less branches (three instead of five). But maybe I’m missing something?

إعجاب واحد (1)

It’s not about when the theme/plugin author pushes a fix, it’s about when Discourse core has a security fix.

Per

the only difference between release and esr is that esr gets security fixes for 6 + 2 = 8 months.

With the current launcher tooling, and this new branch structure, you could have control over upgrade timing by doing something like:

  1. v2026.02 released
  2. You set version: release/v2026.02 in your app.yml file
  3. v2026.03 released
  4. You run a rebuild. You still get 2026.02, with any recent security fixes
  5. When ready, you switch to version: release/v2026.03 in app.yml

But manually editing that app.yml every month is really not ideal, so hopefully we’ll be able to design a system which makes the process more user friendly.

The process in the OP does allow us to treat the branches as ‘release candidates’ before actually marking them as a release. I’m not sure exactly if/how we’ll use that capability at this stage - I think it’s something that will evolve as we get used to the new system.

We’re trying to strike a balance here between the velocity of Discourse development, and the stability for people with extensive customizations. Having a 3+ month delay on getting features into the hands of our customers isn’t an option. If anything, monthly is on the slow side for us. At the moment we still intend to use latest for the majority of our hosting.

But of course, for people hosting Discourse themselves, I understand the desire to have less frequent change. So that’s where the ESR releases come in.

3 إعجابات

It’s up to the plugin/theme author here. Either they can continue with the current strategy, where the main branch of the plugin needs to work with all ‘released’ versions of Discourse. Or they can use the automatic branching strategy, which makes compatibility easier, but also means you may need to do a lot of ‘backporting’ in your plugin in case of any critical bug/security fixes.

At any given time, there are three “supported” versions of Discourse, plus latest. So critical fixes will need to be applied in up-to 4 branches.

إعجابَين (2)