Tags, Releases, Release CLI, Changelogs, Evidence, Asset Links, and Release-Orchestration Pipelines: Concepts, Architecture, and Mental Model
Model a release as an immutable relationship among commit SHA, tag, verified artifact digest, release record, evidence, asset links, authorization, and consumer verification.
Learning objectives
- Explain why a Git tag, a GitLab release record, an artifact, and a deployment are distinct objects.
- Trace a release from exact commit SHA and verified artifact digest through tag, release record, evidence, asset link, and consumer verification.
-
Describe current
releasekeyword andglabbehavior, including the danger of implicit tag creation from a moving default branch. - Distinguish base release evidence from additional evidence collection and from your own digest/provenance evidence.
- Inspect tag/ref/SHA, release identity, changelog/evidence, asset identity, and authorization without mutating the project.
1. The practical problem: “v1.4.0 exists” does not prove what was shipped
Chapter 19 separated deployment-job success, GitLab deployment history, and real target health. Chapter 21 added deployment authorization. Release engineering adds another trust boundary: the version label that humans consume must resolve to the exact commit and exact bytes that were verified. A release page can look complete while its tag points at the wrong commit, its binary was rebuilt later, or an asset link silently points at mutable content.
The safe release question is therefore not “did the release job turn green?” It is “which SHA was tagged, which bytes were approved, which identity created the record, what evidence was captured, and can a consumer independently retrieve and hash the same bytes?”
2. Terms before configuration
| Term | Meaning here | Do not confuse it with |
|---|---|---|
| Git tag | A repository ref that names a commit (lightweight) or tag object (annotated). | A GitLab release record. A tag can exist without a release. |
| Release | GitLab metadata associated with a tag: name, notes, evidence, assets, timestamps, milestones. | The artifact bytes themselves. |
| Release job |
A CI job using the YAML release keyword.
Current GitLab executes release creation through
glab.
|
An ordinary script-only job that happens to run on a tag. |
glab |
GitLab CLI. glab release create can create or
update a release and should be given an explicit
--ref when it may create a tag.
|
Deprecated release-cli. |
| Release evidence | GitLab JSON snapshot collected for a release. Base evidence is generated with the release; some extra evidence capabilities have narrower tiers/offerings. | A cryptographic signature or proof that your binary matches source. |
| Asset link | A URL displayed with a release, optionally with a direct asset path/type. | Immutable storage automatically. The target can still be mutable. |
| Changelog | Markdown generated from commit titles/trailers or curated by humans. | Provenance. Notes describe change; they do not hash the artifact. |
| Protected tag | Rule limiting who may create matching tags and preventing accidental mutation/deletion. | A guarantee the release artifact itself is immutable. |
3. State model: record all identities before publication
| State layer | Evidence to capture | Why release correctness depends on it |
|---|---|---|
| Source/revision |
CI_PIPELINE_SOURCE, ref,
CI_COMMIT_SHA, tag object/target SHA
|
Proves exactly which repository state the release claims to represent. |
| Compiled configuration | Merged YAML, release-job inclusion/rules, image/tool identity | Proves which orchestration GitLab actually compiled. |
| Job/runner |
Pipeline/job IDs, actor, runner/executor,
glab --version
|
Separates configuration intent from execution context. |
| Identity/trust |
CI_JOB_TOKEN or narrowly scoped alternative,
protected-tag rule
|
Shows who was authorized to create/update the tag/release. |
| Artifact/evidence | Artifact SHA-256, evidence snapshot path, changelog input range | Connects release metadata to exact bytes and review evidence. |
| External/distribution | Asset URL, immutable path/digest, consumer re-download digest | Proves consumers retrieve the same object you verified. |
| Governance/recovery | Exact tag/release ID, superseding record, deletion decision | Makes rollback or cleanup bounded instead of “delete latest”. |
4. Mental model: verified candidate → immutable identity → release record → consumer proof
Start with a verified commit and artifact digest. Choose an exact
version/tag name and bind that tag to the intended SHA. The release
job, glab, or Releases API then creates the GitLab
release record. GitLab captures release evidence and displays asset
links. A consumer fetches the exact tagged source or immutable asset
and verifies its digest. If a problem appears later, publish a new
superseding version or delete only the explicitly identified lab
release; do not mutate “latest” as if it were an identity.
flowchart LR
A[Verified commit SHA] --> B[Verified artifact digest]
B --> C[Exact tag/version]
C --> D[release keyword / glab / API]
D --> E[GitLab release record]
E --> F[Evidence + changelog + asset links]
F --> G[Consumer fetches exact version]
G --> H[Digest / provenance verification]
H --> I[Supersede, deprecate, or bounded cleanup]
Each arrow is a contract. If the tag is implicit, if the asset URL moves, or if cleanup uses “latest,” the chain no longer identifies one reproducible object.
5. Current release-job behavior: declarative, but still stateful
The current CI/CD YAML reference documents release as a
job keyword. The job must still have a script; the
release action runs after the main script succeeds and before
after_script. The runner must have a sufficiently
recent glab in PATH. If a release with the
same tag already exists, the YAML release action does not silently
update it; release creation fails, which is useful protection
against accidental rewriting.
release_candidate:
stage: release
image: registry.gitlab.com/gitlab-org/cli:latest
rules:
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/'
script:
- glab --version
- printf '%s %s\n' "$ARTIFACT_SHA256" "dist/app.tar.gz"
release:
tag_name: "$CI_COMMIT_TAG"
name: "Release $CI_COMMIT_TAG"
description: "release-notes.md"
If release:tag_name names a tag that does not yet
exist, GitLab can create that tag. The safer production pattern is
to make the ref relationship explicit: either trigger from a
pre-created protected tag or provide an exact
release:ref / glab --ref based on a
recorded SHA.
6. glab and API identity: exact ref first, token second
glab release create can create a new release or update
an existing one. Current CLI docs warn that if the tag does not
exist and no --ref is supplied, the new tag can be
created from the latest state of the default branch. That
convenience is unacceptable when a release candidate was already
verified at another SHA.
LAB_TAG="v0.0.0-ch22-lab.${CI_PIPELINE_ID}"
EXACT_SHA="$CI_COMMIT_SHA"
GLAB_ENABLE_CI_AUTOLOGIN=true \
glab release create "$LAB_TAG" \
--ref "$EXACT_SHA" \
--name "Disposable $LAB_TAG" \
--notes-file release-notes.md
The Releases API accepts CI_JOB_TOKEN in a
JOB-TOKEN header. For glab in CI, the
recommended job-token path is
GLAB_ENABLE_CI_AUTOLOGIN=true. Do
not set GITLAB_TOKEN to the value of
CI_JOB_TOKEN; that variable is sent as a private token
and the Releases API does not interpret it as a job token.
7. Evidence: GitLab snapshot plus your own byte identity
When a release is created, GitLab generates release evidence JSON. Treat that as a useful audit snapshot, not as a substitute for an artifact digest or signature. Your evidence packet should include the release’s tag/SHA, pipeline and job IDs, tool version, the exact artifact SHA-256, the release/evidence URL, and the final consumer-side digest check.
| Evidence | What it proves | What it does not prove |
|---|---|---|
| Release evidence JSON | Snapshot of GitLab data associated with the release. | That an external binary has not changed after publication. |
| SHA-256 manifest | Exact bytes of one artifact. | Who authorized publication. |
| Protected-tag rule | Who may create matching tag refs. | That all release notes/assets are correct. |
| Pipeline/job IDs | Execution lineage and timestamps. | External consumer retrieved the intended object. |
| Consumer re-hash | Downloaded bytes equal the published digest. | Source-to-binary reproducibility unless the build is independently reproducible. |
8. Changelogs: describe the change range explicitly
GitLab changelogs are Free-tier and can be generated from commit
titles and Git trailers. glab changelog generate has
existed since glab 1.30.0. For release automation,
prefer explicit --from, --to, and
--version inputs rather than relying on ambient
HEAD or whichever tag happens to be considered latest.
PREVIOUS_TAG="v1.3.0"
TARGET_SHA="$CI_COMMIT_SHA"
TARGET_VERSION="1.4.0"
glab changelog generate \
--from "$PREVIOUS_TAG" \
--to "$TARGET_SHA" \
--version "$TARGET_VERSION" \
> release-notes.md
Generated notes are deterministic only to the extent that their input range and changelog configuration are pinned. Human curation can improve communication, but should not silently change the commit/artifact identity that the release represents.
9. Asset links: a link is not an immutable artifact
Release asset links may point to HTTP, HTTPS, or FTP resources. Their metadata can be stable while their target content changes. Direct job-artifact links are particularly fragile because job artifacts can expire or be manually removed. Prefer immutable package/container coordinates or another content-addressed/versioned store; Chapter 23 develops that distribution model in detail.
/latest/ the release identity.
10. Inspect first: read-only checks before creating or changing anything
# Repository identity
git rev-parse HEAD
git tag --points-at HEAD
git show-ref --tags --dereference
# In CI: print identifiers, not secrets
printf 'source=%s ref=%s sha=%s pipeline=%s job=%s\n' \
"$CI_PIPELINE_SOURCE" "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" \
"$CI_PIPELINE_ID" "$CI_JOB_ID"
glab --version
GLAB_ENABLE_CI_AUTOLOGIN=true glab release list --repo "$CI_PROJECT_PATH"
Do not dump the environment to “see what token is present.” Inspect only non-secret identifiers and authorization results.
11. Common mistakes and safer patterns
| Mistake | Why it breaks release integrity | Safer pattern |
|---|---|---|
| Create missing tag without explicit ref | A CLI may tag the moving default branch instead of the verified candidate. |
Pre-create the tag or pass the exact SHA with
--ref.
|
| Rebuild during release | Bytes can differ from the tested artifact. | Promote/link the already verified artifact and its digest. |
Use latest for cleanup |
Ordering is not identity; you may delete an unrelated release. | Require the exact lab tag and guard its prefix/SHA. |
| Use broad PAT by default | Credential can mutate unrelated project state. |
Prefer CI_JOB_TOKEN where the Releases API
supports it; otherwise use the narrowest project identity.
|
| Treat release evidence as artifact integrity | Evidence snapshots are metadata, not a content-address guarantee. | Publish and independently verify artifact digests/provenance. |
12. Summary and evidence checklist
- Record the exact SHA before creating a tag or release.
- Bind the tag to that SHA explicitly.
- Reuse the already verified artifact; do not rebuild for publication.
- Record artifact digest, pipeline/job IDs, release tag, creator identity, evidence URL, and asset coordinates.
- Use protected tags to narrow who can create release refs.
- Let consumers fetch an exact version and verify its digest.
- Supersede or delete by exact tag—not by “latest”.
Knowledge check
Why is glab release create v1.4.0 risky if
v1.4.0 does not exist yet?
Without an explicit --ref, the CLI can create the
tag from the latest default-branch state rather than the SHA you
previously verified.
Does GitLab release evidence replace an artifact SHA-256?
No. Release evidence is an audit snapshot of GitLab-associated data. A digest identifies the exact artifact bytes.
What is the current recommended CI authentication path for
glab with a job token?
Set GLAB_ENABLE_CI_AUTOLOGIN=true and let
glab use CI_JOB_TOKEN as a job token.
Do not assign it to GITLAB_TOKEN.
A release asset URL is stable. Does that prove the file behind it is immutable?
No. The target can change. Verify an immutable coordinate and/or a digest.
Why should cleanup not select the “latest” release?
Latest is an ordering convenience, not a unique identity. Cleanup must target the exact disposable tag/release that the lab created.
Version and compatibility note
GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.
Official references and version notes
Documentation verification date: 2026-09-12.
Releases, changelogs, the Releases API, release links, base release
evidence, and role-based protected tags are documented across
Free/Premium/Ultimate unless a narrower tier is explicitly called
out. The current YAML release flow uses
glab; current docs require glab 1.58.0 or
later. release-cli was deprecated in GitLab 18.0 and is
planned for removal in 20.0. Base release evidence is available on
all tiers; the API action to collect additional evidence snapshots
is documented for Premium/Ultimate on Self-Managed/Dedicated, and
inclusion of some report artifacts has narrower tier requirements.
- Releases — official reference.
- CI/CD YAML release keyword — official reference.
- GitLab Release CLI migration — official reference.
- glab release create — official reference.
- Changelogs — official reference.
- Release evidence — official reference.
- Project Releases API — official reference.
- Release links API — official reference.
- Protected tags — official reference.
- CI job token — official reference.
Current assumptions used in this chapter: Version-sensitive YAML, Runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations must be verified against the exact GitLab, GitLab Runner, tool, and external-system versions used in production. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for reproducible work.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.