What Is Git Tag? Marking Versions Made Easy

Summarize this article with:
Every Git repository has commits flying in daily. But without a way to say “this exact commit is version 2.0,” your release history is just a wall of SHA hashes. That’s what a git tag solves.
A git tag creates a permanent, human-readable label on a specific commit. It’s how teams mark stable releases, trigger deployment pipelines, and keep their version control history clean.
This guide covers everything about git tags: the difference between lightweight and annotated tags, how to create and push them, how they connect to semantic versioning, and the common mistakes that trip up even experienced developers. Whether you’re shipping your first release or managing tags across a large team, you’ll find what you need here.
What is a Git Tag

A git tag is a named reference that points to a specific commit in a Git repository. Think of it as a sticky note you slap on one exact moment in your project’s history that says “this is version 2.0” or “this is the build we shipped on Tuesday.”
Unlike a branch, which moves forward every time you make a new commit, a tag stays put. It locks onto one commit and doesn’t budge.
That’s the whole point. Tags give you a fixed, human-readable label for a specific state of your codebase. Most teams use them to mark release points like v1.0, v2.3.1, or v3.0.0-beta.1.
Internally, Git stores tags inside the .git/refs/tags directory. Each tag file contains the SHA-1 hash of the commit (or tag object) it references. When you run git show v1.0, Git resolves that reference and shows you exactly what was happening in the repository at that snapshot.
GitHub takes this a step further by building its Releases feature directly on top of Git tags. When you push a tag to a GitHub remote, you can attach release notes, changelogs, and downloadable binary assets to it.
Git adoption has climbed from 87.1% in 2016 to over 93% in 2025, according to Command Linux research. With more than 100 million developers now on GitHub alone (Statista, 2023), tags have become the standard mechanism for marking stable release points across virtually every team and open-source project.
A Hutte survey found that 85% of developers say Git has improved collaboration within their teams. Tags play a quiet but significant role in that, since they give everyone on the team (and every CI/CD pipeline) a shared reference point that never drifts.
Lightweight Tags vs Annotated Tags

Git gives you two types of tags. They look similar from the outside, but they store very different amounts of information.
What is a Lightweight Tag
A lightweight tag is just a pointer. Nothing else.
It’s a file inside .git/refs/tags/ that contains a commit hash. No author, no date, no message. Run git tag v1.0-test and Git creates that pointer in under a millisecond.
Lightweight tags work well for temporary markers, personal bookmarks, or quick labels during local development. But they carry no metadata, so they’re a poor fit for anything you plan to share with a team or push to a remote repository as an official release.
What is an Annotated Tag
Annotated tags are full Git objects. They get stored in the Git object database with their own SHA-1 hash, separate from the commit they point to.
Each annotated tag records the tagger’s name, email, timestamp, and a message. You can also sign them with GPG or SSH for cryptographic verification. The command looks like this:
git tag -a v2.0.0 -m "Production release with new payment API"
When you run git show v2.0.0 on an annotated tag, you see all that metadata before the commit details. That extra context matters when you’re trying to figure out who tagged a release and why, six months after the fact.
When to Use Each Type
| Criteria | Lightweight | Annotated |
|---|---|---|
| Metadata stored | None | Author, date, message |
| GPG/SSH signing | Not supported | Fully supported |
| Best for | Local bookmarks, temp markers | Releases, shared milestones |
Pushed with --follow-tags | No | Yes |
The general rule most teams follow: annotated tags for anything public, lightweight tags for anything personal.
Google’s Go module system, npm, and most package managers rely on annotated tags for version resolution. If you’re publishing software that other people depend on, lightweight tags can cause unexpected behavior in dependency graphs.
How to Create a Git Tag

Creating tags is one of the fastest operations in Git. But the syntax changes depending on what kind of tag you want.
Creating a Lightweight Tag
git tag v1.0.0
That’s it. One command, no flags. Git creates a reference to your current HEAD commit. No editor opens, no prompt for a message.
If you want to tag a specific older commit instead of HEAD, pass the hash:
git tag v0.9.0 a1b2c3d
Creating an Annotated Tag
The -a flag tells Git to create a full tag object. The -m flag lets you add a message inline:
git tag -a v2.1.0 -m "Added OAuth2 support and rate limiting"
Skip the -m flag and Git opens your default editor (usually Vim or whatever you’ve set in git config) so you can write a longer, multi-line message. Took me way too long to realize that’s actually useful for release notes that need more detail than a single sentence.
Tagging a Previous Commit
This comes up constantly. You finish a sprint, merge everything, deploy… and then realize nobody tagged the release commit.
First, find the commit you need using git log:
git log --oneline -10
Then tag it directly:
git tag -a v1.3.0 9fceb02 -m "Q3 release - retroactive tag"
The commit hash goes right after the tag name. Works the same for both lightweight and annotated tags.
Tag Naming Conventions
Most teams follow semantic versioning: v{MAJOR}.{MINOR}.{PATCH}
The “v” prefix (v1.0.0 vs 1.0.0) is a convention, not a Git requirement. Some ecosystems expect it (Go modules), others don’t care. Pick one format and stick with it across your project.
Pre-release tags like v2.0.0-beta.1 or v3.0.0-rc.2 follow the SemVer pre-release spec. These signal instability and sort correctly when you list tags with git tag --sort=version:refname.
How to List and View Git Tags

Once a repository has dozens (or hundreds) of tags, you need ways to find and inspect them quickly.
Listing All Tags
Basic list: git tag
This outputs every tag in alphabetical order. Fine for small projects. Completely useless on a repo like the Linux kernel, which has thousands of tags going back to 2005.
Filtering Tags by Pattern
The -l flag accepts glob patterns:
git tag -l "v2."shows all v2.x releasesgit tag -l "beta"finds every pre-release beta taggit tag -l "v1.8."narrows down to a specific minor version series
Sorting Tags by Version
Alphabetical sorting breaks with double-digit versions. v1.10.0 sorts before v1.9.0 alphabetically, which is wrong.
Fix it with:
git tag --sort=version:refname
Or set it globally so you never think about it again:
git config --global tag.sort version:refname
Viewing Tag Details
git show v2.1.0 displays the tag object (for annotated tags) plus the commit it points to, including the diff.
If you only need the commit hash a tag references, git rev-parse v2.1.0 is faster. And git describe tells you where you are relative to the nearest tag, which is handy inside build pipelines when you need to generate version strings automatically.
How to Push Git Tags to a Remote Repository

Here’s something that trips up almost everyone the first time: tags don’t push automatically.
You can run git push origin main all day long. Your tags stay local. Git’s default push behavior deliberately excludes tags, and there’s a good reason for it. Tags are supposed to be stable release markers, not things that get accidentally synced.
Pushing a Single Tag
git push origin v2.1.0
Straightforward. This sends one specific tag to the remote. Use this when you’ve just finished a release and want to share that single marker with the team.
Pushing All Tags at Once
git push origin --tags
Be careful with this one. It pushes every local tag, including lightweight tags, old test markers, and anything else you might have lying around. On a messy local repo, this can pollute the remote with junk tags that are hard to clean up later.
The Safer Alternative
git push origin --follow-tags
This only pushes annotated tags that are reachable from the commits being pushed. Lightweight tags get left behind. It’s the option most DevOps teams actually want to use, and you can set it as default:
git config --global push.followTags true
How Tags Work with Hosting Platforms
Once a tag lands on a remote, each hosting platform handles it slightly differently.
GitHub automatically creates a downloadable source archive for every tag and lets you build a full Release around it with notes and binary assets. GitHub Actions can trigger workflows on tag pushes using on: push: tags: ['v'], which is one of the most common patterns for automated releases. GitHub now processes over 71 million Actions jobs per day (GitHub Blog, 2025).
GitLab CI/CD also supports tag-triggered pipelines, and Bitbucket has its own tag-based deployment rules. The pattern is the same everywhere: push a tag, trigger a deployment pipeline.
The continuous integration tools market was valued at $1.35 billion in 2024 and is projected to reach $6.11 billion by 2033, growing at 18.22% CAGR (Straits Research). Tag-based release automation is a core driver of that growth.
How to Delete and Replace Git Tags

Mistakes happen. You tag the wrong commit, use a typo in the tag name, or realize the build was broken after you already pushed v2.0.0. Fixing it requires deleting and (sometimes) recreating tags.
Deleting a Local Tag
git tag -d v2.0.0
Done. The local reference is gone. Your commit history is untouched because tags are just pointers, not part of the commit graph itself.
Deleting a Remote Tag
Two ways to do this. The newer syntax:
git push origin --delete v2.0.0
Or the older (but still widely used) ref-spec approach:
git push origin :refs/tags/v2.0.0
Both achieve the same result. The tag disappears from the remote. But here’s the problem.
Why Replacing Tags is Risky
Anyone who already pulled the old tag still has it locally. Git doesn’t auto-update tags on fetch. If a teammate pulled v2.0.0 pointing to commit abc123, and you delete it and recreate it pointing to def456, they’ll have a stale reference until they manually clean it up with:
git fetch origin --prune --prune-tags
Most developers don’t run that regularly. So now you have two people on the team with different ideas of what v2.0.0 means. That’s a real headache during a rollback.
The Better Approach
Instead of moving a tag, create a new patch version. If v2.0.0 had a bug, ship v2.0.1. Your software release cycle stays clean and nobody ends up with conflicting references.
Linus Torvalds, who created both Git and the Linux kernel, designed tags to be stable anchors. The Linux kernel project has over 2,134 active contributors per release (Linux kernel 6.18 data), and retagging would cause chaos at that scale. The same principle applies to any team with more than one person.
Git Tags and Semantic Versioning

Tags and semantic versioning go together like commits and branches. One gives you the naming structure, the other gives you the fixed reference point in your repository history.
The SemVer format is MAJOR.MINOR.PATCH. A tag named v2.4.1 tells anyone looking at it that this is a patch fix on the second major version’s fourth minor release. No guessing required.
How SemVer Maps to Tag Names
MAJOR increments when you break backward compatibility. MINOR goes up when you add features that don’t break existing code. PATCH moves for bug fixes only.
| Version Bump | Tag Example | What Changed |
|---|---|---|
| Major | v3.0.0 | Removed deprecated API endpoints |
| Minor | v2.5.0 | Added OAuth2 login support |
| Patch | v2.4.1 | Fixed timezone handling in reports |
| Pre-release | v3.0.0-beta.1 | Unstable preview of breaking changes |
The npm registry now hosts over 3.1 million packages (Wikipedia, 2025), and all of them rely on SemVer-tagged releases for dependency resolution. When you run npm install, the package manager reads those Git tag version numbers to figure out which code to pull.
Pre-release Tags
Labels like v2.0.0-alpha.1, v2.0.0-beta.3, and v2.0.0-rc.1 follow the SemVer pre-release spec. They sort correctly in version listings and signal instability to anyone pulling the code.
Package managers treat pre-release tags differently. npm won’t install a beta version unless you explicitly ask for it with npm install package@next. That’s a safety net built directly on top of Git tag naming conventions.
Tags in CI/CD Automation
Most modern continuous deployment setups use tags as deployment triggers. Push v2.5.0 to your remote, and a pipeline picks it up automatically.
Tools like semantic-release (over 1,173 dependent projects on npm) take this further. They read your commit messages, calculate the next version number, create the Git tag, and publish the release. Zero manual steps.
The 2024 State of DevOps Report by Puppet found that elite-performing teams deploy 973 times more frequently than their peers. Tag-based automation is a core part of how they get there.
Git Tag vs Git Branch

Both tags and branches point to commits. That’s where the similarity ends.
A branch moves. Every time you make a new commit on a branch, the branch pointer advances to that new commit. It’s a moving target by design, because branches exist for active development work.
A tag stays fixed. Once you create v1.0.0, it points to that one commit forever (unless you deliberately delete and recreate it, which you generally shouldn’t).
Key Differences at a Glance
| Behavior | Branch | Tag |
|---|---|---|
| Moves with new commits | Yes | No |
| Intended for | Active development | Permanent snapshots |
| Checkout behavior | Normal HEAD | Detached HEAD |
| Pushed by default | Yes (with tracking) | No |
The Detached HEAD Problem
Checking out a tag puts you in detached HEAD state. That’s Git’s way of saying “you’re looking at a fixed point in history, not an active branch.”
Any commits you make in detached HEAD aren’t attached to any branch. Switch away without saving, and those commits become orphaned. CircleCI’s documentation notes this is one of the most common sources of confusion for developers learning version control.
The fix is simple. If you need to make changes based on a tagged release, create a new branch from it first:
git checkout -b hotfix/v1.0.1 v1.0.0
When to Branch from a Tag
Hotfix scenario: Production is running v2.3.0 and a critical bug shows up. You branch from the v2.3.0 tag, fix the bug, tag it as v2.3.1, and deploy.
Inspection scenario: You just want to look at what the code looked like at v1.5.0. Check out the tag directly, poke around, then switch back to your branch. No new branch needed.
The Hutte developer survey found that 90% of developers believe continuous learning is key to avoiding Git problems. Knowing when to use a tag versus a branch is one of those fundamentals that saves you from messy situations later.
Git Tag in Common Git Workflows
Tags don’t exist in isolation. They fit into specific workflow patterns that teams use every day.
Tagging in Gitflow
In the Gitflow model, releases get their own branch. When a release branch merges back into main, that merge commit gets tagged.
- Release branches like
release/2.0are where final testing and polish happen - The merge commit on
mainreceives the version tag - The tag then triggers any automated deployment pipelines
Over 90% of Fortune 100 companies use GitHub (Kinsta), and many of them follow Gitflow or a variant of it for their release cycles.
GitHub Releases
GitHub Releases are built directly on Git tags. When you push a tag, you can attach release notes, changelogs, and binary assets to it through the GitHub interface or API.
The gh release create v2.0.0 --title "v2.0.0" --notes "Release notes" command creates both the tag and the release in one step from the CLI. GitHub Actions processes over 71 million jobs per day (GitHub Blog, 2025), and a significant chunk of those are release workflows triggered by tag pushes.
Using git describe
The git describe command gives you a human-readable label based on the nearest tag. Run it and you might get something like:
v2.3.0-14-g2414721
That breaks down to: 14 commits after tag v2.3.0, at commit g2414721. Build systems use this constantly to generate version strings for build artifacts without needing manual input.
Package Managers and Git Tags
Go modules: Go resolves dependencies directly from Git tags. A tag like v1.4.2 in your repo becomes an importable module version. No separate registry involved.
npm: The npm version patch command increments the version in package.json, creates a Git commit, and generates a Git tag all in one step. Socket’s 2023 npm retrospective found that 10.5 million package versions were published to npm in a single year, nearly all tagged following SemVer conventions.
The software development process at most companies now depends on tags for traceability. Without them, there’s no reliable way to connect a running production build back to a specific point in the source control history.
Common Git Tag Mistakes

Tags are simple. But simple tools can still cause headaches when you use them wrong. Here are the mistakes that come up over and over.
Forgetting to Push Tags to Remote
This is the number one tag mistake. You create a tag locally, push your commits, and assume the tag went along for the ride. It didn’t.
The fix: Set git config --global push.followTags true once and forget about it. Every annotated tag reachable from pushed commits will sync automatically.
Accidentally Creating Lightweight Tags
Run git tag v2.0.0 instead of git tag -a v2.0.0 -m "message" and you get a lightweight tag with zero metadata. No author, no date, no message.
Lightweight tags won’t push with --follow-tags. Some CI tools ignore them entirely. And six months from now, nobody will know who created it or why. At least in my experience, this one bites people quietly because everything looks normal until it doesn’t.
Tagging the Wrong Commit
Scenario: You merge a PR, then realize you tagged the commit before the merge instead of the merge commit itself.
If you haven’t pushed yet, the cleanup is easy:
git tag -d v2.0.0(delete locally)git tag -a v2.0.0 correct-hash -m "Release v2.0.0"(recreate on the right commit)
If you already pushed, you’re in for the messy dance of deleting the remote tag and asking teammates to run git fetch --prune --prune-tags. Better to just ship v2.0.1 and move on.
Name Conflicts Between Tags and Branches
Git allows a tag and a branch to share the same name. So you can have both a branch called release-2.0 and a tag called release-2.0. Don’t.
When you run git checkout release-2.0, Git has to pick one. Behavior varies across Git versions and can lead to silent, confusing bugs. The source control management documentation recommends using the v prefix for tags (like v2.0) to avoid this entirely.
Overwriting Tags That Teammates Already Fetched
This was covered in the delete section, but it bears repeating. Never force-push a moved tag on a shared repository unless you’ve coordinated with the entire team first.
The Hutte survey found that 75% of developers say peer review helps catch Git issues early. Tags are no exception. Having a second pair of eyes on release tagging, even informally, prevents most of these problems before they reach production.
Kubernetes learned this the hard way early on, establishing strict tag governance after contributors accidentally moved release tags, causing confusion across their massive dev and ops collaboration pipeline. Your mileage may vary, but the lesson applies at any scale.
FAQ on What Is Git Tag
What is a git tag used for?
A git tag marks a specific commit in your repository history with a permanent label. Teams primarily use tags to identify release points like v1.0 or v2.3.1, giving everyone a fixed reference for deployments and version tracking.
What is the difference between a lightweight tag and an annotated tag?
A lightweight tag is just a pointer to a commit with no extra data. An annotated tag stores the tagger’s name, email, date, and a message as a full Git object. Annotated tags also support GPG signing.
How do I create a git tag?
Run git tag -a v1.0.0 -m "Release message" for an annotated tag. For a lightweight tag, use git tag v1.0.0 without the -a flag. You can also tag older commits by appending the commit hash.
Why don’t my git tags push automatically?
Git excludes tags from regular git push by design. Tags are meant to be stable markers, not something synced accidentally. Use git push origin --tags or set push.followTags true in your git config.
How do I delete a git tag from a remote repository?
Run git push origin --delete v1.0.0 to remove it from the remote. Then run git tag -d v1.0.0 locally. Teammates who already fetched the tag will need to prune with git fetch --prune --prune-tags.
What happens when I checkout a git tag?
Checking out a tag puts you in detached HEAD state. You can browse files and run tests, but any new commits won’t belong to a branch. Create a new branch from the tag first if you plan to make changes.
What is the difference between a git tag and a git branch?
A branch moves forward with each new commit. A tag stays fixed on one specific commit permanently. Branches are for active work. Tags are for marking release snapshots in your version control history.
How do git tags work with semantic versioning?
Teams name tags using the MAJOR.MINOR.PATCH format, like v2.4.1. Package managers such as npm and Go modules read these tag names to resolve dependency versions. Pre-release labels like v2.0.0-beta.1 are also supported.
Can I move a git tag to a different commit?
Yes, but it’s risky on shared repositories. Delete the tag locally and remotely, then recreate it on the correct commit. Anyone who already pulled the old tag will have a stale reference. Shipping a new patch version is usually safer.
How do CI/CD pipelines use git tags?
Most pipelines trigger deployments when a new tag is pushed. GitHub Actions, GitLab CI, and similar tools watch for tag patterns like v. Pushing a release tag automatically kicks off build, test, and deploy steps.
Conclusion
Understanding what is git tag comes down to one thing: giving your repository stable, named reference points that never drift. Tags turn a messy commit history into a structured release timeline that your entire team and every CI/CD pipeline can rely on.
Annotated tags with proper semantic versioning names belong on every production release. Lightweight tags have their place for local bookmarks, but they shouldn’t reach your remote repository as official markers.
Push tags deliberately. Use –follow-tags` to avoid syncing junk. And if you tag the wrong commit, ship a patch version instead of rewriting history.
The commands are simple. The real skill is building consistent tagging habits across your software development process so that every release, rollback, and changelog traces back to one clean, immutable snapshot in Git.
- 4 Scalable Hosting Providers for Growing Small Business Websites - April 9, 2026
- 7 Best Private Equity CRM Platforms for Middle-Market Deal Teams [2026 Comparison] - April 8, 2026
- Markdown Cheat Sheet - April 8, 2026






