Skip to content

Git Tag: Marking Releases and Bookmarks in History

Git Tag: Marking Releases and Bookmarks in History

Tags are Git’s mechanism for assigning meaningful labels to specific commits. Unlike branches, tags don’t move — they’re pinned to a commit and serve as anchors for releases, versions, and critical checkpoints. Without tags, release history devolves into a hash search — and that’s a direct path to deployment errors.


Types of Tags: Lightweight vs Annotated

There are two types of tags. Lightweight is just a name attached to a commit, with no additional metadata. Annotated is a full Git object with author, date, message, and signing capability.

Warning

Always use annotated tags for releases. Lightweight tags carry no metadata and can’t be signed — you lose context during audits or rollbacks.

PropertyLightweightAnnotated
Object in DBNoYes (tag object)
MessageNoYes
Author/DateNoYes
GPG SignatureNoYes
Creation SpeedFasterSlightly slower

Creating, Viewing, and Deleting Tags

Create an annotated tag:

git tag -a v1.2.0 -m "Release 1.2.0: API stabilization"

Create a lightweight tag:

git tag v1.2.0-rc1

List all tags:

git tag

Show details of a specific annotated tag:

git show v1.2.0

Delete a local tag:

git tag -d v1.2.0-rc1

List tags matching a pattern:

git tag -l "v1.*"
Tip

The -l flag supports glob patterns. This is faster than piping git tag through grep.


Pushing Tags to Remote

By default, git push does not send tags. This is a common reason why teammates can’t see a release tag on the remote repository.

Push a single tag:

git push origin v1.2.0

Push all local tags:

git push origin --tags

Delete a tag on remote:

git push origin --delete v1.2.0

Local deletion and remote deletion are two separate operations. Forgetting the second is a typical mistake during a release rollback.


GPG-Signing Tags

Annotated tags can be signed with a GPG key. This guarantees the tag was created by a specific author and hasn’t been tampered with.

Ensure your GPG key is configured first:

gpg --list-secret-keys --keyid-format=long
git config --global user.signingkey <KEY_ID>
git config --global gpg.format openpgp

Create a signed tag:

git tag -s v1.2.0 -m "Signed release 1.2.0"

Verify the signature:

git tag -v v1.2.0
Note

If git tag -v returns “no signature found”, the tag isn’t signed. If it says “Good signature from…”, the signature is valid. Make sure the author’s public key is available in your keyring.


Working with Tags in CI/CD Pipelines

In pipelines, tags are the primary trigger for releases. Most CI/CD systems allow filtering by branches and tags.

Example for GitHub Actions:

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-tags: true
      - run: echo "Building for $(git describe --tags)"

Example for GitLab CI:

deploy:
  script:
    - echo "Deploying tag $CI_COMMIT_TAG"
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'

Useful commands inside a pipeline:

# Current tag if the commit is tagged
git describe --tags --exact-match HEAD

# Last tag before the current commit
git describe --tags --abbrev=0 HEAD

# All tags sorted by creation date (newest first)
git tag --sort=-creatordate
Tip

Always use fetch-tags: true in the checkout step. Without it, the pipeline may not see tags and the $CI_COMMIT_TAG filter will break.


Tags are a minimal tool with maximum impact. Annotated with GPG signatures, --tags on release push, and v* filtering in CI — that set is enough to keep release history readable and verifiable.