Git — Tags & Releases
Tags in Git are static references to a specific commit, typically used to mark release points (v1.0, v2.3.1, etc.). Unlike branches, tags do not move when new commits are made — they are permanent markers in your project's history.
This guide covers lightweight and annotated tags, signing tags with GPG, semantic versioning conventions, and release tagging workflows.
Git supports two types of tags: lightweight and annotated. The difference is that annotated tags store metadata (tagger name, email, date, message) and can be cryptographically signed.
| Feature | Lightweight | Annotated |
|---|---|---|
| Storage | Just a name pointing to a commit | Full Git object with metadata |
| Tagger info | No tagger information | Stores name, email, date |
| Message | No message | Has a tag message (like a commit) |
| Signing | Cannot be signed | Can be GPG or SSH signed |
| Use case | Personal bookmarks, temporary markers | Releases, public tags |
| 1 | # Create a lightweight tag (just a pointer) |
| 2 | git tag v1.0.0 |
| 3 | |
| 4 | # Create an annotated tag (recommended for releases) |
| 5 | git tag -a v1.0.0 -m "Release v1.0.0 — stable API" |
| 6 | |
| 7 | # Create an annotated tag with a longer message |
| 8 | git tag -a v1.0.0 -m "Release v1.0.0 |
| 9 | |
| 10 | This release introduces the new payment API |
| 11 | and deprecates the legacy checkout system." |
best practice
Teams adopt different tagging strategies depending on their release process. Here are common workflows for tagging releases:
| 1 | # Workflow 1: Tag on main after merge |
| 2 | # 1. Feature branch merged to main |
| 3 | # 2. CI passes on main |
| 4 | # 3. Tag and release |
| 5 | git checkout main |
| 6 | git pull |
| 7 | git tag -a v1.2.0 -m "Release v1.2.0" |
| 8 | git push origin v1.2.0 |
| 9 | |
| 10 | # Workflow 2: Tag on release branch (Git Flow) |
| 11 | git checkout release/1.3 |
| 12 | # ... final testing and fixes ... |
| 13 | git commit -m "Release 1.3 preparations" |
| 14 | git tag -a v1.3.0 -m "Release v1.3.0" |
| 15 | git push origin v1.3.0 |
| 16 | git checkout main |
| 17 | git merge release/1.3 |
| 18 | |
| 19 | # Workflow 3: Automated tagging via CI |
| 20 | # In CI pipeline (GitHub Actions example): |
| 21 | # - name: Tag release |
| 22 | # run: | |
| 23 | # git tag -a v${env.VERSION} -m "Release v${env.VERSION}" |
| 24 | # git push origin v${env.VERSION} |
| 25 | # |
| 26 | # This integrates with semantic-release for full automation |
Always Use Annotated Tags for Releases
Public releases should always use annotated tags with a descriptive message. Lightweight tags are fine for personal bookmarks but lack the metadata needed for provenance.
Follow SemVer Conventions
Use the vMAJOR.MINOR.PATCH format for all release tags. This ensures compatibility with tools like npm version, Go modules, and semantic-release.
Automate Tagging in CI/CD
Use tools like semantic-release or CI pipeline scripts to create tags automatically. This reduces human error and ensures every release is properly tagged.
Sign Release Tags
For public packages and open-source projects, sign your release tags with GPG. This allows consumers to verify that the release was created by you and has not been tampered with.