Git Tags and Releases Best Practices: A Complete Guide

Git Tags and Releases Best Practices

A Git tag is a permanent name for one commit, usually a version like v1.4.0. A GitHub release is a page built on top of a tag, with release notes and downloadable files. Use annotated tags for releases, push them with git push --follow-tags, and let CI create the release so every version is built and documented the same way.

This guide covers the tag commands you will actually use, how to fix tagging mistakes, how to protect release tags, and a current GitHub Actions workflow that publishes a release on every version tag. It is part of our complete Git and GitHub Actions workflow guide.

Choosing how releases fit your branching model? See Git Flow vs GitHub Flow vs trunk-based development.


Lightweight vs Annotated Tags

LightweightAnnotated
What it storesJust a pointer to a commitA full Git object: tagger, date, message, optional signature
Creategit tag v1.0.0git tag -a v1.0.0 -m "Release 1.0.0"
Pushed by --follow-tagsNoYes
Used by git describe by defaultNoYes
Use forPrivate, temporary markersReleases

Always use annotated tags for releases. They record who tagged the release and when, and several Git features ignore lightweight tags by default.


Everyday Tag Commands

git tag                          # list tags
git tag -l "v1.*"                # list matching tags
git show v1.0.0                  # show the tag and its commit
git tag -a v1.1.0 -m "Release 1.1.0"            # tag the current commit
git tag -a v1.0.1 abc1234 -m "Release 1.0.1"    # tag a specific commit

git push origin v1.1.0           # push one tag
git push --follow-tags           # push commits plus their annotated tags

git tag -d v1.1.0                # delete locally
git push origin --delete v1.1.0  # delete on the remote

git describe --tags              # e.g. v1.1.0-3-g9f2c1ab (3 commits after v1.1.0)

Prefer git push --follow-tags over git push --tags. --tags pushes every local tag, including throwaway lightweight ones; --follow-tags only pushes annotated tags that point at commits you are pushing. You can make it the default with git config --global push.followTags true.


Signed Tags

git tag -s v1.2.0 -m "Release 1.2.0"   # sign with your GPG or SSH key
git tag -v v1.2.0                      # verify the signature

Signed tags prove the release came from you. GitHub shows a “Verified” badge on signed tags if your signing key is added to your account. With SSH signing, set git config --global gpg.format ssh and point user.signingkey at your public key.


Fixing Tag Mistakes

Tagged the wrong commit (not pushed yet)

git tag -d v1.0.0
git tag -a v1.0.0 <correct-commit> -m "Release 1.0.0"

Tagged the wrong commit (already pushed)

git tag -d v1.0.0
git push origin --delete v1.0.0
git tag -a v1.0.0 <correct-commit> -m "Release 1.0.0"
git push origin v1.0.0

Be careful here. Anyone who already fetched the old tag keeps it; Git will not overwrite an existing tag on fetch. If the version has been downloaded or deployed, it is usually better to leave it and release v1.0.1 instead.

Tag not showing on the remote

Tags are not pushed by a plain git push. Push it by name, or use --follow-tags.


Best Practices for Release Tags

  • Use semantic versioning (MAJOR.MINOR.PATCH) with a v prefix: v2.3.1.
  • Only tag commits that passed CI. Tag from main after the merge, not from a feature branch.
  • Never move a published tag. Release a new patch version instead.
  • Protect release tags. On GitHub, add a repository ruleset targeting tags matching v* that restricts who can create, update or delete them.
  • Consider immutable releases. When enabled on GitHub, a published release’s tag and assets cannot be changed or deleted, which protects users from tampered downloads.
  • Automate the release step so notes and artifacts are generated the same way every time.

Creating a GitHub Release

From the web UI: Releases → Draft a new release → choose or create a tag → click Generate release notes → publish. From the terminal, the GitHub CLI does the same in one line:

gh release create v1.1.0 --generate-notes
gh release create v1.1.0 ./dist/app.tar.gz --generate-notes   # with an attached file

Automating Releases with GitHub Actions

This workflow creates a GitHub release with auto-generated notes every time you push a tag starting with v:

name: Release
on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: Create GitHub release
        uses: softprops/action-gh-release@v3
        with:
          generate_release_notes: true

The permissions: contents: write block matters. Repositories created in recent years give GITHUB_TOKEN read-only access by default, and without it the release step fails with a 403 error. To attach build output, add a build step and pass its files to the action with files: dist/*. For more on workflow syntax, see our GitHub Actions workflow YAML guide.

Your release flow then becomes two commands:

git tag -a v1.1.0 -m "Release 1.1.0"
git push --follow-tags

Frequently Asked Questions

What is the difference between a lightweight and an annotated Git tag?

A lightweight tag is just a pointer to a commit; an annotated tag is a full object storing the tagger, date, message and an optional signature. Use annotated tags for releases.

How do I push tags to a remote?

Run git push origin TAGNAME to push one tag, or git push origin –tags to push all local tags.

How do I delete a Git tag?

Run git tag -d TAGNAME to delete it locally, then git push origin –delete TAGNAME to remove it from the remote.

Can I move a Git tag to a different commit?

Yes, with git tag -f TAGNAME COMMIT and a force push, but moving a published tag is discouraged because existing clones keep the old one.

You Might Also Like