RFC: A new versioning strategy for Discourse

david
Tue, 23 Sep 2025 07:55:33 +0000
Development

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. stable → esr. beta → release. tests-passed → latest.

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

release

release-previous

(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.

59 сообщений - 18 участников