Building a Modern Development Platform: Versioning Your Azure DevOps Pipelines π·οΈ
The Problem: Breaking Changes in Shared Templates π₯
Remember our pipeline templates post? Teams across your organization reference your template repository to keep their pipelines consistent and DRY.
But thereβs a critical issue:
Most teams point to the main branch:
resources:
repositories:
- repository: templates
type: git
name: YourProject/pipeline-templates
ref: refs/heads/main # β οΈ Always pulls latest
This works great until you make a breaking change to your templates:
- β You rename a parameter:
buildConfigurationβconfiguration - β You restructure stages: 3 stages becomes 2 stages
- β You change variable names:
appNameβprojectName - β Everyoneβs pipelines break immediately
The Breaking Change Scenario β
Imagine you share pipeline templates across 50 teams in your organization:
# Current template: stages/build.yml
parameters:
- name: buildConfiguration
type: string
default: 'Release'
You want to improve naming, so you rename the parameter:
# Updated template: stages/build.yml
parameters:
- name: configuration # β Changed from buildConfiguration
type: string
default: 'Release'
What happens at 3 AM:
- π¨ All 50 teamsβ pipelines start failing
- π± Teams canβt deploy because their pipelines reference
buildConfiguration - π Your team gets paged frantically
- βΉοΈ Production deployments are blocked
- π€ Teams lose trust in shared templates
Suddenly your entire organization is blocked, unable to deploy. Teams are scrambling to fix their pipelines. Production deployments are halted. All because you tried to improve your templates.
The Solution: Versioned Templates π―
With versioning, teams pin to specific versions using either release branches or specific tags:
Option 1: Release Branch (Recommended for automatic bug fixes)
resources:
repositories:
- repository: templates
type: git
name: YourProject/pipeline-templates
ref: refs/heads/releases/v1.latest # β
Branch: Always gets v1.x patches
Option 2: Specific Tag (For complete control)
resources:
repositories:
- repository: templates
type: git
name: YourProject/pipeline-templates
ref: refs/tags/v1.2.0 # β
Tag: Pinned to exact version
Now you can safely make breaking changes in a new major version:
- β v1.2.0 continues to work for existing teams (old parameter names, old structure)
- β v2.0.0 introduces breaking changes (new parameter names, new structure)
- β Teams upgrade on their schedule, at their pace
- β Your pipelines remain stable and predictable
How Automated Versioning Solves This βοΈ
An automated versioning pipeline creates a disciplined release process for your templates:
- β Generates semantic version numbers (MAJOR.MINOR.PATCH)
- π·οΈ Creates Git tags automatically
- πΏ Maintains release version branches
- π Provides version information to downstream pipelines
- π Prevents duplicate versions
- π Enables safe, controlled template changes
The Versioning Advantage β
With versioning, you release breaking changes in a new major version:
v1.2.0 (current - stable for everyone)
parameters:
- name: buildConfiguration # Original parameter name
type: string
v2.0.0 (new - breaking changes allowed)
parameters:
- name: configuration # Improved parameter name
type: string
Now:
- β Teams on v1.2.0 continue working without any changes
- β New teams can adopt v2.0.0 with modern parameter names
- β Teams upgrade at their own pace
- β No surprises, no emergency calls at 3 AM
- β Version history is clear: what changed, when, and why
Manual Versioning Problems β
Without automation:
- π Human errors lead to duplicate versions
- π Inconsistent version numbering across teams
- π€· Unclear when versions were created or who released them
- π Difficult to track which template version each project uses
- β οΈ Hard to communicate breaking changes to teams
Automated Versioning Benefits β
An automated pipeline solves these problems:
- β¨ Consistency: Same versioning strategy everywhere
- π Traceability: Git tags mark exactly whatβs in each version
- π Repeatability: Runs the same way every time
- π Transparency: Entire team sees version history
- β‘ Efficiency: No manual tagging or branch creation
- π‘οΈ Safety: Prevents duplicate versions and accidental overwrites
The Versioning Strategy π―
Semantic Versioning
We use Semantic Versioning (SemVer) format: MAJOR.MINOR.PATCH
v2.0.0
β β ββ PATCH: Bug fixes, internal improvements (no new features)
β ββββββ MINOR: New features, backward compatible
ββββββββ MAJOR: Breaking changes, significant changes
Examples:
v1.0.0 β Initial release
v1.1.0 β New feature added (backward compatible)
v1.1.1 β Bug fix
v2.0.0 β Breaking change, new major release
Components
Our versioning system consists of:
- Major version: Major release number
- Minor version: Feature releases
- Patch version: Bug fix releases
- Pre-release identifiers:
-alpha,-beta,-rc1for testing releases
Using Version Numbers in Other Pipelines π
Reference Pipeline Templates with Version Branch or Tag
When teams reference your shared pipeline templates, they have two safe options:
Option 1: Reference the Release Branch (Recommended)
Use the releases/vX.latest branch to get bug fixes automatically:
resources:
repositories:
- repository: templates
type: git
name: YourProject/pipeline-templates
ref: refs/heads/releases/v1.latest # β
Always v1.x.x with latest patches
trigger:
- main
extends:
template: stages/build.yml@templates
parameters:
appName: 'WeatherApp.Api'
buildConfiguration: 'Release'
projectPath: 'src/WeatherApp.Api'
Benefits:
- π Automatically gets bug fixes (v1.0.1, v1.0.2, v1.1.0)
- π‘οΈ Protected from breaking changes (v2.0.0 wonβt affect you)
- β‘ No manual version updates needed
Option 2: Reference a Specific Tag
Pin to an exact version for complete control:
resources:
repositories:
- repository: templates
type: git
name: YourProject/pipeline-templates
ref: refs/tags/v1.0.0 # β
Frozen at exactly v1.0.0
trigger:
- main
extends:
template: stages/build.yml@templates
parameters:
appName: 'WeatherApp.Api'
buildConfiguration: 'Release'
projectPath: 'src/WeatherApp.Api'
Benefits:
- π Completely frozen behavior - templates never change
- π Predictable for compliance/audit scenarios
- β οΈ Must manually update tags to get bug fixes
Comparison:
| Approach | Benefit | Trade-off |
|---|---|---|
refs/heads/releases/v1.latest |
Get bug fixes (v1.0.1, v1.1.0) automatically | Must trust template maintainers for PATCH/MINOR updates |
refs/tags/v1.0.0 |
Complete control, frozen behavior | Must manually update to get bug fixes |
refs/heads/main |
Always latest features | β Breaking changes could break you overnight |
Recommendation: Use releases/vX.latest for your major version. You get stability (no breaking changes) plus automatic bug fixes. Only use specific tags if you need complete control for compliance reasons.
Pipeline Architecture ποΈ
The versioning pipeline has two core files:
1. versioning.yaml - Configuration
This file holds the version number and is updated when you want to release a new version.
2. versioning-pipeline.yaml - Automation
This pipeline reads the version from versioning.yaml, creates Git tags, and manages release branches.
Setting Up the Versioning Pipeline π¦
Step 1: Create the Versioning Folder
Create a versioning/ directory in your repository root:
mkdir -p versioning
cd versioning
Step 2: Create versioning.yaml
Create versioning/versioning.yaml with your initial version:
variables:
# Version Components
major: 1
minor: 0
revision: 0
# Pre-release version support
preRelease: '' # Set to 'alpha', 'beta', 'rc1', etc. for pre-release versions or leave empty for stable
# Composite Version Numbers
version: '$(major).$(minor).$(revision)'
# Prerelease version uses same base version as stable
prereleaseVersion: '$(major).$(minor).$(revision)-$(preRelease)'
Version Components Explained:
| Component | Purpose | Example |
|---|---|---|
major |
Major version number | 1 in v1.0.0 |
minor |
Minor version number | 0 in v1.0.0 |
revision |
Patch version number | 0 in v1.0.0 |
preRelease |
Pre-release suffix | alpha, beta, rc1, or empty for stable |
version |
Full semantic version | 1.0.0 |
prereleaseVersion |
Version with pre-release suffix | 1.0.0-alpha or 1.0.0 if stable |
Step 3: Create versioning-pipeline.yaml
Create versioning-pipeline.yaml at the repository root:
trigger:
branches:
include:
- main
paths:
include:
- versioning/versioning.yaml
variables:
- template: versioning/versioning.yaml
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
persistCredentials: true
clean: true
- task: Bash@3
displayName: 'Setup Git Configuration'
inputs:
targetType: 'inline'
script: |
git config user.name "Azure DevOps Pipeline"
git config user.email "noreply@azuredevops.com"
- task: Bash@3
displayName: 'Prepare Version Variables'
inputs:
targetType: 'inline'
script: |
echo "Current version: $(version)"
echo "Prerelease version: $(prereleaseVersion)"
echo "Major version: $(major)"
echo "Minor version: $(minor)"
echo "Revision: $(revision)"
echo "Pre-release: '$(preRelease)'"
# Determine branch suffix and version tag based on prerelease
if [ -z "$(preRelease)" ]; then
BRANCH_SUFFIX="latest"
VERSION_TAG="v$(version)"
else
BRANCH_SUFFIX="$(preRelease)"
VERSION_TAG="v$(prereleaseVersion)"
fi
MAJOR_BRANCH="releases/v$(major).$BRANCH_SUFFIX"
echo "Version tag: $VERSION_TAG"
echo "Major branch: $MAJOR_BRANCH"
# Set variables for subsequent steps
echo "##vso[task.setvariable variable=versionTag]$VERSION_TAG"
echo "##vso[task.setvariable variable=majorBranch]$MAJOR_BRANCH"
echo "##vso[task.setvariable variable=branchSuffix]$BRANCH_SUFFIX"
- task: Bash@3
displayName: 'Check Versioning Requirements'
inputs:
targetType: 'inline'
script: |
VERSION_TAG="$(versionTag)"
echo "Target version: $VERSION_TAG"
if [ -z "$(preRelease)" ]; then
# For stable releases, check if tag exists
echo "Checking stable release tag: $VERSION_TAG"
if git tag -l | grep -q "^$VERSION_TAG$"; then
echo "##vso[task.logissue type=warning]Stable release tag $VERSION_TAG already exists! Skipping versioning."
echo "##vso[task.setvariable variable=shouldSkip]true"
echo "##vso[task.setvariable variable=skipReason]Stable tag $VERSION_TAG already exists"
else
echo "Tag $VERSION_TAG does not exist, proceeding with stable release..."
echo "##vso[task.setvariable variable=shouldSkip]false"
fi
else
# For prereleases, skip tag creation entirely
echo "Prerelease detected: $(preRelease)"
echo "Skipping tag creation for prerelease builds..."
echo "##vso[task.setvariable variable=shouldSkip]false"
fi
- task: Bash@3
displayName: 'Delete Existing Major Version Branch'
condition: and(succeeded(), ne(variables['shouldSkip'], 'true'))
inputs:
targetType: 'inline'
script: |
MAJOR_BRANCH="$(majorBranch)"
echo "Target branch to recreate: $MAJOR_BRANCH"
# Check if branch exists locally
if git branch --list | grep -q "$MAJOR_BRANCH"; then
echo "Deleting local branch: $MAJOR_BRANCH"
git branch -D "$MAJOR_BRANCH"
fi
# Check if branch exists on remote
if git ls-remote --heads origin "$MAJOR_BRANCH" | grep -q "$MAJOR_BRANCH"; then
echo "Deleting remote branch: $MAJOR_BRANCH"
git push origin --delete "$MAJOR_BRANCH"
else
echo "Remote branch $MAJOR_BRANCH does not exist"
fi
- task: Bash@3
displayName: 'Set Build Number'
condition: and(succeeded(), ne(variables['shouldSkip'], 'true'))
inputs:
targetType: 'inline'
script: |
VERSION_TAG="$(versionTag)"
# Remove 'v' prefix from tag for build number
BUILD_NUMBER_VALUE=$(echo "$VERSION_TAG" | sed 's/^v//')
echo "Setting build number to: $BUILD_NUMBER_VALUE"
echo "##vso[build.updatebuildnumber]$BUILD_NUMBER_VALUE"
- task: Bash@3
displayName: 'Create Version Tag'
condition: and(succeeded(), ne(variables['shouldSkip'], 'true'), eq(variables['preRelease'], ''))
inputs:
targetType: 'inline'
script: |
VERSION_TAG="$(versionTag)"
echo "Creating stable release tag: $VERSION_TAG"
TAG_MESSAGE="Version $(version) - Created by Azure DevOps Pipeline"
git tag -a "$VERSION_TAG" -m "$TAG_MESSAGE"
echo "Pushing tag to remote..."
git push origin "$VERSION_TAG"
echo "β Successfully created and pushed tag: $VERSION_TAG"
- task: Bash@3
displayName: 'Create Major Version Branch'
condition: and(succeeded(), ne(variables['shouldSkip'], 'true'))
inputs:
targetType: 'inline'
script: |
MAJOR_BRANCH="$(majorBranch)"
BRANCH_TYPE=$(echo "$(branchSuffix)")
if [ "$BRANCH_TYPE" = "latest" ]; then
echo "Creating stable release branch: $MAJOR_BRANCH"
else
echo "Creating prerelease branch: $MAJOR_BRANCH (prerelease: $BRANCH_TYPE)"
fi
git checkout -b "$MAJOR_BRANCH"
echo "Pushing branch to remote..."
git push -u origin "$MAJOR_BRANCH"
echo "β Successfully created and pushed branch: $MAJOR_BRANCH"
# Switch back to source branch
echo "Current branch before checkout: $(git branch --show-current)"
echo "Target branch: $(Build.SourceBranchName)"
if git show-ref --verify --quiet refs/heads/$(Build.SourceBranchName); then
echo "Switching back to branch: $(Build.SourceBranchName)"
git checkout $(Build.SourceBranchName)
else
echo "Branch $(Build.SourceBranchName) doesn't exist locally, staying on current branch"
echo "Available local branches:"
git branch --list
fi
- task: Bash@3
displayName: 'Version Summary'
inputs:
targetType: 'inline'
script: |
echo "=================================="
echo " VERSION SUMMARY "
echo "=================================="
if [ "$(shouldSkip)" = "true" ]; then
echo "β οΈ VERSIONING SKIPPED"
echo "Reason: $(skipReason)"
echo "Target Version: $(versionTag)"
echo "Target Branch: $(majorBranch)"
else
echo "β
VERSIONING COMPLETED"
echo "Created Branch: $(majorBranch)"
if [ "$(branchSuffix)" != "latest" ]; then
echo "Type: Pre-release ($(branchSuffix)) - No tag created"
else
echo "Type: Stable release"
echo "Created Tag: $(versionTag)"
fi
fi
echo ""
echo "Build Information:"
echo "Source Branch: $(Build.SourceBranchName)"
echo "Build Number: $(Build.BuildNumber)"
if [ -z "$(preRelease)" ]; then
echo "Original Version: v$(version)"
echo "Prerelease Version: N/A"
else
echo "Original Version: v$(version)"
echo "Prerelease Version: v$(prereleaseVersion)"
fi
echo "Final Version Used: $(versionTag)"
echo "=================================="
echo "Available tags:"
git tag --sort=-version:refname | head -10
echo ""
echo "Available branches matching releases/v*:"
git branch -r | grep "releases/v" | head -10 || echo "No release version branches found"
Understanding the Pipeline Steps π
Step 1: Git Configuration βοΈ
git config user.name "Azure DevOps Pipeline"
git config user.email "noreply@azuredevops.com"
Configures Git so the pipeline can commit and tag with proper identity.
Step 2: Prepare Version Variables π
# Determine if this is a stable or prerelease version
if [ -z "$(preRelease)" ]; then
BRANCH_SUFFIX="latest"
VERSION_TAG="v$(version)"
else
BRANCH_SUFFIX="$(preRelease)"
VERSION_TAG="v$(prereleaseVersion)"
fi
MAJOR_BRANCH="releases/v$(major).$BRANCH_SUFFIX"
Converts version numbers and pre-release status into variables for subsequent steps:
- π·οΈ Stable releases (empty preRelease): Create
releases/v1.latestbranch andv1.0.0tag - π§ͺ Pre-releases (alpha/beta): Create
releases/v1.alphabranch, no tag created - π¦ Sets
versionTag,majorBranch, andbranchSuffixfor other steps to use
Step 3: Check Versioning Requirements β
if [ -z "$(preRelease)" ]; then
# For stable releases, check if tag exists
if git tag -l | grep -q "^$VERSION_TAG$"; then
# Skip to prevent duplicate tags
shouldSkip=true
fi
else
# For prereleases, always proceed (no tag check needed)
shouldSkip=false
fi
Stable releases only: Prevents creating duplicate tags. If version already exists, skips remaining steps.
Pre-releases: Always proceeds (doesnβt create tags, so no duplicate risk).
Step 4: Delete Existing Major Version Branch π§Ή
git branch -D "$MAJOR_BRANCH"
git push origin --delete "$MAJOR_BRANCH"
Deletes the old releases/v1.latest or releases/v1.alpha branch so we can create a fresh one pointing to the current commit.
Why? These branches always point to the latest release of their type. When you release v1.1.0, the releases/v1.latest branch moves to this new commit.
Step 5: Set Build Number π
BUILD_NUMBER_VALUE=$(echo "$VERSION_TAG" | sed 's/^v//')
echo "##vso[build.updatebuildnumber]$BUILD_NUMBER_VALUE"
Updates the Azure DevOps build number to match your version (e.g., β1.0.0β instead of β12345β).
Step 6: Create Version Tag π·οΈ
# Only for stable releases (preRelease is empty)
git tag -a "$VERSION_TAG" -m "Version $(version)"
git push origin "$VERSION_TAG"
Stable releases only: Creates a Git tag marking this exact point in code.
Pre-releases: Skipped (set by condition: eq(variables['preRelease'], ''))
Step 7: Create Major Version Branch πΏ
git checkout -b "$MAJOR_BRANCH"
git push -u origin "$MAJOR_BRANCH"
Creates a branch for this release series:
- Stable:
releases/v1.latest- tracks all v1.x patch releases - Pre-release:
releases/v1.alpha- tracks pre-release builds
Step 8: Version Summary π
Displays what was created, distinguishing between stable and pre-release builds.
Workflow: How to Release a New Version π
Releasing v1.0.0 β v1.1.0 (Minor Release)
1. Update versioning.yaml:
variables:
major: 1
minor: 1 # β Incremented
revision: 0 # β Reset to 0
2. Commit and push:
git add versioning/versioning.yaml
git commit -m "chore: bump version to 1.1.0"
git push origin main
3. Pipeline automatically:
- β
Detects the change to
versioning/versioning.yaml - β
Creates tag
v1.1.0 - β
Creates/updates branch
releases/v1.latest - β Publishes results
Releasing v1.0.0 β v1.0.1 (Patch Release)
1. Update versioning.yaml:
variables:
major: 1
minor: 0
revision: 1 # β Incremented
2. Commit and push:
git add versioning/versioning.yaml
git commit -m "chore: bump version to 1.0.1"
git push origin main
Releasing v1.0.0 β v2.0.0 (Major Release)
1. Update versioning.yaml:
variables:
major: 2 # β Incremented
minor: 0 # β Reset to 0
revision: 0 # β Reset to 0
2. Commit and push:
git add versioning/versioning.yaml
git commit -m "chore: bump version to 2.0.0 (breaking changes)"
git push origin main
Using Version Numbers in Other Pipelines π
Reference Pipeline Templates with Version Branch or Tag
When teams reference your shared pipeline templates, they have two safe options:
Option 1: Reference the Release Branch (Recommended)
Use the releases/vX.latest branch to get bug fixes automatically:
resources:
repositories:
- repository: templates
type: git
name: YourProject/pipeline-templates
ref: refs/heads/releases/v1.latest # β
Always v1.x.x with latest patches
trigger:
- main
extends:
template: stages/build.yml@templates
parameters:
appName: 'WeatherApp.Api'
buildConfiguration: 'Release'
projectPath: 'src/WeatherApp.Api'
Benefits:
- π Automatically gets bug fixes (v1.0.1, v1.0.2, v1.1.0)
- π‘οΈ Protected from breaking changes (v2.0.0 wonβt affect you)
- β‘ No manual version updates needed
Option 2: Reference a Specific Tag
Pin to an exact version for complete control:
resources:
repositories:
- repository: templates
type: git
name: YourProject/pipeline-templates
ref: refs/tags/v1.0.0 # β
Frozen at exactly v1.0.0
trigger:
- main
extends:
template: stages/build.yml@templates
parameters:
appName: 'WeatherApp.Api'
buildConfiguration: 'Release'
projectPath: 'src/WeatherApp.Api'
Benefits:
- π Completely frozen behavior - templates never change
- π Predictable for compliance/audit scenarios
- β οΈ Must manually update tags to get bug fixes
Comparison:
| Approach | Benefit | Trade-off |
|---|---|---|
refs/heads/releases/v1.latest |
Get bug fixes (v1.0.1, v1.1.0) automatically | Must trust template maintainers for PATCH/MINOR updates |
refs/tags/v1.0.0 |
Complete control, frozen behavior | Must manually update to get bug fixes |
refs/heads/main |
Always latest features | β Breaking changes could break you overnight |
Recommendation: Use releases/vX.latest for your major version. You get stability (no breaking changes) plus automatic bug fixes. Only use specific tags if you need complete control for compliance reasons.
Pre-Release Versions π§ͺ
For testing releases before going to production, use pre-release identifiers:
variables:
major: 1
minor: 0
revision: 0
preRelease: 'alpha' # β Set for testing
prereleaseVersion: '$(major).$(minor).$(revision)-$(preRelease)'
This creates version 1.0.0-alpha:
- β
Pre-release tags like
v1.0.0-alpha,v1.0.0-beta,v1.0.0-rc1 - β
Final release uses empty
preReleasevalue - β Pre-release packages typically get lower priority in feeds
Workflow:
1.0.0-alpha β Testing with early adopters
1.0.0-beta β Feature complete, bug fixing
1.0.0-rc1 β Release candidate
1.0.0 β Final production release
Best Practices π―
1. Communicate Breaking Changes with Release Notes π’
When releasing a major version, always create release notes documenting what changed:
## v2.0.0 - Breaking Changes β οΈ
### Changed Parameters
- `buildConfiguration` β `configuration`
- `appPath` β `projectPath`
- `dockerRegistry` β `registryName`
### Removed Parameters
- `legacyBuildFormat` (use standardized format instead)
- `customScriptPath` (merged into build template)
### New Features
- Added support for multi-configuration builds
- Added native TypeScript build support
### Migration Guide
Update your azure-pipelines.yml:
```yaml
# OLD (v1.x)
ref: refs/heads/releases/v1.latest
# NEW (v2.0+)
ref: refs/heads/releases/v2.latest
Also update your parameter references:
# OLD
buildConfiguration: Release
# NEW
configuration: Release
Notify Teams
- π§ Send email with migration guide for breaking changes
- π¬ Post in team Slack channels
- π Create metrics dashboard showing which teams use which versions
- π― Set upgrade deadlines for unsupported versions
2. Provide Migration Path π€οΈ
Give teams clear steps to upgrade:
## Upgrading from v1.2.0 to v2.0.0
1. Review breaking changes above
2. Update your vars.yml with new parameter names
3. Update resource reference: `ref: refs/tags/v2.0.0`
4. Run a test pipeline build
5. If successful, merge to main
Estimated time: 15 minutes
3. Consider Deprecation Periods β³
For critical parameters, support both old and new names during a transition:
# Accept both old and new parameter names
parameters:
- name: configuration
type: string
- name: buildConfiguration # Deprecated alias
type: string
default: ''
# In your steps, use whichever is provided
- script: |
if [ -z "$" ]; then
CONFIG="$"
else
echo "β οΈ Warning: buildConfiguration is deprecated, use configuration instead"
CONFIG="$"
fi
This gives teams time to migrate without immediate breakage.
4. Commit Messages Should Reference Versions π
git commit -m "chore: bump version to 2.1.0 - new feature: X, bugfix: Y"
Makes it easy to understand what changed in each version.
5. Use Protected Branches π
Protect the main branch to prevent accidental version bumps:
- Require pull requests
- Require approvals
- Run all tests before merging
6. Tag After Testing β
Consider this workflow:
- Develop features on feature branches
- Merge to
mainwith pre-release version (e.g.,2.1.0-rc1) - Run full testing pipeline across multiple sample projects
- When tests pass, bump to final version (e.g.,
2.1.0) - Pipeline creates the official tag
- Notify teams of new version availability
7. Automate Version Bumping π
For advanced scenarios, consider automating version bumping based on commit messages:
- Commit with
[major]tag β bumps MAJOR version - Commit with
[minor]tag β bumps MINOR version - Commit with
[patch]tag β bumps PATCH version
This reduces manual steps and improves consistency.
Troubleshooting π§
Issue: βTag v2.0.0 already existsβ
Solution: This is intentional! The pipeline prevents duplicate versions. Options:
- Bump the version in
versioning.yamland try again - Delete the tag:
git tag -d v2.0.0 && git push origin :refs/tags/v2.0.0
Issue: βPermission deniedβ when pushing tag
Solution: Ensure the pipeline has permission to push:
- In Azure DevOps project settings β Pipelines β Settings
- Enable βMake secrets available to builds of forksβ
- Grant the pipeline identity push permissions to the repository
Issue: Version variables not available in other pipelines
Solution: Make sure other pipelines reference the versioning template:
variables:
- template: versioning/versioning.yaml
This must be at the pipeline root, not in stages.
Conclusion π
An automated versioning pipeline is a cornerstone of a mature platform. It ensures:
- β Consistency: Same versioning everywhere
- π Traceability: Git tags mark exact code for each version
- π Safety: Prevents duplicate versions and accidental overwrites
- β‘ Efficiency: No manual tagging or branch management
- π€ Transparency: Entire team sees version history
By implementing this versioning pipeline, your team gains confidence in releases and can easily roll back to any previous version if needed.
Key Takeaways:
- Store version numbers in a single
versioning.yamlfile - Use semantic versioning: MAJOR.MINOR.PATCH
- Automate tag creation and release branch management
- Reference version variables in all build pipelines
- Use pre-release identifiers for testing releases
Start versioning your platform today!
Resources: π