Skip to main content

Tags — Marking Releases

What is a Tag?

A tag is an immutable pointer to a specific commit — unlike a branch, it never moves. Tags are used to mark release points (v1.2.0), milestones, or any commit you want to reference permanently.

There are two types of tags:

TypeDescription
LightweightJust a pointer to a commit — like a branch that never moves
AnnotatedA full Git object with a message, tagger name, email, date, and optional GPG signature. Recommended for releases.

Creating Tags

git tag -a v1.2.0 -m "Release 1.2.0 — transaction export feature"

Lightweight tag (simple bookmark)

git tag v1.2.0-rc1

Tag a specific past commit

git tag -a v1.1.5 a3f9bc2 -m "Retroactive tag for 1.1.5 hotfix"

Listing and Inspecting Tags

# List all tags
git tag

# List tags matching a pattern
git tag -l "v1.2.*"
git tag -l "v1.*"

# Show tag details (annotated tags show the full object)
git show v1.2.0

# List tags with their commit messages (annotated only)
git tag -n # show first line of each tag message
git tag -n5 # show up to 5 lines

Pushing Tags to Remote

Tags are not pushed automatically with git push:

# Push a specific tag
git push origin v1.2.0

# Push all tags
git push --tags

# Push all annotated tags only (recommended — excludes lightweight tags)
git push origin 'refs/tags/v*'

Deleting Tags

# Delete a local tag
git tag -d v1.2.0-rc1

# Delete a remote tag
git push origin --delete v1.2.0-rc1
# or:
git push origin :refs/tags/v1.2.0-rc1

Checking Out a Tag

Tags are not branches — you cannot commit on them. Checking out a tag puts you in detached HEAD state:

git checkout v1.2.0
# HEAD is now at a3f9bc2... (detached HEAD)

# To work from a tag, create a branch from it
git switch -c hotfix/JIRA-999 v1.2.0

Semantic Versioning

Follow Semantic Versioning for release tags:

v<MAJOR>.<MINOR>.<PATCH>[-<pre-release>][+<build>]

v1.0.0 ← initial stable release
v1.1.0 ← new feature, backward compatible
v1.1.1 ← bug fix
v1.2.0-rc.1 ← release candidate
v2.0.0 ← breaking changes
SegmentIncrement when
MAJORBreaking changes — not backward compatible
MINORNew features, backward compatible
PATCHBug fixes, backward compatible

Signed Tags (GPG)

For high-security release pipelines, sign tags with GPG:

# Create a signed tag
git tag -s v1.2.0 -m "Release 1.2.0"

# Verify a signed tag
git tag -v v1.2.0

Release Automation with Tags

Use tags to trigger CI/CD release pipelines:

# .github/workflows/release.yml
name: Release

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # matches v1.2.0, v2.0.0, etc.

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Get tag version
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

- name: Build and publish
run: |
mvn versions:set -DnewVersion=${{ steps.version.outputs.VERSION }}
mvn deploy -B

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
name: Release ${{ steps.version.outputs.VERSION }}
generate_release_notes: true

Tag on Main, Never on Feature Branches

Always create release tags on main (or your release branch) after merging and verifying the release build. Tagging on a feature branch creates a tag that points to an unmerged commit, which is confusing and can lead to incorrect releases.