In today’s software development world, semantic versioning (SemVer) has become the go-to way of handling version numbers. Instead of random or confusing version bumps, SemVer provides a clear, predictable, and meaningful structure to versions helping teams instantly understand whether a release introduces a bug fix, a new feature, or a breaking change.
When you combine this with Azure DevOps pipelines, you unlock a powerful automation workflow. Every build, release, or deployment can automatically generate the right version number, attach it to your artifacts, and push it all the way through your environments. This makes your releases not just consistent, but also traceable and auditable.
Semantic Versioning (often called SemVer) uses a simple, three-part number format: MAJOR.MINOR.PATCH (for example, 2.1.3
). Each part of the version number has a specific meaning:
This structured approach provides clarity about what changed, predictability for upgrades, and automated dependency management.
As teams grow and deployments become more frequent, manually managing versions quickly turns into a challenge. Automated semantic versioning helps solve this problem by bringing structure and reliability to the process.
Automating semantic versioning in Azure DevOps Pipelines makes version management easier and more reliable. Instead of manually updating version numbers, the pipeline handles it for you — ensuring every build and release gets a clear, consistent, and meaningful version. By applying SemVer rules directly, your pipeline can automatically bump versions based on the type of changes made, making it simpler to track updates and keep your CI/CD process smooth and efficient.
0.0.0
Initial version
main
).steps:
- checkout: self
persistCredentials: true
- script: |
echo "Detecting latest valid Semantic Version tag..."
# Get valid semver tags only
tags=$(git tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V)
latestTag=$(echo "$tags" | tail -n 1)
if [ -z "$latestTag" ]; then
latestTag="0.0.0"
fi
echo "Latest tag: $latestTag"
version=$latestTag
IFS='.' read -r major minor patch <<< "$version"
branchName="$(Build.SourceBranch)"
echo "Current branch: $branchName"
if [[ "$branchName" == *"/fix/"* || "$branchName" == *"/bugfix/"* || "$branchName" == *"/hotfix/"* ]]; then
echo "Patch branch detected - incrementing patch version"
patch=$((patch + 1))
elif [[ "$branchName" == *"/feature/"* || "$branchName" == *"/feat/"* ]]; then
echo "Feature branch detected - incrementing minor version"
minor=$((minor + 1))
patch=0
elif [[ "$branchName" == *"/major/"* ]]; then
echo "Major branch detected - incrementing major version"
major=$((major + 1))
minor=0
patch=0
else
echo "No matching branch type found - defaulting to patch increment"
patch=$((patch + 1))
fi
newTag="$major.$minor.$patch"
echo "New version: $newTag"
echo "##vso[task.setvariable variable=ImageTag;isOutput=true]$newTag"
echo "Pushing tag $newTag..."
git tag "$newTag"
git push origin "$newTag"
displayName: "Generate Semantic Version Tag"
name: SetVersion
- stage: Versioning
displayName: "Generate Semantic Version"
jobs:
- job: TagVersion
displayName: "Compute Semantic Version"
steps:
- template: steps/semantic-version.yaml
parameters:
name: SetVersion
Now add the below line as a variable to use this tag as you Docker image tag or in any other steps of your pipeline.
variables:
IMAGE_TAG: $[stageDependencies.Versioning.TagVersion.outputs['SetVersion.ImageTag']]
NOTE: In above line IMAGE_TAG is the variable name, used to tag the Docker Image. You can change that as per you variable name.
The script uses branch names to determine version bumps:
PATCH
increment (e.g., 1.2.3 → 1.2.4
)MINOR
increment (e.g., 1.2.3 → 1.3.0
)MAJOR
increment (e.g., 1.2.3 → 2.0.0
)So, when creating branches, stick to these conventions.
After the pipeline runs, check the Tags section in your Azure Repo. You should see automatically created tags like 1.0.0
, 1.1.0
, 1.1.1
, etc.
The following Bash script is used in the Azure DevOps pipeline to automatically generate the next semantic version tag based on the branch name and here is the detailed Explanation of the the script.
tags=$(git tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V)
latestTag=$(echo "$tags" | tail -n 1)
if [ -z "$latestTag" ]; then
latestTag="0.0.0"
fi
echo "Latest tag: $latestTag"
git tag -l
→ lists all tags in the repository.grep -E '^[0-9]+\.[0-9]+\.[0-9]+$'
→ filters only valid SemVer tags (like 1.2.3
)sort -V
→ sorts them in version order (so 1.10.0
comes after 1.2.0
)tail -n 1
→ gets the latest version tag.0.0.0
.version=$latestTag
IFS='.' read -r major minor patch <<< "$version"
IFS='.'
→ sets the delimiter as .
read -r major minor patch
→ splits the version string into variables.1.2.3
→ major=1
, minor=2
, patch=3
.branchName="$(Build.SourceBranch)"
echo "Current branch: $branchName"
$(Build.SourceBranch)
→ Azure DevOps variable that contains the branch name (e.g., refs/heads/feature/add-login
).if [[ "$branchName" == *"/fix/"* || "$branchName" == *"/bugfix/"* || "$branchName" == *"/hotfix/"* ]]; then
echo "Patch branch detected - incrementing patch version"
patch=$((patch + 1))
elif [[ "$branchName" == *"/feature/"* || "$branchName" == *"/feat/"* ]]; then
echo "Feature branch detected - incrementing minor version"
minor=$((minor + 1))
patch=0
elif [[ "$branchName" == *"/major/"* ]]; then
echo "Major branch detected - incrementing major version"
major=$((major + 1))
minor=0
patch=0
else
echo "No matching branch type found - defaulting to patch increment"
patch=$((patch + 1))
fi
1.2.3 → 1.2.4
).1.2.3 → 1.3.0
).1.2.3 → 2.0.0
).newTag="$major.$minor.$patch"
echo "New version: $newTag"
git tag "$newTag"
git push origin "$newTag"
git tag "$newTag"
→ creates the new tag locally.git push origin "$newTag"
→ pushes it back to Azure Repos, making it available for everyone.In the end, adding semantic versioning to your Azure DevOps pipeline isn’t just about following a standard, it’s about making life easier for your team and anyone using your software. With clear version numbers that reflect the type of change, you remove guesswork, build trust in your releases and keep deployments smooth and predictable. It’s a small step in setup, but it pays off big in long-term clarity and confidence.
We use cookies to enhance your browsing experience, analyze traffic, and serve personalized marketing content. You can accept all cookies or manage your preferences.