Skip to content
shellhacks.dev
Git

How to Use git tags for Releases and Versions With Examples

Use git tag for releases: lightweight versus annotated tags, signing, pushing to a remote, listing and deleting, and naming versions with semantic versioning.

6 min read
Summarize in AI
Illustration for "How to Use git tags for Releases and Versions With Examples": a terminal session, a git branch graph, a loop arrow.

A branch moves; a tag stays put, which makes it the right way to mark "this commit is version 1.2.0". CI systems, package managers and release pages all key on tags. Getting the details right means choosing the annotated form and remembering that pushing is a separate step. The related scripting is in release versioning with shell. The examples assume Git 2.47.

Create tags

Shell
git tag v0.9.0-rc1
git tag -a v1.2.0 -m "Release 1.2.0"
git tag -a v1.1.5 9fceb02 -m "Backported fix"
git tag -l 'v1.*'
Text
v1.1.5
v1.2.0

The first line makes a lightweight tag, a bare name for the current commit. The second makes an annotated tag, a real object with a tagger, date and message. The third tags an older commit by its hash. Tools that read release metadata, including git describe by default, use only annotated tags, so make that the habit.

Inspect a tag

Shell
git show v1.2.0 --no-patch
git describe --tags
git rev-list -n 1 v1.2.0
Text
tag v1.2.0
Tagger: Pete Houston <[email protected]>
Date:   Tue Nov 3 10:12:44 2026 +0000

Release 1.2.0
v1.2.0-14-g3a1b2c4
3a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b

git describe prints the nearest tag, the number of commits since it and the abbreviated hash. That string makes a good build version for an untagged commit: it sorts, it tells you what the base was and it points back to the exact source.

Share tags

Shell
git push origin v1.2.0
git push origin --tags
git push --follow-tags

A plain git push does not send tags. Push one by name, push all with --tags, or use --follow-tags to push only annotated tags reachable from the commits being pushed, which avoids publishing stray lightweight tags. Set git config --global push.followTags true to make that the default. git fetch --tags brings tags from the remote, as explained in fetch versus pull.

Delete or fix a tag

Shell
git tag -d v1.2.0
git push origin --delete v1.2.0
git tag -a v1.2.0 -m "Release 1.2.0" 3a1b2c4
git push origin v1.2.0

Moving a published tag is dangerous, because anyone who already fetched it keeps the old target and builds diverge. If a release was wrong, tag a new patch version, like v1.2.1. Only re-tag when you are sure nobody has used it, such as a tag pushed a minute ago. Sign tags with git tag -s when consumers verify them; the key setup is in GPG for scripts.

Name versions consistently

Semantic versioning (MAJOR.MINOR.PATCH) with a v prefix is the convention most tooling assumes: v1.2.0, with pre-releases like v1.3.0-rc.1. Go modules and many CI templates require the prefix. The Semantic Versioning specification defines what each number means.

Lightweight and annotated tags

Git has two kinds of tag. A lightweight tag is a named pointer to a commit, with nothing else attached. An annotated tag is a full object with a tagger name, a date and a message, and it can be signed with GPG or SSH keys. Use annotated tags for releases. They record who made the release and when, they show a message in git show, and git describe prefers them by default. Lightweight tags suit private bookmarks that you do not intend to share, such as marking a commit while bisecting.

Create an annotated tag with git tag -a v1.4.0 -m "Release 1.4.0", and tag an older commit by adding its hash at the end. Signing with -s lets users verify that the release came from you, which matters for software others install, and verification is done with git tag -v.

Tags are not moved by pushes

A plain git push does not send tags. Push one with git push origin v1.4.0, or all with --tags, though pushing all can publish private bookmarks by accident. The safer alternative is --follow-tags, which pushes only annotated tags that point at commits being pushed. The same asymmetry applies on fetch, where tags reachable from fetched branches come along automatically but others need --tags. The behaviour explains the common report that a tag exists locally and not on the server, or the reverse.

Moving a published tag is a bad idea. Others have fetched the old target and Git does not update an existing tag on fetch without force, so people end up with different meanings for the same name. If a release was tagged wrongly, create a new tag with the next patch number and leave the old one in place, noting the problem in the release notes.

Using tags to find and build releases

Tags make releases findable. git describe --tags prints the nearest tag, the number of commits since it and the abbreviated hash, such as v1.4.0-12-g3a1b2c4, which makes a good version string for a development build because it identifies the exact commit. Build pipelines can trigger on tag pushes that match a pattern, check out the tagged commit, build the artifacts and attach them to a release. A script can compute the next version from the latest tag using git tag --list 'v*' --sort=-v:refname | head -n1, where the version sort understands numeric ordering.

Keep the tag name format consistent. The leading v or its absence, the separator and the pre-release suffix such as -rc.1 should be decided once, since tooling that parses versions depends on it.

Deleting and correcting

Delete a local tag with git tag -d name and a remote one with git push origin --delete name. Do this only for tags that never left your team or were created in error and not yet consumed by users, packages or caches. Package registries and mirrors may already have recorded the commit a tag pointed to, so changing it creates a mismatch that is hard to detect. Protect release tags on the server with a tag protection rule so that only maintainers can create or delete them. Finally, record the process in the release checklist: run the tests, update the changelog, create the signed annotated tag, push it, wait for the pipeline and verify the published artifact. A written checklist is the cheapest way to get the same result from every release.

Teams that release often benefit from automation around tags. A small script can read the latest tag, bump the major, minor or patch part, create the annotated tag with a message built from the commits since the last one and push it, with a dry run option. Combined with conventional commit messages, it can even choose the bump from the changes. The script becomes the one way to release, which removes the variation between people and the typos in version names that cause broken pipelines.

Where this does not apply

Continuous deployment of every commit does not need release tags, though tagging the deployed commit is a cheap audit trail. Tags do not carry files: attach build artifacts to a release page, as in GitHub Releases for distribution. See the git-tag manual and the Pro Git chapter on tagging.

Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.