Checkpoint Lab — Package Registry, Dependency Proxy, Package Formats, Permissions, and Artifact Distribution
Publish, consume, verify, diagnose, and remove one synthetic Generic Package while proving URL, version, checksum, credential lifetime, downstream impact, and cleanup evidence.
Learning objectives
- Predict package URL/version/project before publication.
- Prove publish and consume through short-lived job identities.
- Compare deterministic content with registry checksum/provenance metadata.
- Diagnose one controlled missing-file failure without widening privilege.
- Remove the synthetic package and any optional token only after verified impact analysis.
1. Checkpoint scenario and success criteria
You own a disposable project and need to distribute one tiny text payload as if it were a release binary. You must publish with the narrowest practical CI identity, consume the exact version, prove content and producer provenance, diagnose one missing-package-style failure without broadening permissions, and remove the synthetic package after documenting downstream impact.
Success is not “curl returned 200.” Success means the package coordinate was predicted, the published bytes map to a commit/pipeline, the consumer obtained exactly those bytes, no persistent credential was created, and deletion is independently verified.
2. Preflight and assumptions
- GitLab: current 19.3-compatible behavior; Free path.
- Project: fresh disposable project; Package Registry enabled.
- Role: project Owner/Maintainer for final cleanup; publication itself needs no manually created token.
- Runner: tiny runner access, or use the no-runner simulation and still complete all predictions/evidence design.
- No production state: no valuable packages, deployments, or external consumers.
-
Secret rule: do not echo
CI_JOB_TOKEN, do not enableCI_DEBUG_TRACE, and do not create a deploy token unless doing the explicitly optional external-consumer extension.
3. Write the inclusion/identity table before running
Record these predictions in
evidence/ch22/predictions.md:
| Prediction | Expected state |
|---|---|
| Package owner | Exactly the disposable project; no group-level ownership transfer. |
| Package coordinate |
ch22-checkpoint / 0.0.<CI_PIPELINE_IID> /
payload.txt.
|
| Publish identity | Producer job’s ephemeral CI_JOB_TOKEN. |
| Consumer identity |
Consumer job’s own ephemeral CI_JOB_TOKEN.
|
| Content | Deterministic line containing exact commit SHA and pipeline ID. |
| Required jobs | Publish → verify → safe missing-file diagnostic (optional/manual). |
| Persistent secret | None in mandatory path. |
| Cleanup | Delete exact package ID/version only after evidence capture. |
4. Build the checkpoint pipeline
stages: [publish, verify]
publish_checkpoint_package:
image: alpine:3.22
stage: publish
before_script:
- apk add --no-cache curl
script:
- VERSION="0.0.${CI_PIPELINE_IID}"
- printf 'chapter22-checkpoint|sha=%s|pipeline=%s\n' "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" > payload.txt
- sha256sum payload.txt
- |
curl --fail-with-body --location \
--header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
--upload-file payload.txt \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/ch22-checkpoint/${VERSION}/payload.txt"
- echo "checkpoint_version=${VERSION}"
verify_checkpoint_package:
image: alpine:3.22
stage: verify
needs: [publish_checkpoint_package]
before_script:
- apk add --no-cache curl
script:
- VERSION="0.0.${CI_PIPELINE_IID}"
- |
curl --fail-with-body --location \
--header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
--output downloaded.txt \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/ch22-checkpoint/${VERSION}/payload.txt"
- printf 'chapter22-checkpoint|sha=%s|pipeline=%s\n' "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" > expected.txt
- sha256sum expected.txt downloaded.txt
- cmp expected.txt downloaded.txt
missing_file_probe:
image: alpine:3.22
stage: verify
needs: [publish_checkpoint_package]
when: manual
allow_failure: true
before_script:
- apk add --no-cache curl
script:
- VERSION="0.0.${CI_PIPELINE_IID}"
- |
curl --fail-with-body --location \
--header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
--output should-not-exist.txt \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/ch22-checkpoint/${VERSION}/missing.txt"
The probe is intentionally optional/manual so its expected 404 does not disguise a required release failure.
5. Validate configuration and source state
Use CI Lint/Pipeline Editor, then record:
git status --short --branch
git rev-parse HEAD
glab ci lint .gitlab-ci.yml 2>/dev/null || echo "Use Pipeline Editor / CI Lint on this glab version"
If your current glab does not expose the exact lint
subcommand shown, use the documented UI/API CI Lint rather than
inventing flags. The lesson’s package workflow does not depend on a
specific local glab lint alias.
6. Execute the successful publish and consume path
Push the disposable branch and wait for the required jobs. Capture pipeline/job IDs and logs without authorization headers:
git switch -c ch22/checkpoint
git add .gitlab-ci.yml evidence/ch22/predictions.md
git commit -m "lab: checkpoint package lifecycle"
git push -u origin ch22/checkpoint
glab ci status --wait
glab ci get --output json | jq '{id,status,ref,sha}'
Compare the pipeline SHA to your prediction. The package version is
0.0.<pipeline IID>. The verify job must show
equal hashes and successful cmp.
7. Prove registry identity independently
Use your authenticated local session; do not export another token:
PROJECT_ID="<disposable-project-id>"
VERSION="0.0.<pipeline-iid>"
mkdir -p evidence/ch22
glab api "projects/$PROJECT_ID/packages?package_type=generic&package_name=ch22-checkpoint&package_version=$VERSION&per_page=100" \
> evidence/ch22/package.json
PACKAGE_ID="$(jq -r '.[0].id // empty' evidence/ch22/package.json)"
test -n "$PACKAGE_ID"
glab api "projects/$PROJECT_ID/packages/$PACKAGE_ID/package_files" \
> evidence/ch22/files.json
jq 'map({file_name,size,file_sha256,created_at})' evidence/ch22/files.json
Expected: one payload.txt file and a SHA-256 matching
the successful job output. Record package ID, version, pipeline and
commit in your evidence note.
8. Optional second-path download with glab
Use the current Generic Package command to independently download the exact package. Checksum verification is on by default:
glab packages download \
--name ch22-checkpoint \
--version "$VERSION" \
--filename payload.txt \
--path evidence/ch22/downloaded-via-glab.txt
sha256sum evidence/ch22/downloaded-via-glab.txt
Do not use --no-verify merely to make an integrity
failure disappear. If verification fails, preserve evidence and
diagnose the package/file identity.
9. Inject one safe missing-file failure and diagnose it
Trigger missing_file_probe manually. Predict HTTP 404
and curl exit code 22. Preserve the log. Then explain the
least-destructive repair: request payload.txt at the
exact same project/name/version. Do not create a token, republish
the package, or retry the missing coordinate.
10. Prove the package is disposable before deletion
Before deletion, answer these questions in
evidence/ch22/cleanup-decision.md:
- Is this exact version referenced by any real deployment, release, lockfile, or external host? Expected: no.
- Is its checksum/provenance recorded? Expected: yes.
- Is package request forwarding relevant to this Generic Package coordinate? Document the format-specific answer instead of assuming.
- Would deleting a package file rather than the whole synthetic package leave malformed state? Avoid that path.
11. Credential cleanup: prove there is nothing persistent to revoke
The mandatory path used only CI_JOB_TOKEN, which
expires with each job. Record: “No PAT/project/deploy token created
for this checkpoint.” If you separately performed the optional
external-consumer exercise using a deploy token, revoke it now and
verify it can no longer authenticate. Never paste its value into
your evidence file.
12. Destructive cleanup: delete the exact synthetic package and verify absence
Re-read the package ID from evidence, print only non-secret identity, then delete:
PACKAGE_ID="$(jq -r '.[0].id // empty' evidence/ch22/package.json)"
test -n "$PACKAGE_ID"
printf 'Deleting disposable package id=%s version=%s project=%s\n' "$PACKAGE_ID" "$VERSION" "$PROJECT_ID"
glab api -X DELETE "projects/$PROJECT_ID/packages/$PACKAGE_ID"
REMAINING="$(glab api "projects/$PROJECT_ID/packages?package_type=generic&package_name=ch22-checkpoint&package_version=$VERSION&per_page=100" | jq 'length')"
test "$REMAINING" -eq 0
echo "verified package removal"
This deletion is appropriate only because the checkpoint proved there are no real consumers. Production retention decisions require deployment/release/audit analysis before removal.
13. Clean up the disposable branch without hiding failure
After package verification and deletion:
git switch main
if git ls-remote --exit-code --heads origin ch22/checkpoint >/dev/null 2>&1; then
git push origin --delete ch22/checkpoint
if git ls-remote --exit-code --heads origin ch22/checkpoint >/dev/null 2>&1; then
echo "remote branch still exists" >&2
exit 1
fi
fi
if git show-ref --verify --quiet refs/heads/ch22/checkpoint; then
git branch -D ch22/checkpoint
fi
sha256sum evidence/ch22/package.json evidence/ch22/files.json > evidence/ch22/evidence.sha256
The conditional checks distinguish an already-absent branch from a deletion failure. Package deletion verification above and branch cleanup both remain observable rather than being hidden with permissive error handling. Keep evidence files if they are part of the learning record.
14. Final verification checklist
- Package project/name/version/file matched the written prediction.
- Publisher and consumer used job-scoped identity; no persistent token was needed.
- Consumer reconstructed expected bytes independently and matched them.
- Packages API file SHA-256 matched observed content.
- Missing-file probe produced the predicted coordinate error and was diagnosed without privilege widening.
- Package deletion was preceded by consumer-impact analysis and followed by an exact absence check.
- No real secret, production package, container image, or external deployment was touched.
15. What Chapter 22 adds to the production GitLab operating model
You now have a governed distribution contract: source/pipeline provenance, project package ownership, version/file identity, ephemeral CI authentication, format-aware duplicate policy, checksum verification, explicit retention, and safe consumer-aware deletion. Chapter 23 applies the same rigor to OCI images, where immutable digests, tags, cleanup, registry authentication, and vulnerability scanning introduce a different registry protocol.
Knowledge check
Why is CI_JOB_TOKEN the narrowest practical
credential in this checkpoint?
GitLab creates it for the running job, the Package Registry documents it for CI use, and it expires when the job ends—so no persistent package credential must be distributed or revoked.
What independently proves the downloaded package matched what this pipeline intended to publish?
The consumer reconstructs the deterministic expected content from its own commit/pipeline variables and compares bytes/hash, while registry metadata provides a separate stored SHA-256.
The missing-file probe returns 404. What should you change first?
The exact file/package coordinate. Do not broaden token scope or republish until identity is proven wrong.
Why do you inspect consumers before deleting the package?
Package deletion can break builds/deployments/rollback and, for formats with request forwarding, can alter what a missing private coordinate resolves to.
If you performed the optional deploy-token extension, what cleanup evidence is required?
Revoke the token, verify it no longer authenticates, and record only token identity/scope/expiry metadata—not its value.
What is the natural bridge to Chapter 23?
Packages and container images are both governed distribution objects, but OCI images add digest/tag semantics, container-registry cleanup/authentication, and image vulnerability scanning.
Summary
The checkpoint proves more than upload/download syntax. You predicted the registry coordinate, used ephemeral identity, bound package bytes to commit/pipeline provenance, verified SHA-256, diagnosed a safe failure by identity, evaluated downstream impact, and proved destructive cleanup. That is a production-quality package lifecycle.
Official references
Primary sources used for the current GitLab 19.3 behavior taught in this lesson:
- GitLab Docs — Package Registry
- GitLab Docs — Supported package managers and functionality
- GitLab Docs — Generic Packages
- GitLab Docs — Packages API
- GitLab Docs — CI/CD job token
- GitLab Docs — Dependency Proxy for container images
- GitLab Docs — Dependency Proxy for packages
- GitLab Docs — Reduce Package Registry storage
- GitLab CLI — glab packages upload
- GitLab CLI — glab packages download
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.