Skip to content
C Codeloom
Git

Git Tags and Releases Tutorial

Learn lightweight vs annotated tags, signed tags, semantic versioning, and how to ship clean releases on GitHub. Practical workflow from tagging to changelog to publishing.

·4 min read · By Codeloom
Beginner 8 min read

What you'll learn

  • The difference between lightweight and annotated tags
  • How to sign tags with GPG or SSH for trust
  • Semantic versioning in practice
  • Linking tags to GitHub Releases and changelogs
  • How to move or delete a tag without breaking consumers

Prerequisites

  • Basic Git usage and a remote like GitHub

What and Why

A tag in Git is a named pointer to a specific commit. Branches move as you commit; tags stay put. That makes tags the natural way to mark “this is the exact code that shipped as v1.4.0”. On top of tags, hosts like GitHub layer Releases: a tag plus a title, notes, and downloadable assets.

You tag releases for three reasons: customers and CI systems can fetch a stable version, you can compare what changed between versions, and you can roll back without guesswork.

Mental Model

There are two kinds of tags:

  • Lightweight: a file under .git/refs/tags/ containing a commit SHA. No metadata, no signature, no message. Like a sticky note.
  • Annotated: a full Git object stored in the object database with a tagger name, date, message, and optionally a GPG/SSH signature. It points to a commit.

Annotated tags are what you almost always want for releases: they are traceable, signable, and survive git describe cleanly.

Semantic versioning (MAJOR.MINOR.PATCH) gives the tag a meaning:

  • MAJOR: breaking changes
  • MINOR: backward-compatible features
  • PATCH: backward-compatible fixes

Pre-releases use suffixes like -rc.1 or -beta.2. Build metadata uses +sha.abcdef0.

Hands-on Example

Create and push an annotated tag for a release:

git checkout main
git pull
git tag -a v1.4.0 -m "v1.4.0: add CSV export and fix login redirect"
git push origin v1.4.0

List and inspect tags:

git tag --list 'v1.*'
git show v1.4.0          # tag metadata + commit + diff

Sign a tag so consumers can verify it:

git config --global user.signingkey <KEYID>
git tag -s v1.4.1 -m "v1.4.1: patch null pointer in export"
git tag -v v1.4.1        # verify signature

Generate release notes from commits since the previous tag:

git log v1.3.0..v1.4.0 --pretty=format:'- %s (%h)' --no-merges

Create a GitHub Release from the tag using the GitHub CLI:

gh release create v1.4.0 \
  --title "v1.4.0" \
  --notes-file CHANGELOG-v1.4.0.md \
  dist/app-v1.4.0.tar.gz
main:  C1 -- C2 -- C3 -- C4 -- C5
                |          |
                v1.3.0     v1.4.0   (annotated, signed)

git push origin v1.4.0
      |
      v
 GitHub stores tag object
      |
      v
 gh release create v1.4.0
      |
      v
 Release page:
   title, notes, assets (tar.gz, checksums, sigs)
   download link pinned to commit C5
From commit graph to published release

Common Pitfalls

  • Moving tags after publish: once consumers fetch v1.4.0, do not retag the same name on a different commit. It silently breaks reproducible builds. Cut v1.4.1 instead.
  • Forgetting to push tags: git push does not push tags by default. Use git push origin <tag> or git push --tags for all of them.
  • Lightweight tags for releases: they lose history when re-cloned shallowly and cannot be signed. Use annotated tags.
  • Tagging the wrong commit: always check git log -1 on the commit you intend to tag, especially after merges.
  • Inconsistent prefix: mixing v1.0.0 and 1.0.0 confuses tooling. Pick one and stick with it.

Practical Tips

  • Automate tagging in CI when a release/* branch merges, so humans do not forget to push tags.
  • Pair every tag with a CHANGELOG entry; “Keep a Changelog” is a solid format.
  • Use git describe --tags to derive build versions like v1.4.0-3-gabc1234.
  • Delete a local tag with git tag -d v1.4.0 and a remote tag with git push origin :refs/tags/v1.4.0 only if it has never been consumed.
  • For monorepos, prefix tags by component: api/v1.4.0, web/v2.0.1.

Wrap-up

Tags are cheap, immutable, and incredibly useful. Annotate them, sign them when trust matters, follow semver, and pair them with a Release page that links to notes and artifacts. Future-you, debugging an outage at 2am, will thank past-you for tagging clearly and never moving a published tag.