‹ All posts

Documentation versioning best practices with docs-as-code

Many software products end up having multiple releases over their lifetimes. When this happens, the documentation needs to be versioned along with the product.

In this post we will look at documentation best practices when using a Git-based docs-as-code workflow.

Software versioning basics

Before going into how we should version documentation, let’s take a look at how software projects generally handle versioning.

Disclaimer: there is more than one way to version software. The method outlined in this post works for many projects, but there are other valid workflows too.

Versioned releases

Products like Windows, PostgreSQL, or React JS all have versioned releases. Every once in a while, depending on their release cadence, a new version with new features is released.

PingCap's TiDB's documentation versions

A long list of versions for PingCap's TiDB documentation

Different projects will use different versioning schemes, but Semantic Versioning is a popular way of giving structure to your version numbers. Another option is using a date-based scheme.

Usually, different versions are managed in separate Git branches.

A typical branching strategy is to have all work for the upcoming release happening on a main or development branch. Then, when a new release is ready, a new branch is created from the main branch and named after the release (for example v4.0).

1
2
3
4
5
6
7
 main  # Development happens in this branch
  * 
  |   v4.0 # New branch for the release
  *     *
  |    /
  * ——
  |

There’s a few reasons for this. Firstly, it’s clear what code belongs in the release, and what does not. But importantly, you can apply hotfixes to the release branch:

1
2
3
4
5
6
7
8
9
 main 
  *
  |   v4.0 
  *     *  # <- New hotfix commit
  |     |
  *     *
  |    /
  * ——
  |

This allows you to update and fix the 4.0 version, while you keep moving forward in your main branch towards the next release. Perhaps you’ll even release a patch release with bug fixes: a 4.0.1.

This strategy also makes it clear which features and commits belong to which version. You don’t have code for multiple versions mixed together. There is clear separation between versions.

When is versioning not needed?

There are some software products that do not have multiple versions. Take a service like Slack. There is only one version of it: the currently live version.

In such cases, you typically have one set of documentation that lives with the product. New features are built, documented, released, and ultimately deprecated in unison.

What these kinds of products however may require is API versioning (see below).

Git versioning strategies for documentation

Ok now that we have an idea of how software versioning works, how do we apply this to documentation?

One of the reasons you want to use docs-as-code is to mirror the release process of the rest of your product. When you release a new version of your code, you also release the corresponding documentation.

How is this managed in Git?

Versioning documentation in multiple branches

With this method, you put the documentation for each version in its own branch:

  • Version 3.4 -> branch v3.4
  • Version 4.0 -> branch v4.0
  • New development -> branch main

This is essentially the same strategy as described above with source code.

When you’re ready to create a new release, you create a new branch from the main branch:

1
2
3
4
5
# Ensure you are on the main branch
git checkout main

# Checkout a new branch with the name of the version
git checkout -b v2.0

Pros and cons

The main benefit here is that you are able to match the workflow of the source code itself. There is also no duplication of content: the content for each version lives in its own branch.

The downside of this approach is that many docs-as-code tools do not support this workflow. Static site generators like Docusaurus cannot construct one site from multiple Git branches. Instead, you have to deploy separate sites for each version, which can add a lot of complexity.

Doctave supports this form of versioning and can build your documentation from separate branches into a single documentation site. The user can then select which version of the documentation to view from a dropdown menu.

On the open source side, the AsciiDoc documentation site generator Antora also supports this workflow.

So in short:

  • ✅ Mirror how source code is versioned
  • ✅ Little duplication of content
  • ⚠️ Requires specialized support from your tools or managing multiple sites

Versioning documentation in one branch

You can also choose to version your documentation all in one branch. This is arguably the simpler way to manage documentation.

The idea is to structure your documentation something like this in your Git repository:

1
2
3
4
5
6
7
docs
├── v1
│   └── ...
├── v2
│   └── ...
└── v3
    └── ...

Each version lives in its own subdirectory, in a single branch. Docusaurus supports this method of versioning, and it is able to generate a dropdown that lets the user swap between versions.

You can also decide to build each version into a separate site that can be deployed and hosted independently. But then the same issues regarding routing come into play as in the multi-branch setup.

Pros and cons

The main benefit here is simplicity. If you are not an experienced Git-user, having everything in one branch is easier than juggling multiple branches.

There are however some significant downsides to this workflow.

The main issue is around content duplication and build times. For example, adding a new version in Docusaurus means copying all of the content you have in your existing docs into a new subdirectory, which now becomes your new version.

If you only change 20% of your docs between versions, 80% of your files will be needlessly duplicated.

This can lead to very long build times. The Write The Docs Slack, popular among technical writers, is full of anecdotes of Docusaurus builds even taking over 30 minutes.

Some other tools may be faster, but the fact is that the more content you have, the slower your builds will be. Tools that cannot build versions independently will always struggle when you add more versions.

So, to recap:

  • ✅ Simple - no need to manage multiple branches
  • ⚠️ Duplicated content
  • ⚠️ Can be a big cause of long build times

Versioning documentation using Git tags

One final option that works in some cases is using Git tags. Tags are a feature in Git that lets you effectively give a name to a specific commit.

In this workflow, when you are ready to publish the documentation for a given version, you add a tag to that commit:

1
git tag -a v1.4 -m "Version 1.4"

You can then go back to any tag you’ve created easily by checking out that tag:

1
git checkout v1.4

You can also list your tags easily:

1
2
3
4
5
git tag
# v1.0
# v1.1
# v1.2
# v1.4

Tip: Use tags liberally

Tags are a cheap and easy way to add details to your Git history. You can use them in conjunction with other versioning schemes too, and for other reasons than just versioning.

Pros and cons

This is a very lightweight way to add “checkpoints” in your Git history that signify “this was the state of the documentation at the time of release”.

The main issue with this workflow is that you cannot backport fixes or make changes to old versions. You can’t change the history of your main branch (without some serious rebasing).

The good news is that when you do, you can create a new branch!

1
2
3
4
5
6
7
8
9
10
11
# Checkout the tag you want to update
git checkout v1.4

# Create a branch from this point
git checkout -b v1.4.1

# ...make your changes...

# Commit your changes
git add .
git commit -m "Updated docs for version v.1.4.1"

You have now gone back to the branch-per-version workflow!

Finally, your tools will have to support the same workflow as with the branch-based workflow: you have to be able to build your versions separately.

So, to recap:

  • ✅ Simple - no need to manage multiple branches
  • ✅ Works great if you know past versions will never be updated
  • ⚠️ Can’t make changes to old versions without moving to a branch-based model
  • ⚠️ Requires specialized support from your tools or managing multiple sites

REST API documentation versioning

API versioning is related to versioning in general, and warrants its own section. This is a nuanced topic: there is no “one way” to version your APIs, even if some best practices have emerged.

REST APIs will often be versioned independently from the rest of the product. And because you generally cannot make breaking changes without giving your users ample time to migrate, you can have multiple versions of your API running at the same time.

Stripe is a great example of an API that does this. They write about their versioning strategy here. The Stripe API expects a Stripe-Version HTTP header that specifies which version of the API you want to be using. This way the user can decide which version to use, and can update at their own pace.

When a breaking change is made, Stripe creates a new version:

When backwards-incompatible changes are made to the API, a new, dated version is released. The current version is 2023-10-16.

Now any client that wants to opt into these new features or breaking changes can update their code and swap the HTTP header to the latest version.

Documenting API versions

Stripe is of course famous for its documentation, and they customize their API reference to match the API version of your account. When you update your API version, the documentation changes to reflect that.

Another way to document differences between versions is to include notes in the API reference itself. For example adding a note in an OpenAPI description field saying Deprecated since v2.4 or Requires v4.1 or later for any operations or fields that have versioning requirements.

Or, you may end up using one OpenAPI specification per API version. This may happen if your API versions live under different path namespaces (for example /api/v1/ and /api/v2/. In this case, you will likely want to allow the user to select which version of the API they are using in the documentation itself, and show the appropriate API reference.

Hosting considerations

Good tools that create documentation websites will always have some kind of versioning mechanism. You’ll want to make sure that it matches the workflow you want to use for versioning.

Here are a few versioning-related details to consider when choosing your tools.

Switching between versions

Somewhere in your documentation, you will want to give your users the ability to change the version they are viewing.

A version dropdown in Doctave

A version dropdown in a Doctave site

Commonly your URLs will map to specific versions too:

  • Content for the default version will be under docs.mycompany.com/...
  • Content for a v2 version would be under docs.mycompany.com/v2/...
  • Content for a v3 version would be under docs.mycompany.com/v3/...

Access control

In some cases it can be beneficial to only allow public access to certain versions. Perhaps you only want to give preview access to a version to a specific version?

In this case, you’ll have to make sure either your hosting provider or documentation platform supports this, or you have to do some custom engineering to achieve this.

Doctave is a solution that supports this out of the box. You can create public, private, or password-protected versions and map them to any Git branch.

Releasing new versions

Eventually new releases will show up, and old versions will be removed. Your documentation has to reflect these changes.

Releasing new versions is the simpler case. Let’s say you are moving from v3 to v4. The process of updating your docs would likely be as follows:

  1. You default docs (i.e docs.mycompany.com) now point to your v4 docs
  2. You create a new space for your old v3 docs under docs.mycompany.com/v3

One big benefit of this is SEO. Any content that did not change between versions is still in the same URL path, and Google will keep sending traffic to the latest version of your documentation.

Deprecating old versions

Deprecating versions is more involved, if you want your old URLs to still work. This is done with redirects.

Let’s say you’re deprecating your legacy v1 version and it lives under docs.mycompany.com/v1. There will be content and links both on the internet and in your own docs pointing to this old documentation. Ideally, you want those links to continue to work. Both for SEO, but also to minimize confusion when users are faced with a 404 page.

You’ll want to add redirect rules from your deprecated documentation that will send readers to another version of your documentation.

The simple way is to redirect all links under the /v1 path to e.g. your latest version. This is easier, but can lead to confusion if your user was searching for a specific topic, and then finds themselves on an unexpected page.

The more involved (but user friendlier) way is to try to map each piece of content from your deprecated version into a corresponding page in your new documentation. This will be simple for features that exist in both versions: you can map a /v1/account-creation page to the latest version’s equivalent page (likely, /account-creation). If an equivalent page does not exist, you can fall back to sending people to the front page of your documentation.

Final thoughts

In the end, your documentation versioning strategy should match your product’s versioning strategy. The important thing is that your tools support the strategy you choose and you are able to update past versions easily.

There are multiple right ways to do versioning, so you should discuss the options with your team, and choose what is right for your situation.

Articles about documentation, technical writing, and Doctave into your inbox every month.