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
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.
# 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.
# 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
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>
...
Listing and Inspecting Tags
# 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:
# 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
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
# 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
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
| Part | When to increment | Example |
|---|---|---|
| MAJOR | Breaking API changes — existing code may break | v1.0.0 → v2.0.0 |
| MINOR | New features, backward compatible | v1.2.0 → v1.3.0 |
| PATCH | Bug fixes, backward compatible | v1.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.
# 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.0orgit 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
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.
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.
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.
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.