Component Versioning
Goal
Larger applications consist of multiple components that reference each other and rely on compatibility of the interfaces/contracts of the components.
To achieve the goal of loosely coupled applications, each component should be versioned independently hence allowing developers to detect breaking changes or seamless updates just by looking at the version number.
Version Numbers and Versioning schemes
For developers or other components to detect breaking changes the version number of a component is important.
There is different versioning number schemes, e.g.
major.minor[.build[.revision]]
or
major.minor[.maintenance[.build]]
.
Upon build / CI these version numbers are being generated. During CD / release components are pushed to a component repository such as Nuget, NPM, Docker Hub where a history of different versions is being kept.
Each build the version number is incremented at the last digit.
Updating the major / minor version indicates changes of the API / interfaces / contracts:
Major Version: A breaking change
Minor Version: A backwards-compatible minor change
Build / Revision: No API change, just a different build.
Semantic Versioning
Semantic Versioning is a versioning scheme specifying how to interpret the different version numbers. The most usual format is major.minor.patch
. The version number is incremented based on the following rules:
Major version when you make incompatible API changes,
Minor version when you add functionality in a backwards-compatible manner, and
Patch version when you make backwards-compatible bug fixes.
Examples of semver version numbers:
1.0.0-alpha.1: +1 commit after the alpha release of 1.0.0
2.1.0-beta: 2.1.0 in beta branch
2.4.2: 2.4.2 release
A common practice is to determine the version number during the build process. For this the source control repository is utilized to determine the version number automatically based the source code repository.
The GitVersion
tool uses the git history to generate repeatable and unique version number based on
number of commits since last major or minor release
commit messages
tags
branch names
Version updates happen through:
Commit messages or tags for Major / Minor / Revision updates.
Branch names (e.g. develop, release/..) for Alpha / Beta / RC
Otherwise: Number of commits (+12, ...)
Infrastructure Component Versioning
Modern cloud-native applications include infrastructure components that also need versioning. This includes:
Terraform modules
Kubernetes Helm charts
Azure Bicep modules
Container images
CloudFormation templates
When versioning infrastructure components, consider these best practices:
Immutable versioning: Once published, a specific version should never change
Version pinning: Always pin dependencies to specific versions to ensure reproducibility
Version compatibility matrix: Maintain documentation on compatible versions between components
Impact labeling: Tag versions with their potential impact (e.g.,
high-risk
,db-migration
)
Example Terraform module versioning:
Versioning in Multi-Cloud Environments
When working across multiple cloud providers, component versioning becomes even more critical. Consider these practices:
Cross-provider abstraction layers: Version your abstraction libraries independently from specific provider implementations
Provider-specific version tags: Use tags like
aws/v1.2.3
orazure/v1.2.3
for provider-specific releasesCompatibility matrices: Document which versions work with specific provider versions
Versioned provider configurations: Pin cloud provider versions in your IaC code
Example of version pinning in Terraform across providers:
Automated Versioning in CI/CD
Modern DevOps practices use automated versioning in CI/CD pipelines. Here are some implementation patterns:
GitHub Actions Example
Azure DevOps Pipeline Example
Calendar Versioning
Some projects use Calendar Versioning (CalVer) instead of Semantic Versioning. This approach uses dates in version numbers.
Common formats include:
YYYY.MM.DD
(e.g., 2025.06.01)YY.MM.MICRO
(e.g., 25.06.1)YYYY.MM
(e.g., 2025.06)
CalVer is particularly useful for:
Regular release cycles (monthly/quarterly)
Applications where "compatibility" isn't easily determined
Projects that follow time-based releases rather than feature-based releases
Example: Ubuntu's version numbers like 22.04
(Year 2022, April)
Version Constraint Operators
When declaring dependency versions, various constraint operators can be used:
=
= 1.2.3
Exactly version 1.2.3
!=
!= 1.2.3
Any version except 1.2.3
>
> 1.2.3
Any version greater than 1.2.3
>=
>= 1.2.3
Version 1.2.3 or greater
<
< 1.2.3
Any version less than 1.2.3
<=
<= 1.2.3
Version 1.2.3 or less
~>
~> 1.2.3
Any version between 1.2.3 and 1.3.0 (exclusive)
~>
~> 1.2
Any version between 1.2.0 and 2.0.0 (exclusive)
Tools and Automation
Beyond GitVersion, several other tools can help manage component versions:
Conventional Changelog - Generates changelogs based on commit messages
semantic-release - Fully automates package releases
commitizen - Interactive CLI for formatted commit messages
bump2version - Simple version bumping
Release Please - Google's release automation tool
Example semantic-release configuration (.releaserc
):
Best Practices Summary
Choose a versioning scheme that matches your release cadence and dependency model
Automate version generation in your CI/CD pipeline
Use explicit version pinning for all dependencies
Document version compatibility between components
Use git tags to mark releases in your repository
Include version information in build artifacts and deployments
Consider deployment impact in your versioning strategy
Maintain a changelog that maps versions to changes
Resources
Last updated