What are Git tags and when to use them?
Git tags are fixed references to specific commits, used to mark release points like v1.0.0.
Theory
TL;DR
- Tags are like a sticky note on a commit: they stay on that exact spot forever, unlike branches that move forward with every new commit
- Two types: lightweight (just a pointer to a commit) and annotated (a full Git object with message, author, date)
- Annotated tags are for releases; lightweight tags are for quick local bookmarks
- Tags are not pushed automatically - you have to push them explicitly
Quick example
# Lightweight tag (just a pointer)
git tag v1.0
# Annotated tag (full object with metadata)
git tag -a v1.1.0 -m "Stable release v1.1.0"
# List all tags
git tag
# v1.0
# v1.1.0
# Push a specific tag to remote
git push origin v1.1.0After git tag -a, Git creates a tag object storing who tagged it, when, and why. Lightweight tags skip all that and just point to a commit SHA.
Key difference: tags vs branches
A branch pointer moves forward every time you commit. HEAD on main today is not the same commit as HEAD on main last week. A tag never moves. v1.0.0 points to the same commit in six months as it does today. That immutability is exactly what makes tags useful for releases - you can always get back to exactly what shipped.
When to use tags
- Marking releases: annotated tags for versions like
v2.0.0with a message describing what changed - Hotfixes on old versions: tag the base commit before creating a hotfix branch (
git checkout -b hotfix v1.0.0) - CI/CD triggers: push a tag to kick off a production deploy pipeline
- Local bookmarks: lightweight tags work fine for "I need to come back here" moments during development
Do not tag work-in-progress. That is what branches are for.
How it works internally
Git stores tags in .git/refs/tags/. A lightweight tag is just a file containing the commit SHA. An annotated tag creates a separate tag object in the Git database with its own SHA, which then points to the commit. When you run git checkout v1.0.0, Git puts you in detached HEAD state - HEAD points directly to the commit hash, not to any branch name.
Common mistakes
Forgetting to push tags. Running git push after creating a tag does nothing for the tag. Your teammates pull and see no tags at all.
# Wrong
git tag v1.0.0
git push # tag stays local only
# Right
git push origin v1.0.0
# or push all local tags at once
git push --tagsCommitting in detached HEAD after a tag checkout. If you check out a tag and start committing, those commits belong to no branch. Switch away and they are gone.
# Wrong
git checkout v1.0.0
# ... make changes, commit ...
git checkout main # commits are now dangling
# Right
git checkout -b hotfix-v1.0.0 v1.0.0
# now you are on a branch, commits are safeUsing lightweight tags for releases. A lightweight tag has no message, no author, no date. git show v1.0 just dumps the raw commit. Annotated tags give you context that is searchable and auditable. Teams that use lightweight tags for releases usually regret it six months later when they can not tell who tagged what or why.
Deleting a shared tag. Local delete (git tag -d v1.0.0) does not remove the tag from the remote. Remote delete (git push origin :refs/tags/v1.0.0) breaks CI pipelines and anyone who pinned to that tag. Coordinate with the team before touching published tags.
Real-world usage
- npm:
npm version patchautomatically creates av1.2.3commit and tag in one step - React on GitHub: tags like
v18.2.0mark the exact commit before each npm publish - Linux kernel: tags like
v6.1andv6.2mark merge windows for downstream distro builds - Docker Hub: a tag push can trigger an image build automatically
semantic-release: reads existing tags to compute the next version number
Follow-up questions
Q: What is the difference between a lightweight and an annotated tag?
A: A lightweight tag is a file containing a commit SHA. An annotated tag is a Git object with its own SHA, containing the tagger name, email, date, and message. Use git tag -a for anything shared or release-related.
Q: How do you push all tags at once?
A: git push --tags pushes all local tags. git push origin --follow-tags (Git 2.16+) pushes only annotated tags reachable from the current branch, which is the safer default for most workflows.
Q: What is detached HEAD when checking out a tag?
A: HEAD points directly to a commit SHA instead of a branch name. Any commits you make from that state belong to no branch and can be lost when you switch away.
Q: Can you move a tag to a different commit?
A: Locally: git tag -d v1.0.0 then recreate it. On a remote: delete with git push origin :refs/tags/v1.0.0, then push again. Both are disruptive on shared repos. Avoid it on anything already published.
Q: In a monorepo with dozens of packages, how do you tag individual package releases without tagging the whole repo?
A: Tools like Changesets or Lerna handle this. They create per-package tags in the format @org/package@v1.0.0 and run the tagging logic in CI, so you never manually tag individual packages.
Examples
Basic: Creating and inspecting an annotated tag
git checkout main
git pull origin main
# Create annotated tag for a release
git tag -a v2.0.0 -m "Major update: new auth system"
# Inspect the tag object
git show v2.0.0
# tag v2.0.0
# Tagger: Jane Dev <jane@example.com>
# Date: Mon Oct 14 11:30:00 2024
# Major update: new auth system
#
# commit abc1234...
# Push the tag to remote
git push origin v2.0.0The git show output includes tagger metadata you do not get from a lightweight tag. That matters when you are debugging what exactly shipped and who created the release.
Intermediate: Tag-triggered deploy in a Node.js project
# Typical Express/Node workflow
git checkout main
git pull origin main
# run tests first
# npm version creates a commit + tag automatically
npm version 4.18.2 -m "Release Express 4.18.2"
# creates tag v4.18.2
# Push the commit and the tag together
git push origin main --follow-tags
# In your CI config (GitHub Actions):
# on:
# push:
# tags:
# - 'v*'
# The deploy job only runs when a version tag is pushedThe --follow-tags flag pushes the branch commit and any reachable annotated tags in one shot. No separate step to remember, no accidentally leaving the tag local while the code is already on the remote.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.