TechEarl

Git Tags: Create, Push, List, and Delete

Create, push, list, and delete git tags from the command line. When to use an annotated tag over a lightweight one, why git push leaves your tags behind, and how to delete a tag on the remote.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Create, push, list, and delete git tags: annotated vs lightweight tags, pushing tags to a remote, and removing a tag locally and on origin.

A git tag is a permanent, human-readable name pinned to one commit, the way you mark v1.2.0 so you can find the exact code you shipped months later. For anything you release, create an annotated tag, push it to the remote on its own, and you are done:

bash
git tag -a v1.2.0 -m "Release 1.2.0"   # create an annotated tag
git push origin v1.2.0                  # push it (git push alone will NOT)

That is the whole job for most people. The rest of this page is the detail behind it: the difference between annotated and lightweight tags, tagging a commit you made last week, listing and inspecting tags, deleting one in both places it lives, and checking one out.

Annotated vs lightweight tags

Git has two kinds of tag, and the choice matters more than it looks.

An annotated tag is a full object in git's database. It stores the tagger's name and email, the date, a message, and can be GPG-signed and verified. It is the right tool for a release, and it is what release tooling and CI expect.

bash
git tag -a v1.2.0 -m "Release 1.2.0"

A lightweight tag is just a pointer to a commit, with no metadata attached. It is fine as a private, throwaway bookmark, but it carries no record of who tagged it or when.

bash
git tag v1.2.0

The rule I follow: annotated for anything anyone else will see, lightweight only for a quick local marker. There is a real functional reason beyond tidiness. Some commands, git describe chief among them, ignore lightweight tags by default, so a release marked with a lightweight tag can quietly fall out of your version strings. If in doubt, use -a.

To sign a tag instead of just annotating it (the tagger identity is then cryptographically verifiable), use -s:

bash
git tag -s v1.2.0 -m "Release 1.2.0"   # GPG-signed
git tag -v v1.2.0                       # verify a signed tag

Tag a past commit

By default a tag points at whatever HEAD is right now. To tag an older commit (you forgot to tag the release, or you are backfilling history), pass the commit SHA as the last argument:

bash
git log --oneline          # find the commit you want
git tag -a v1.0.0 -m "Release 1.0.0" 9fceb02   # tag that specific commit

The same form works with any commit-ish: a branch name, HEAD~3, or a short SHA.

Push tags to the remote

Here is the gotcha that catches almost everyone: git push does not push tags. You can tag a commit, push your branch, and the tag stays sitting on your machine. Tags have to go up explicitly.

Push a single tag (the form I prefer, because it is deliberate):

bash
git push origin v1.2.0

Push every tag you have that the remote does not:

bash
git push origin --tags

There is a middle option worth knowing. --follow-tags pushes your branch and any annotated tags that point at commits in the range being pushed, in one command. It deliberately ignores lightweight tags, which is another reason to annotate your releases:

bash
git push --follow-tags

You can make that the default with git config --global push.followTags true, so a normal git push carries your release tags along without --tags dragging up every stray bookmark.

List and inspect tags

List all tags:

bash
git tag

Filter with a pattern (-l plus a glob), which is how you find one release line among many:

bash
git tag -l "v1.*"

To see what a tag actually points at, plus its message and (for annotated tags) the tagger and date, use git show:

bash
git show v1.2.0

For an annotated tag that prints the tag object header, then the commit it references. For a lightweight tag you just get the commit, which is itself a quick way to tell the two apart.

Delete a tag

A tag lives in two places once you have pushed it: your local repo and the remote. Deleting it locally does not touch the remote, so you usually need both commands.

Delete the local tag:

bash
git tag -d v1.2.0

Delete it on the remote:

bash
git push origin --delete v1.2.0

You may still see the old syntax git push origin :refs/tags/v1.2.0 around. It does the same thing (pushing an empty source ref deletes the destination), but --delete is the readable modern form, so reach for that.

Check out a tag

You can check out the code at a tag to inspect or build it. Because a tag points at a fixed commit rather than a moving branch, this drops you into a detached HEAD state, where new commits belong to no branch and are easy to lose:

bash
git checkout v1.2.0

If you only want to look, that is fine. If you intend to make changes from that point, branch off it first so your work has a home:

bash
git switch -c hotfix-1.2.1 v1.2.0

For more on moving between branches and the modern switch command, see my essential git branch commands reference.

FAQ

See also

Sources

Authoritative references this article was fact-checked against.

Tagsgitgit taggit push tagsannotated tagdelete git tagversion controlCLI

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Create a Pull Request

A pull request proposes merging one branch into another so a teammate can review it first. Here is the full flow on GitHub and GitLab: branch, push, open, review, merge.

How to Find and Delete Files Safely with find -delete

find -delete removes every matched file with no confirmation and no undo. The safe pattern is to write the command with -print first, eyeball the list, then swap -print for -delete. Plus the directory-depth-first trap, when to use -exec rm instead, and the find -delete vs xargs rm -f tradeoff.