# Git Tag: Marking Releases and Bookmarks in History

LLMS index: [llms.txt](/en/llms.txt)

---

## 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.

| Property | Lightweight | Annotated |
|---|---|---|
| Object in DB | No | Yes (tag object) |
| Message | No | Yes |
| Author/Date | No | Yes |
| GPG Signature | No | Yes |
| Creation Speed | Faster | Slightly slower |

---

## Creating, Viewing, and Deleting Tags

Create an annotated tag:

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

Create a lightweight tag:

```bash
git tag v1.2.0-rc1
```

List all tags:

```bash
git tag
```

Show details of a specific annotated tag:

```bash
git show v1.2.0
```

Delete a local tag:

```bash
git tag -d v1.2.0-rc1
```

List tags matching a pattern:

```bash
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:

```bash
git push origin v1.2.0
```

Push all local tags:

```bash
git push origin --tags
```

Delete a tag on remote:

```bash
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:

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

Create a signed tag:

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

Verify the signature:

```bash
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:

```yaml
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:

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

Useful commands inside a pipeline:

```bash
# 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.
