Git, CI/CD & GitOpsXXXIII · Commit and Tag SigningSigningTagsVsCommits
Signing tags versus commits — both can be signed; tags survive rebases
What you'll learn
- Explain why a signed tag survives rebases while a signed commit does not
- Distinguish what a signed commit attests from what a signed tag attests
- Use git tag -s, git tag -u <keyid>, and tag.gpgsign to produce signed release tags
- Combine signed commits on a branch with a signed tag at the release point as the production pattern
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
Both commits and tags can be signed, but they answer different questions. A signed commit attests the moment a change was recorded: the holder of this key made this commit at this time, against this tree. A signed tag attests the moment a release was cut: the holder of this key tagged this commit with this message, as the release point. The production pattern is signed commits on every change plus a signed tag at every release.
What survives a rebase — and what does not
A commit that is signed and then rebased is a new commit whose SHA has changed; the old signature covers the old bytes, and the new bytes have no signature. Rebasing erases commit signatures. A tag, by contrast, points at a commit and is not part of the commit graph — rebasing the underlying commit does not move the tag.
flowchart LR
A["commit A\nsigned"] --> B["rebase"]
B --> C["commit A'\nnew SHA\nold signature invalid"]
A --> D["tag v1.0.0\nsigned"]
D --> E["rebase"]
E --> F["tag v1.0.0\nsame tag object\nsignature still valid"]
A signed commit is a fragile attestation — invalidated by any history rewrite (rebase, amend, force-push). A signed tag is a robust attestation — survives history rewrites because the tag itself is not part of the rewritten history.
What a signed commit attests
A signed commit covers the tree, the parent commit references, the author and committer identities and timestamps, and the commit message. It does not cover any subsequent commit on the branch, the branch name, or any tag pointing at the commit.
git cat-file -p HEAD
# tree 8a3f9d2...
# parent 9b4c0e3...
# author Alice Engineer <alice@corp.example.com> ...
# committer Alice Engineer <alice@corp.example.com> ...
#
# fix: typo in production deploy script
# gpgsig -----BEGIN PGP SIGNATURE-----
# iQEzBAABCgAdFiEE...
# =/WvN
# -----END PGP SIGNATURE-----
The signature is in the gpgsig header (or ssig for SSH).
It covers everything else in the commit object minus the
signature itself.
What a signed tag attests
A signed tag covers the tagged commit (the SHA in the
object header), the tag name, the tagger identity and
timestamp, and the tag message. It does not cover the
contents of the tagged commit’s tree (the tag attests
which commit, not what the commit contains) or any other
tag in the repository.
git tag -s v1.0.0 -m "Release v1.0.0: hotfix for canary rollout"
git tag -u "ops-release@example.com" v1.0.0 -m "Release v1.0.0"
git cat-file -p v1.0.0
# object 8a3f9d2...
# type commit
# tag v1.0.0
# tagger Ops <ops@example.com> ...
#
# Release v1.0.0
# -----BEGIN PGP SIGNATURE-----
# ...
# -----END PGP SIGNATURE-----
A signed tag is the production unit of release identity. The GitOps controller reads the tag, the deploy script reads the tag, the consumer pins to the tag.
The production pattern — signed commits plus signed tags
flowchart LR
A["branch main"] --> B["commit 1\nsigned"] --> C["commit 2\nsigned"] --> D["commit 3\nsigned"] --> E["tag v1.0.0\nsigned"] --> F["release artifact\nbuilt from v1.0.0"]
Every commit on the development branch is signed
(commit.gpgsign true makes this automatic). The release
commit is tagged with a signed tag (git tag -s in the
release pipeline). The release artifact is built from the
tag, not from the branch HEAD — the tag is stable; the
branch HEAD moves.
COMMIT_SHA=$(git rev-parse HEAD)
git tag -s "v$CURRENT_VERSION" -m "Release v$CURRENT_VERSION" "$COMMIT_SHA"
git push origin "v$CURRENT_VERSION"
The pipeline pushes only the tag (not the branch HEAD), so the release artefact’s identity is the tag’s signature.
Lightweight tags cannot be signed
A lightweight tag is a direct ref to a commit — no tag
object exists. There is no payload to sign, so git tag -s
on a lightweight tag creates an annotated tag object and
signs it. The signed tag is always an annotated tag.
git tag -s v1.0.0 -m "Release" "$COMMIT_SHA"
git tag v1.0.0 "$COMMIT_SHA" # lightweight, cannot be signed
The discipline is to never use lightweight tags for release artefacts.
Production discipline
- Sign every commit on the development branch.
commit.gpgsign trueat global scope. - Sign every release tag.
git tag -sin the release pipeline;tag.gpgsign truefor ad-hoc tagging. - Build the release artifact from the tag, not the branch HEAD. Pinning to the tag pins the artifact to a specific commit and signature.
- Do not rebase commits that have signed tags on top of them. The rebase invalidates the tag’s signature.
- Use
-u <keyid>in CI. The default key may not be the team’s release key; specifying the key avoids silent fallback.
Cross-course references
- Git, CI/CD & GitOps — Part XIX (Tags and releases) — release-pipeline patterns that consume signed tags.
- Git, CI/CD & GitOps — Part XXVI-06 (Signing configuration) — toggles and key references.
- Git, CI/CD & GitOps — Part XXXII-05 (Tag protection) — forge-side rules that prevent silent tag overwrites.
- Git, CI/CD & GitOps — Part XXX (Supply chain) — the upstream consumer that pins to signed tags.
Quiz
Knowledge check · 4 questions
Q1. A release pipeline runs `git tag -s v1.0.0 -m 'Release' $COMMIT_SHA` and then someone force-pushes the branch with a rebase. What happens to the signed tag?
Q2. A signed tag attests the contents of the tagged commit (the file changes), so a release artifact built from a signed tag is not automatically attested end-to-end.
Q3. Explain why a signed tag is more useful than a signed commit as the unit of release identity, naming the operation that breaks one but not the other.
Q4. Diagnose a release where the signed tag is valid but the audit team rejects the release because the underlying commits are not signed.
A team has `tag.gpgsign true` set globally. The release pipeline runs `git tag -s v2.1.0 -m 'Release' $COMMIT_SHA && git push origin v2.1.0`. Six months later, an incident traces back to a change on the v2.1.0 branch. `git log --pretty='%h %an %G? %s' main` shows a mix of `G` and `N` for the commits leading up to v2.1.0.
Passing score: 75%. Answers are checked in this browser.