Tagging Releases
Reviewed & published by Brayan K
When you ship a version, you want a permanent, memorable marker for that exact commit. Tags do precisely that. By the end of this lesson you'll create lightweight and annotated tags, push them, delete them, and version your releases with semantic versioning.
Part of the free Git course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- What a tag is and how it differs from a branch
- Create lightweight tags with git tag
- Create annotated tags with git tag -a (recommended)
- List and inspect tags with git tag and git show
- Push tags explicitly (they don't go with a normal push)
- Version releases with semantic versioning
1️⃣ Lightweight vs Annotated Tags
A tag is a fixed name for one commit; it doesn't move when you commit again. A lightweight tag (git tag v1.0.0) is just that name. An annotated tag (git tag -a v1.1.0 -m "...") is a full object storing who tagged it, when, and why — the recommended choice for real releases, and the only kind that can be signed.
# A tag is a fixed name for one specific commit — usually a release.
# Unlike a branch, a tag does NOT move when you make new commits.
# LIGHTWEIGHT tag — just a name pointing at the current commit
git tag v1.0.0
# ANNOTATED tag — a full object with tagger, date, and a message.
# Recommended for real releases (and required to be signed).
git tag -a v1.1.0 -m "Release 1.1.0 — adds dark mode"
# Tag an OLDER commit by giving its hash
git tag -a v0.9.0 9z8y7x6 -m "Beta release"2️⃣ Listing & Inspecting Tags
git tag lists every tag, and git tag -l "v1.*" filters by pattern. git show <tag> reveals what it points to — for an annotated tag you also see the tagger, date, and message; a lightweight tag just shows its commit.
# List and inspect your tags.
# List all tags (alphabetical/version order)
git tag
# Filter with a pattern
git tag -l "v1.*"
# Show what a tag points to. For an ANNOTATED tag you also see the
# tagger, date, and message; a lightweight tag just shows the commit.
git show v1.1.03️⃣ Pushing & Deleting Tags
Here's the catch that surprises everyone: a normal git push does not send tags. Push one with git push origin v1.1.0, or all of them with git push --tags. Delete a tag locally with git tag -d, and remove it from the remote with git push origin --delete.
# ⚠️ Tags are NOT pushed by a normal git push. You must send them.
# Push one specific tag
git push origin v1.1.0
# Push ALL your local tags at once
git push --tags
# Delete a tag locally...
git tag -d v0.9.0
# ...and delete it on the remote too (note the colon / --delete form)
git push origin --delete v0.9.04️⃣ Semantic Versioning
Tags are most useful when their names mean something. Semantic versioning uses MAJOR.MINOR.PATCH: bump MAJOR for breaking changes, MINOR for new backward-compatible features, and PATCH for backward-compatible bug fixes. A glance at v2.3.1 tells users how risky an upgrade is.
Your turn. Cut an annotated release tag and publish it. Fill in the three blanks.
# 🎯 YOUR TURN — replace each ___ then run it in your terminal.
# Goal: cut and publish a release tag.
# 1) Create an ANNOTATED tag for version 1.0.0 with a message
git tag ___ v1.0.0 -m "First release" # 👉 the flag that makes it annotated
# 2) Confirm it exists
git ___ # 👉 the command that lists tags
# 3) Publish it to the remote (tags don't go with a normal push)
git push origin ___ # 👉 the tag name to push
# ✅ Expected: step 1 is quiet, step 2 lists v1.0.0, step 3 reports
# "* [new tag] v1.0.0 -> v1.0.0".Common Errors (and the fix)
- Tag missing on the remote: a normal push doesn't send tags. Run git push origin <tag> or git push --tags.
- "fatal: tag 'v1.0.0' already exists": you tried to recreate an existing tag. Delete it first (git tag -d) or pick a new version.
- git show on a tag shows no message: it's a lightweight tag, which stores no metadata. Use annotated tags (-a) for releases.
- Deleted a tag locally but it's still on the remote: git tag -d is local only. Also run git push origin --delete <tag>.
Pro Tips
- 💡 Annotate releases: always use -a for shipped versions so the who/when/why is recorded.
- 💡 Follow semver: consistent MAJOR.MINOR.PATCH tags make upgrades predictable.
- 💡 Tag, then push tags: make it a habit so releases never get stuck locally.
- 💡 Don't retag published versions: if a tag is out there, ship a new version instead of moving it.
📋 Quick Reference
| Command | Purpose |
|---|---|
| git tag v1.0.0 | Create a lightweight tag |
| git tag -a v1.0.0 -m "msg" | Create an annotated tag |
| git tag | List all tags |
| git show v1.0.0 | Inspect a tag and its commit |
| git push origin v1.0.0 | Push one tag |
| git push --tags | Push all tags |
| git tag -d v1.0.0 | Delete a tag locally |
Mini-Challenge: Version a Project
No commands given this time — just the plan. Walk a tiny project through four releases using correct semver tags.
# 🎯 MINI-CHALLENGE: version a small project with semver.
# No commands given — just the plan. Run each step in your terminal.
#
# 1. Make a commit and tag it v1.0.0 (annotated) — your first release
# 2. Add a backward-compatible feature, commit, and tag the right version
# 3. Fix a bug, commit, and tag the right PATCH version
# 4. Make a breaking change, commit, and tag the right MAJOR version
# 5. Run "git tag" and confirm: v1.0.0, v1.1.0, v1.1.1, v2.0.0
#
# ✅ Expected: four tags following MAJOR.MINOR.PATCH correctly.
# your commands here🎉 Lesson Complete!
- ✅ A tag is a fixed name for one commit — unlike a branch, it doesn't move
- ✅ git tag name is lightweight; git tag -a is annotated (recommended)
- ✅ Annotated tags store the tagger, date, and message
- ✅ Tags don't go with a normal push — use git push origin <tag> or --tags
- ✅ Semantic versioning is MAJOR.MINOR.PATCH
Practice quiz
What is a Git tag?
- A movable branch label
- A commit message
- A fixed, named pointer to a specific commit
- A remote URL
Answer: A fixed, named pointer to a specific commit. A tag marks one specific commit with a memorable name, typically for releases.
How does a tag differ from a branch?
- A branch moves forward as you commit; a tag stays pinned to one commit
- Tags move with new commits; branches don't
- They are identical
- Tags can't be pushed
Answer: A branch moves forward as you commit; a tag stays pinned to one commit. Branches advance with each commit, while a tag stays fixed on the commit it marks.
Which command creates a LIGHTWEIGHT tag?
- git tag -a v1.0.0
- git tag --light v1.0.0
- git branch v1.0.0
- git tag v1.0.0
Answer: git tag v1.0.0. git tag <name> with no -a creates a lightweight tag — just a name pointing at a commit.
Which creates an ANNOTATED tag?
- git tag v1.0.0
- git tag -a v1.0.0 -m "message"
- git tag --quick v1.0.0
- git commit --tag
Answer: git tag -a v1.0.0 -m "message". git tag -a creates an annotated tag, a full object with tagger, date, and message.
What extra information does an annotated tag store?
- Tagger name, date, and a message
- Nothing extra
- The whole repo
- A branch list
Answer: Tagger name, date, and a message. Annotated tags are real objects holding the tagger, a timestamp, and a message.
Why are annotated tags recommended for releases?
- They're smaller
- They move automatically
- They carry metadata (who/when/why) and can be signed
- They skip the staging area
Answer: They carry metadata (who/when/why) and can be signed. Annotated tags record who tagged, when, and why, which matters for real releases.
By default, does `git push` send your tags to the remote?
- Yes, automatically
- No — tags must be pushed explicitly
- Only annotated tags
- Only on the first push
Answer: No — tags must be pushed explicitly. Tags aren't pushed by default; use git push origin <tag> or git push --tags.
Which command pushes ALL your local tags?
- git push
- git tag --push
- git send-tags
- git push --tags
Answer: git push --tags. git push --tags uploads every local tag to the remote.
In semantic versioning v2.3.1, the '3' is the...
- MAJOR version
- MINOR version
- PATCH version
- Build number
Answer: MINOR version. In MAJOR.MINOR.PATCH, the middle number is the MINOR version, bumped for backward-compatible features.
Which command deletes a local tag?
- git tag -r v1.0.0
- git untag v1.0.0
- git tag -d v1.0.0
- git rm v1.0.0
Answer: git tag -d v1.0.0. git tag -d <name> deletes a tag locally.
Continue this course
- Previous: Amending & Fixing Commits
- Next: Rebasing Branches — Replay commits for a linear history — and the golden rule of rebasing
- Quick reference: Git cheat sheet › Tags & Releases
Frequently asked questions
What's the difference between a tag and a branch?
A branch is a movable label that advances every time you commit — it tracks an ongoing line of work. A tag is a fixed pointer to one specific commit and never moves on its own, which is exactly what you want for marking a release like v1.0.0. You commit on branches; you tag milestones.
Should I use a lightweight or an annotated tag?
Use annotated tags (git tag -a) for anything that matters, especially releases. An annotated tag is a full Git object storing the tagger's name, the date, and a message, and it can be GPG-signed. A lightweight tag (git tag <name>) is just a bare name pointing at a commit, with no metadata — fine for a quick private bookmark, but not for shared releases.
Why didn't my tags show up on GitHub after pushing?
Because a normal git push does not send tags — only commits on branches. You have to push tags explicitly: git push origin <tagname> for one tag, or git push --tags to send all of them. This is a very common surprise: your release tag exists locally but the remote never received it until you push it.
What is semantic versioning?
Semantic versioning (semver) is the MAJOR.MINOR.PATCH convention. You bump MAJOR for breaking changes, MINOR for new backward-compatible features, and PATCH for backward-compatible bug fixes. So v1.4.2 means major 1, minor 4, patch 2. Tagging releases with semver tells users at a glance whether an upgrade is risky (major) or safe (patch).
How do I delete or move a tag?
Delete a tag locally with git tag -d <name>, and remove it from the remote with git push origin --delete <name>. Tags are meant to be immutable, so 'moving' one is really delete-and-recreate — and if it was already pushed and others rely on it, retag with a new version instead. Rewriting a published tag confuses anyone who already fetched it.