Releases, Tags, Release CLI, Evidence, Changelogs, and Deployment Traceability: Guided Hands-On Workflow and Core Operations
Create a disposable release from an exact commit, inspect it through Git/glab/API, attach only synthetic or durable package-style links, generate a controlled changelog, verify identity, and clean up safely.
Learning objectives
- Inspect exact commit/tag/release state before creating anything.
- Create a disposable tag and GitLab Release using UI/glab/API-safe patterns.
- Attach synthetic or durable package-style links instead of fragile expiring job-artifact URLs.
- Generate a controlled changelog from commits with Git trailers.
- Verify commit, tag, release, asset identity, and evidence before cleanup.
glab release delete documentation requires
Maintainer-or-higher, so the fully scripted glab cleanup path is
Maintainer-scoped; Developer-level learners can use the documented
UI/Releases API path where permitted or simulate cleanup. On-demand
evidence recollection is Premium/Ultimate and limited to
Self-Managed/Dedicated; retaining report artifacts as release evidence
is Ultimate. release-cli was deprecated in GitLab 18.0
and is scheduled for removal in 20.0; use glab or the
Releases API for new automation. The mandatory labs use only a
disposable project/tag, synthetic asset links or package/image
coordinates, and no paid feature.
1. Disposable scenario and safety boundary
Create a private disposable GitLab project or a clearly isolated branch/tag in an existing throwaway project. The lab never publishes a real customer release, never points at production artifacts, and never uses a persistent access token unless you deliberately choose an optional API exercise.
Mandatory path: GitLab Free, Developer-or-higher
access for create/update operations; use Maintainer-or-higher for
the fully scripted glab release delete cleanup shown
later, or use the documented UI/Releases API path/simulation where
your role permits. Git + glab are used, and a runner is
needed only for the optional CI extension. Use tag
v0.24.0-lab, synthetic notes, and an
example.invalid asset URL or a durable disposable
package/image coordinate created in earlier chapter-style fixtures.
2. Preflight: prove project, branch, HEAD, tags, and release absence
Do not create a release until the intended source is recorded:
git fetch --tags --prune
git status --short
PROJECT_PATH="$(glab repo view -F json | python -c 'import json,sys; print(json.load(sys.stdin).get("path_with_namespace", ""))')"
HEAD_SHA="$(git rev-parse HEAD)"
printf "project=%s\nhead=%s\n" "$PROJECT_PATH" "$HEAD_SHA" | tee ch24-preflight.txt
TAG=v0.24.0-lab
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then echo "tag already exists" >&2; exit 1; fi
if glab release view "$TAG" -F json >/dev/null 2>&1; then echo "release already exists" >&2; exit 1; fi
If glab repo view output differs on your installed
version, use the project URL/UI and record it manually. The required
invariant is not the convenience command; it is unambiguous
project/ref/SHA evidence.
3. Create two controlled changelog commits
Use harmless text files so the changelog range is deterministic:
git switch -c ch24/release-lab
printf "release lab base\n" > ch24-release.txt
git add ch24-release.txt
git commit -m "Add release lab fixture
Changelog: added"
BASE_SHA="$(git rev-parse HEAD)"
printf "release lab verification\n" >> ch24-release.txt
git add ch24-release.txt
git commit -m "Add release verification note
Changelog: fixed"
RELEASE_SHA="$(git rev-parse HEAD)"
printf "base=%s\nrelease=%s\n" "$BASE_SHA" "$RELEASE_SHA"
Push only the disposable branch if a hosted Release will be created from it.
4. Prediction ledger before mutation
| Prediction | Expected state |
|---|---|
| Tag |
v0.24.0-lab points to exactly
RELEASE_SHA.
|
| Release | One Release object exists for that tag, with synthetic notes. |
| Assets | One synthetic/durable link; no secret-bearing URL and no expiring job-artifact dependency in the mandatory path. |
| Evidence | Automatic evidence file/evidence SHA becomes visible with the Release. |
| Deployment | None is created merely by creating a Release. |
| Cleanup | Release is deleted first, then disposable tag/branch; deleting the Release alone must leave the tag. |
5. Create and independently inspect the Git tag
Create an annotated test tag so Git-native metadata is visible:
TAG=v0.24.0-lab
git tag -a "$TAG" "$RELEASE_SHA" -m "Disposable Chapter 24 release"
git show-ref --tags "$TAG"
git rev-list -n 1 "$TAG"
git cat-file -t "$TAG"
git show "$TAG" --no-patch --decorate
git rev-list -n 1 gives the commit reached by the tag.
For an annotated tag, git show-ref can identify the tag
object itself, so do not compare the tag-object SHA to the commit
SHA by accident.
6. Push only the disposable tag
Push the branch first, then the tag. If a protected-tag rule rejects the push, preserve the rejection and do not weaken governance merely to finish the lab. Either choose a permitted disposable tag pattern or use the local/API simulation path.
git push -u origin ch24/release-lab
git push origin "refs/tags/$TAG"
# Re-read from the remote-tracking context afterward:
git ls-remote --tags origin "refs/tags/$TAG" "refs/tags/$TAG^{}"
7. Create the Release with glab
Use glab release create against the explicit tag. A
release can also be created through the UI or
POST /projects/:id/releases. Keep the notes synthetic:
cat > ch24-notes.md <<'EOF'
## Chapter 24 disposable release
- Source: controlled lab commits
- Artifact identity: synthetic only
- No production deployment
EOF
glab release create "$TAG" --name "Chapter 24 Lab $TAG" -F ch24-notes.md
If the Release already exists unexpectedly, stop and inspect instead of deleting it blindly. Existing state may belong to a previous lab attempt.
8. Verify Release metadata through glab and API
Read after write from two surfaces:
glab release view "$TAG" -F json > ch24-release.json
glab release list -F json --per-page 20 > ch24-releases.json
# API equivalent (do not print token values):
# GET /api/v4/projects/:id/releases/:tag_name
Compare release.commit.id with
git rev-list -n 1 "$TAG". Record
evidence_sha and the evidence file path if returned.
9. Add one durable/synthetic asset link and record immutable identity separately
For the no-paid path, use an inert URL such as
https://example.invalid/releases/v0.24.0-lab/artifact.txt
and write the expected synthetic SHA-256 in the release
notes/evidence ledger. If you have a disposable Generic Package or
OCI image from Chapters 22–23, link that durable coordinate instead
and record its package SHA-256 or OCI digest separately.
cat > ch24-asset-ledger.txt <<'EOF'
asset_name=chapter24-synthetic-artifact
asset_url=https://example.invalid/releases/v0.24.0-lab/artifact.txt
asset_sha256=fixture:4d3f...replace-with-real-hash-if-using-real-disposable-package
EOF
Asset links do not make mutable URLs immutable. The ledger’s hash/digest is the identity evidence.
10. Generate a controlled changelog
Generate Markdown from the controlled commit range. The version must follow semantic versioning; because the lab tag contains a suffix, pass an explicit semantic version to the changelog command:
glab changelog generate --version 0.24.0 --from "$BASE_SHA" --to "$RELEASE_SHA" > ch24-generated-changelog.md
cat ch24-generated-changelog.md
Expected entries come from commits carrying the
Changelog trailer. Review the generated Markdown; do
not automatically treat it as customer-ready notes.
11. Optional CI creation path with job-scoped authentication
For a disposable tag pipeline, current GitLab supports
glab authentication with the job token through CI
auto-login:
release_with_glab:
stage: release
image: registry.gitlab.com/gitlab-org/cli:latest
rules:
- if: $CI_COMMIT_TAG == "v0.24.0-lab"
variables:
GLAB_ENABLE_CI_AUTOLOGIN: "true"
script:
- |
if glab release view "$CI_COMMIT_TAG" -F json > release-existing.json; then
echo "Release exists; compare identity before any update."
else
echo "Release lookup failed. Verify that the cause is a genuine not-found result, not authentication/authorization, before creating." >&2
exit 1
fi
This fixture demonstrates an inspect-then-create idempotence
pattern. Do not set GITLAB_TOKEN equal to
CI_JOB_TOKEN.
12. Verify the release chain independently
Produce one evidence file:
TAG_COMMIT="$(git rev-list -n 1 "$TAG")"
RELEASE_COMMIT="$(python - <<'PY2'
import json
print(json.load(open("ch24-release.json"))["commit"]["id"] )
PY2
)"
printf "tag_commit=%s\nrelease_commit=%s\n" "$TAG_COMMIT" "$RELEASE_COMMIT" | tee ch24-identity.txt
test "$TAG_COMMIT" = "$RELEASE_COMMIT"
If using a real disposable package/image link, also verify its package checksum or OCI digest from its registry API and compare that value with the asset ledger.
13. Challenge: choose the correct surface
Your team needs an immutable binary for a release, a human-readable summary, and a machine-readable audit snapshot. Which GitLab surfaces should own each?
Expected reasoning: store bytes in a durable package/OCI registry and verify checksum/digest; use the GitLab Release for human-facing version metadata and links; use release evidence plus pipeline/deployment/registry evidence for audit. Do not upload a binary only as an expiring job artifact and call the problem solved.
14. Cleanup in an evidence-preserving order
First save the final Release JSON, tag→commit mapping, asset ledger, changelog, and any evidence JSON URL/metadata. Then delete the Release and prove the tag still exists. Finally delete the disposable tag and branch.
glab release view "$TAG" -F json > ch24-release-final.json
glab release delete "$TAG" --yes # current glab docs: Maintainer-or-higher
# Release deletion must not delete the Git tag:
git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null
# Now remove the disposable tag and branch:
git push origin ":refs/tags/$TAG"
git push origin --delete ch24/release-lab
# Verify absence:
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then echo "tag cleanup failed" >&2; exit 1; fi
If the tag is protected, follow the protected-tag UI/API permission model instead of bypassing protection. Never force-delete or weaken a production release tag rule for a lab.
Knowledge check
Why inspect the tag and Release independently after creation?
Because they are separate objects. The Release metadata must resolve to the exact commit reached by the tag.
Why is an expiring job-artifact URL a weak long-lived release asset?
The artifact can expire or be deleted, leaving the release link broken even though the Release metadata remains.
What is the safe CI job-token configuration for glab release operations?
Set GLAB_ENABLE_CI_AUTOLOGIN=true; do not assign
CI_JOB_TOKEN to GITLAB_TOKEN.
After deleting a Release, should its tag still exist?
Yes. Release deletion does not delete the associated Git tag.
What should happen if a protected-tag rule blocks the lab tag?
Preserve the evidence and use an allowed disposable tag/simulation. Do not weaken production governance merely to complete the lab.
Summary
The workflow created a controlled commit range, annotated tag, Release metadata, durable/synthetic asset identity, generated changelog, and independent identity checks. Cleanup proved the Release/tag deletion semantics instead of assuming them.
Official references
Primary sources used for the current GitLab 19.3 behavior taught in this lesson:
- GitLab Docs — Releases
- GitLab Docs — Project releases API
- GitLab Docs — Release links API
- GitLab Docs — Release evidence
- GitLab Docs — Release fields and assets
- GitLab Docs — release-cli (deprecated)
- GitLab CI/CD YAML — release keyword
- GitLab CLI — release
- GitLab CLI — release create
- GitLab CLI — release view
- GitLab CLI — release list
- GitLab Docs — Changelogs
- GitLab CLI — changelog generate
- GitLab Docs — Repositories API changelog endpoints
- GitLab Docs — Protected tags
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.