Ad – 728×90
⚙️ Advanced Git

Git Tags & Releases – Marking Versions with git tag

While branches move with every commit, a tag is a permanent bookmark that never moves — perfect for marking release versions. Tags are how you tell the world "this commit is v1.2.0". This lesson covers creating lightweight and annotated tags, pushing them to GitHub, deleting stale tags, and the semantic versioning convention used by most professional projects.

⏱️ 15 min read 🎯 Intermediate 📅 Updated 2026

What Are Tags?

Tags are references that point to specific commits in your Git history. Unlike branches (which advance with each new commit), tags are fixed — they always point to the same commit. This makes them ideal for marking release points:

  • Stable release: v1.0.0, v2.3.1
  • Pre-release: v2.0.0-beta.1, v1.5.0-rc.2
  • Internal milestones: sprint-42-end
ℹ️
Tags vs Branches

A branch pointer moves forward with every new commit. A tag pointer never moves — it permanently marks one specific commit. If you checkout a tag, you enter a "detached HEAD" state because there's no branch to advance.

Lightweight Tags

A lightweight tag is simply a named pointer to a commit — nothing more. It stores no metadata about who created the tag or why.

Bash
# Create a lightweight tag on the current HEAD
git tag v1.0.0

# Create a lightweight tag on a specific past commit
git tag v0.9.0 3f2a1b9

# Show the commit a lightweight tag points to
git show v1.0.0

Lightweight tags are useful for temporary or personal bookmarks but are generally not recommended for public releases — use annotated tags for those.

Annotated Tags

Annotated tags are full Git objects. They store: the tagger name and email, the tagging date, a message, and optionally a GPG signature. These are the standard for release tags.

Bash
# Create an annotated tag with a message
git tag -a v1.0.0 -m "Release v1.0.0 – initial public release"

# Create an annotated tag on a specific past commit
git tag -a v0.9.0 3f2a1b9 -m "Beta release v0.9.0"

# View full tag details (tagger, date, message, commit info)
git show v1.0.0
▶ git show v1.0.0 output
tag v1.0.0
Tagger: Jane Dev <jane@example.com>
Date: Mon Jun 8 14:30:00 2026 +0000

Release v1.0.0 – initial public release

commit a1b2c3d4e5f6... (HEAD -> main)
Author: Jane Dev <jane@example.com>
...
Ad – 336×280

Listing and Inspecting Tags

Bash
# List all tags (alphabetically)
git tag

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

# List tags with their associated commit messages
git tag -n

# Show full details of a specific tag
git show v2.0.0

Pushing Tags to Remote

Tags are NOT pushed automatically with git push. You must push them explicitly:

Bash
# Push a single tag to origin
git push origin v1.0.0

# Push ALL local tags to origin at once
git push origin --tags

# Push all tags along with a regular push
git push --follow-tags   # Only pushes annotated tags reachable from pushed commits
💡
Prefer --follow-tags over --tags

Use git push --follow-tags instead of --tags in CI/CD pipelines. It only pushes annotated tags that are reachable from the commits being pushed, avoiding accidentally pushing local test/temp tags.

Deleting Tags

Bash
# Delete a local tag
git tag -d v1.0.0-beta

# Delete a remote tag
git push origin --delete v1.0.0-beta

# Alternative syntax for deleting remote tag
git push origin :refs/tags/v1.0.0-beta
⚠️
Avoid deleting published release tags

Once a tag has been pushed and others may have pulled it (e.g., in open source projects), deleting it causes confusion. If you tagged the wrong commit, create a new corrected tag (e.g., v1.0.1) and document what happened, rather than deleting and recreating the original tag.

Semantic Versioning

The convention used by almost all software projects is Semantic Versioning (SemVer): MAJOR.MINOR.PATCH

PartWhen to incrementExample
MAJORBreaking API changes — existing code may breakv1.0.0 → v2.0.0
MINORNew features, backward compatiblev1.2.0 → v1.3.0
PATCHBug fixes, backward compatiblev1.2.3 → v1.2.4

Pre-release versions use a hyphen suffix: v2.0.0-alpha.1, v2.0.0-beta.3, v2.0.0-rc.1. Starting from v0.y.z signals initial development — anything may change.

Bash
# Typical release tagging workflow
git switch main
git pull origin main

# Create the annotated release tag
git tag -a v1.3.0 -m "Release v1.3.0

- Add OAuth2 login support
- Fix null pointer in user profile
- Improve dashboard load time by 40%"

# Push the tag to trigger CI/CD release pipeline
git push origin v1.3.0

On GitHub, navigate to Releases → Draft a new release → Choose a tag. Select your tag, add release notes, attach binaries if needed, and publish. GitHub automatically generates a downloadable archive of the source code for each tag.

📋 Summary

  • Tags are permanent commit bookmarks — unlike branches, they never move.
  • Lightweight tags (git tag v1.0.0) are just pointers — no extra metadata.
  • Annotated tags (git tag -a v1.0.0 -m "msg") store tagger, date, and message — use these for releases.
  • Tag a past commit with: git tag -a v0.9.0 <commit-hash>
  • Tags are not pushed automatically — use git push origin v1.0.0 or git push --follow-tags.
  • Delete locally: git tag -d v1.0.0. Delete remotely: git push origin --delete v1.0.0.
  • Semantic versioning: MAJOR (breaking) . MINOR (new features) . PATCH (bug fixes).

FAQ

Should I use lightweight or annotated tags for releases? +

Always use annotated tags for releases. They store who created the tag, when, and why — invaluable when reviewing release history months later. Lightweight tags are useful for temporary personal bookmarks (e.g., marking a commit before a risky rebase) but lack the provenance information that release tags need.

Why didn't my tag show up on GitHub after git push? +

Tags are not pushed with a regular git push. You must push them explicitly: git push origin v1.0.0 for a single tag, or git push origin --tags for all tags. Many teams configure their CI/CD pipeline to push tags automatically when the release process is triggered, rather than relying on developers to remember.

What happens if I checkout a tag? +

You enter a "detached HEAD" state — your HEAD points directly to a commit rather than to a branch. This is fine for reading code or building from a specific version. If you want to make changes from a tagged commit, create a branch first: git switch -c hotfix/1.0.1 v1.0.0. Any commits made in detached HEAD state without first creating a branch will eventually be garbage collected by Git.

Can I move a tag to a different commit? +

You can force-update a local tag with git tag -f v1.0.0 <new-commit>, then force-push with git push origin --force v1.0.0. However, this is strongly discouraged for published tags because anyone who already fetched the tag will have a different reference than those who fetch after the change. It's better to create a new tag (e.g., v1.0.1) and document the correction.