Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXII · Protected BranchesTagProtection

Tag protection — preventing tag deletion, restricting tag creation, enforcing signed tags

Advanced⏱ ~20 mingit

What you'll learn

  • Configure tag-deletion protection so a release tag cannot be silently removed
  • Restrict who can create or move tags on a production repository
  • Explain how signed-tag enforcement ties a tag to a key and what it refuses without verification
  • Diagnose why a release tag was overwritten despite a tag-protection rule

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

Tags are the release artefacts of a Git repository. A tag named v3.2.7 identifies, by a SHA, the exact commit that produced the corresponding artifact. A consumer that reads v3.2.7 - a deploy script, a GitOps controller, a downstream package index - trusts the tag to point at the right commit. Branch protection defends the branch; tag protection defends the release artefact. The two are siblings, and both are necessary: a repository with branch protection only is a repository whose release tags can be silently rewritten, with no protection rule to detect it.

This lesson walks the three rule sets on tags: refusing tag deletion, restricting who can create or move a tag, and the signed-tag enforcement that ties a tag to a key.

Refusing tag deletion

The first rule is the simplest: a tag, once created, cannot be deleted. If the tag exists on the server, the server refuses to remove it. On GitHub this is a tag-protection rule with “Do not allow tag deletion” enabled. On GitLab the same is exposed as “Allowed to delete: No one”. On Bitbucket, branch-permissions are slightly different - tags live under their own permissions configuration.

Why this matters: a deploy system that pins to v3.2.7 must trust that v3.2.7 resolves to the same commit tomorrow as it did today. If v3.2.7 can be deleted, then re-created pointing at a different SHA, every consumer pinning to the tag is silently rerouted. The audit trail of what v3.2.7 pointed at when it was originally created is what survives the delete - and only if the consumers preserve it themselves. A tag-deletion protection makes the delete itself impossible, so the reroute cannot happen on the server.

git push origin --delete v3.2.7

The above command, against a server with tag-deletion protection on v*, is refused with an error. The error message names the rule and the tag pattern.

Restricting tag creation and movement

The second rule is access control on tag create and tag force- update. Without this rule, any user with write access can create a tag and any user with delete-and-recreate permission can move a tag. The pattern of production releases is narrower:

flowchart LR
    A[Push tag attempt] --> B{Tag matches pattern?}
    B -->|no| C[Server refuses]
    B -->|yes| D{Actor permitted?}
    D -->|no| E[Server refuses]
    D -->|yes| F[Server accepts]
  • Allowed to create. The forge’s tag-protection rule lists users, teams, or roles who can create or move a tag matching the pattern. Typically this is a release-engineering role or the CI service account that runs the release pipeline.
  • Pattern specificity. Production release tags follow a pattern - vX.Y.Z, release-*, production-*. The rule matches the pattern and applies only to those tags. Feature branches or scratch tags can be created more freely.

The operational discipline is to wire tag creation to the release pipeline. The pipeline is the only system that creates production release tags; humans do not create them by hand. The forge’s tag-protection rule allows the pipeline’s service account and forbids other actors. A human-driven tag is refused; a pipeline-driven tag is accepted.

The rule around tag movement (a force-push of a tag to point at a different SHA) is conceptually distinct. Git itself does not natively allow a tag to move without first being deleted and re-created; the rule against deletion is therefore the rule against movement. Forges differ in how they surface this - some have a separate “do not allow force updates” toggle on tag protection - but the underlying prohibition is the same.

Signed-tag enforcement

The third rule ties a tag to a key. A signed tag carries a GPG or SSH signature from a specific key; anyone who has the tag and the public key can verify that the tag was created by the holder of the private key. The forge-side rule is configured in two layers:

git tag -s v3.2.7 8a3f9d2
git push origin v3.2.7

The above sequence creates a signed tag locally and pushes it. The forge-side rule then decides what to accept:

  • GitHub: A “Require signed commits” branch or tag rule refuses a tag that is not signed with a verified key. A tag signed by an unverified key - or a key whose email does not match the committer - is refused at push time.
  • GitLab: A “Reject unsigned commits” or “Reject unsigned tags” rule exposes the same check.
  • Forges that do not surface a tag-specific signed-rule: The team implements the rule at the CI side: the release pipeline refuses to publish a release unless the tag was created with -s and the signature verifies.

The signature’s role is two-fold. First, it ties the tag to a key, so an attacker who can push a tag cannot push it under the release engineer’s identity without the private key. Second, it binds the tag to a specific point in time: the signature carries the timestamp at which it was produced, and consumers can verify that the binding was made when the key holder signed it, not retrospectively.

git tag -d v3.2.7
git push origin --tags

The first command deletes a tag locally; the second propagates the delete. On a server with tag-deletion protection, the push of the deletion is refused. The signed-tag enforcement is a complement: even if a tag is deleted and recreated, the new tag is unsigned and the rule refuses it; the original signed tag is preserved in the local repository as a verifiable reference. A consumer that pinned the original tag and recorded its signature can verify that any recreated tag is not the same artefact.

Tag protection as part of the release stack

Tag protection is the third leg of the release stack alongside branch protection (which protects the branch the tag points at) and signing (which protects the commit the tag references). A production repository has all three:

flowchart LR
    A[Branch protection] --> F[Release stack]
    B[Tag protection] --> F
    C[Signed commits and tags] --> F
    D[Tag-deletion lock] --> F

Each rule covers a different outcome:

  • Branch protection prevents a bad merge from landing on the branch.
  • Signing prevents a bad commit from being attributed to a release engineer.
  • Tag protection prevents a release tag from being silently rewritten to point at a bad commit.
  • Tag-deletion lock prevents a release tag from disappearing entirely.

A production release is verifiable: the tag can be fetched, the signature verified, the commit the tag points at inspected, the PR that landed the commit reconstructed, the protections that were in force on the PR confirmed.

Production discipline

Four rules for tag protection on a production repository:

  1. Production tags cannot be deleted. The pattern covers every release tag and forbids both deletion and movement.
  2. Production tags can only be created by the release pipeline. The forge’s tag-protection rule allows the pipeline’s service account; humans cannot create or move production tags.
  3. Production tags are signed. The pipeline refuses to publish a tag whose signature does not verify with a key whose email is on the release team.
  4. Tag-deletion and signed-tag rules are configured together. A signed-tag rule without a tag-deletion lock is incomplete; the deletion can erase the signed artefact. Both are necessary.

Cross-course references

  • Linux for Production Sysadmins - Part XIV (AuditLogging) covers the OS-level analogue: archive signing and signature verification on package artefacts.
  • Ansible for Production Sysadmins - Part XXXVIII (QualGates) covers the same pattern in pipeline-produced artefacts.
  • Terraform for Production Sysadmins - Part XXXIII (StateGuard) covers the same pattern on Terraform module tags and infra-reference tags.

Quiz

Knowledge check · 4 questions

  1. Q1. A tag named v3.2.7 was deleted and recreated by an engineer pointing at a different commit, despite a tag-protection rule. What typically explains the discrepancy between the rule and the actual state?

  2. Q2. Deleting a release tag and re-creating it on a different commit is an acceptable recovery for a release-time incident because the tag is just a pointer and pointers can be moved.

  3. Q3. List the three tag-protection rules that together form a complete release-artefact stack.

  4. Q4. Diagnose why a signed tag fails to push despite a tag-protection rule that lists the release pipeline as an allowed actor.

    The release pipeline attempts to push a signed tag v3.2.8 to the production repository. The forge rejects the push with a message about the signature. The pipeline's service account is in the allowed-to-create list. The team can verify the tag's signature locally with the release key and is convinced the tag is correct.

Passing score: 75%. Answers are checked in this browser.