Version your docs with branches

This how-to guide sets up versioned documentation the way Antora intends: one git branch per documented version, with the playbook assembling all of them into one site. No copied folders, no docs-v2-final/, and backporting a fix to an older version is a cherry-pick.

It assumes a working component and playbook (from the tutorial) and a product that's about to ship a version worth freezing the docs for.

The model in one paragraph

The documented version of a component comes from the version key in antora.yml — as it exists on each branch. The branch that holds today's development docs says version: '2.0'; the branch you froze at the last release says version: '1.0' in its copy of the same file. The playbook lists which branches to read, Antora builds every component version it finds, and the site's version selector appears on its own.

1. Decide what a "version" is

Document versions the reader chooses between, not every patch. If users run "Widget 1.4" and "Widget 2.0", those are your docs versions; 1.4.3 is not. Each docs version gets a long-lived branch in the content repository. Reuse your release branches if the product already maintains them, since docs fixes then ride the branch your team already backports code to.

2. Cut the version branch at release

When Widget 1.0 ships, freeze its docs by creating the branch (if your release process hasn't already):

cd widget
git switch -c v1.0 main
git push -u origin v1.0

The antora.yml on this branch already says version: '1.0', so there's nothing to edit here. This branch now receives only fixes that apply to 1.0.

3. Roll main forward to the next version

On main, update the descriptor so development docs stop claiming to be 1.0. While you're in the file, mark the unreleased version as a prerelease:

name: widget
title: Widget
version: '2.0'
prerelease: true
nav:
- modules/ROOT/nav.adoc

prerelease: true keeps 2.0 out of the "latest" slot: it still builds and appears in the version selector (labeled as a prerelease), but links that point at the component's latest version keep resolving to 1.0 until 2.0 actually ships. At ship time, cut v2.0 from main, delete the prerelease line on the new branch, and bump main to '2.1' — that's the whole release ritual.

4. Tell the playbook which branches to read

In antora-playbook.yml, widen the content source from one branch to a pattern:

content:
sources:
- url: https://github.com/your-org/widget.git
branches: [main, v*]
start_path: docs

branches accepts exact names and globs; [main, v*] reads the development docs plus every version branch you've cut. Rebuild, and the site now serves /widget/1.0/ and /widget/2.0/ side by side with a working version selector — that's the entire feature.

5. Backport a fix

A reader reports that the 1.0 install page documents the wrong flag, and the mistake is still present on main. Fix it once, then backport:

git switch main
# edit, commit via the normal pull-request flow
git switch v1.0
git cherry-pick <commit-sha>
git push

The next site build publishes the correction in both versions. This is the payoff of versions-as-branches: the operation you already trust for code works unchanged for docs, review included if you prefer a pull request over a direct cherry-pick.

NOTE: If a fix applies only to an old version (say, 1.0-specific behavior), commit it directly to v1.0 and skip main. Branches are independent; nothing forces changes to flow through the development branch.

6. Retire a version

When 1.0 leaves support, stop publishing its docs by removing it from the playbook, not by deleting the branch:

branches: [main, v*, "!v1.0"]

The negated pattern excludes the branch from builds while the branch itself (and its history) stays in the repository, ready if you ever need to republish or consult it. Deleting docs history to unpublish a page is the wiki reflex; you don't need it anymore.

Common mistakes

  • Unquoted versions. version: 2.0 is a YAML float and will bite you as '2' somewhere downstream. Always quote: version: '2.0'.
  • Fixing docs only on main. Readers of 1.0 never see it. Make "does this need a backport?" a standard pull-request question for docs, same as for code.
  • A version branch nobody can find. Name docs version branches exactly like the product's release branches, or document the mapping in the component's CONTRIBUTING notes.
  • Versioning pages that don't version. Release notes or a glossary that should always show the latest content can live in a separate unversioned component (version: ~) instead of being backported forever.