Git Flow and Release Tagging
Git Flow and Release Tagging
Before diving into pipelines and artifact promotion, you need to understand how source control branches and release tags create the foundation for traceability and safe deployments.
Why Git flow matters for DevSecOps
Your pipeline doesn't just deploy code — it deploys a specific snapshot of your repository with known properties:
A good Git flow strategy answers:
- Which commits are safe to deploy to production?
- Which commits are currently running in each environment?
- How do you roll back to the last known-good version?
- What changed between version 1.2.3 and 1.2.4?
Without a consistent branching and tagging strategy, these questions become difficult or impossible to answer.
Common Git flow strategies
GitHub Flow (simple, continuous deployment)
Rules:
mainbranch is always deployable- Feature branches merge to
mainvia pull request - Every merge to
maintriggers deployment - Tags mark releases
Best for:
- SaaS applications with continuous deployment
- Single production environment
- Small teams with fast iteration
Security consideration:
Every commit merged to main goes to production immediately. Quality and security gates must run before merge approval.
Git Flow (structured, multi-environment)
Rules:
mainbranch represents productiondevelopbranch is integration branch- Feature branches merge to
develop - Release branches cut from
develop, merge to bothmainanddevelop - Tags on
mainmark production releases - Hotfix branches cut from
main, merge to bothmainanddevelop
Best for:
- Enterprise applications with release schedules
- Multiple environments (dev, test, staging, prod)
- Teams that need time to stabilize releases
Security consideration:
The release branch is where you run final security validation before tagging and deploying to production.
Trunk-Based Development (very simple, requires strong CI)
Rules:
- Everyone commits to
main(or very short-lived branches <1 day) - Feature flags hide incomplete features
- Every commit must pass all quality and security gates
- Tags mark releases
Best for:
- Teams with very strong automated testing
- Continuous deployment culture
- Microservices with independent release cycles
Security consideration:
This requires exceptionally strong quality and security gates because there's no separate stabilization phase.
Semantic versioning (SemVer)
Tags should follow semantic versioning to communicate the nature of changes:
v<MAJOR>.<MINOR>.<PATCH>
Example: v1.2.3| Component | When to increment | Example |
|---|---|---|
| MAJOR | Breaking changes (API contract changes, removed features) | v1.5.2 → v2.0.0 |
| MINOR | New features (backward-compatible additions) | v1.5.2 → v1.6.0 |
| PATCH | Bug fixes (backward-compatible fixes) | v1.5.2 → v1.5.3 |
Why this matters for security:
- API consumers know when your changes might break their integrations (
MAJORbump) - Security patches should be
PATCHreleases, making them easy to adopt - OWASP ZAP scans can compare API schemas across
MINORversions to catch breaking changes
Example version history:
v1.0.0 - Initial release
v1.0.1 - Security patch: fix SQL injection in /api/users
v1.1.0 - New feature: add /api/auth/mfa endpoint
v1.1.1 - Bug fix: CORS headers on OPTIONS requests
v2.0.0 - BREAKING: remove deprecated /api/v1/* endpointsGit tags vs build numbers
You'll see two versioning schemes in DevSecOps pipelines:
Git tags (developer-controlled)
git tag -a v1.2.3 -m "Release 1.2.3: Add MFA support"
git push origin v1.2.3- Created manually by developers or release managers
- Follow semantic versioning
- Represent intended releases
- Permanent and immutable (don't delete production tags!)
Build numbers (pipeline-controlled)
# Azure Pipelines example
name: $(BuildID)
variables:
BUILD_NUMBER: $(Build.BuildId)
BUILD_VERSION: 1.2.3-build-$(Build.BuildId)- Auto-generated by CI/CD system
- Monotonically increasing (42, 43, 44...)
- Represent every pipeline run
- Used for internal traceability
Best practice: combine both
Artifact filename: backend-v1.2.3-build-456-abc123.zip
│ │ │
│ │ └─ Git commit SHA (first 7 chars)
│ └────────── Azure DevOps build number
└───────────────── Git tag (semantic version)This gives you:
- Human-readable version from Git tag (
v1.2.3) - Unique build identifier from pipeline (
456) - Exact source code from commit SHA (
abc123)
Tagging workflow for secure deployments
Here's a recommended workflow for Azure DevOps pipelines:
1. Feature development (no tags)
git checkout -b feature/add-zap-scanning
# ... make changes ...
git commit -m "feat: add OWASP ZAP baseline scan to pipeline"
git push origin feature/add-zap-scanningPipeline runs quality gates and security scans on the feature branch but does not deploy.
2. Merge to main/develop (no tags yet)
# Via pull request approval
git checkout main
git merge feature/add-zap-scanning
git push origin mainPipeline runs full validation and deploys to dev environment automatically.
3. Create release tag (triggers production deployment)
# After validating in dev/test
git tag -a v1.3.0 -m "Release 1.3.0: Add OWASP ZAP security scanning"
git push origin v1.3.0Pipeline detects the tag and:
- Builds artifact with version
v1.3.0 - Runs all security scans
- Deploys to test environment (requires approval)
- Deploys to production environment (requires approval)
4. Rollback (reuse existing tag)
# If v1.3.0 has a critical bug, deploy the previous tag
git checkout v1.2.9
# Or create a hotfix
git checkout -b hotfix/1.3.1 v1.3.0
# ... fix the bug ...
git commit -m "fix: resolve authentication bypass in /api/auth"
git tag -a v1.3.1 -m "Hotfix 1.3.1: Security patch for auth bypass"
git push origin v1.3.1Traceability chain with Git
Every production deployment should have a complete traceability chain:
Questions this traceability chain answers:
| Question | Answer from... |
|---|---|
| What code is running in production? | Git commit SHA in deployment metadata |
| When was it deployed? | Deployment timestamp in Azure DevOps |
| Did it pass security scans? | Semgrep/Trivy/Checkov results linked to build |
| What changed since the last release? | git log v1.2.2..v1.2.3 |
| Who approved the production deployment? | Azure DevOps approval logs |
| Can I reproduce this build? | Git tag + build number + artifact storage |
Azure Pipelines trigger patterns
Your pipeline YAML can trigger on different Git events:
Trigger on branch commits
# Run pipeline on every commit to main or develop
trigger:
branches:
include:
- main
- developUse case: Deploy to dev environment automatically on merge.
Trigger on tags
# Run pipeline only when a tag matching v*.*.* is pushed
trigger:
tags:
include:
- v*.*.*Use case: Deploy to production only when a release tag is created.
Trigger on both
trigger:
branches:
include:
- main
tags:
include:
- v*.*.*
# Use conditional logic in pipeline stages
stages:
- stage: DeployDev
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
- stage: DeployProd
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))Use case: Single pipeline handles both continuous deployment (dev) and release deployment (prod).
Protected tags and branch policies
In production systems, you should protect your release process:
Azure DevOps branch policies (on main branch)
- Require pull request reviews (minimum 1 reviewer)
- Require build validation (quality + security gates must pass)
- Require linked work items (traceability to requirements)
- Enforce merge strategy (squash or merge commit, not rebase)
Git tag protection (manual enforcement)
Since Git tags are immutable:
- Only release managers create tags (controlled via repository permissions)
- Never delete tags used in production
- Never force-push to
mainbranch (corrupts history) - Sign tags with GPG for authenticity (optional, advanced)
# Signed tag with GPG (proves tag creator identity)
git tag -s v1.2.3 -m "Release 1.2.3: Security hardening"
git push origin v1.2.3Common mistakes and risks
Reusing tag names
# WRONG: Delete and recreate tag
git tag -d v1.2.3
git push origin :refs/tags/v1.2.3
git tag v1.2.3
git push origin v1.2.3Risk: Production deployment points to v1.2.3, but the code changed. Rollback becomes impossible.
Fix: Increment to v1.2.4 instead.
Building from branch instead of tag
# WRONG: Deploy production from main branch
- stage: DeployProd
jobs:
- job: Deploy
steps:
- checkout: self
ref: main # ← This branch can change!Risk: Production deployment might get different code than what was tested if someone pushes to main during the deployment.
Fix: Deploy from immutable Git tags or specific commit SHAs.
Not embedding version in artifact
# WRONG: Generic artifact name
backend-app.zipRisk: Artifact storage has 10 copies of backend-app.zip. Which one is in production?
Fix: Include version and build number in filename.
# CORRECT
backend-v1.2.3-build-456-abc1234.zipDeploying without traceability metadata
If your App Service has no environment variables like:
APP_VERSION=v1.2.3
BUILD_NUMBER=456
GIT_COMMIT=abc1234567890Then:
- Your
/api/healthendpoint can't report what version is running - Security incident response teams can't determine which code is deployed
- Log correlation becomes difficult
Fix: Embed version metadata in every artifact and deployment (you'll do this in Lab 3.6).
Summary
| Concept | Purpose | Example |
|---|---|---|
| Git flow strategy | Define which branches are safe to deploy | GitHub Flow, Git Flow, Trunk-Based |
| Semantic versioning | Communicate change impact | v1.2.3 (major.minor.patch) |
| Git tags | Mark specific commits as releases | git tag -a v1.2.3 |
| Build numbers | Uniquely identify pipeline runs | Azure DevOps build #456 |
| Artifact naming | Embed traceability in filename | backend-v1.2.3-build-456-abc1234.zip |
| Traceability chain | Answer "what's in prod?" | Commit → Tag → Build → Scans → Artifact → Deployment |
| Branch policies | Enforce quality/security gates | Require PR approval + passing tests |
| Protected tags | Prevent release tampering | Repository permissions, signed tags |