Checkpoint Lab — Container Registry, Package Registry, Generic Packages, Dependency Proxy, and Artifact Promotion
Publish and consume one disposable artifact by immutable identity, verify producer SHA and digest, diagnose one authorization or mutable-tag failure, and clean up only the identified lab object.
Learning objectives
Checkpoint goals
- Publish a disposable Generic Package from an exact SHA and prove its checksum.
- Consume it by exact version, not by a moving alias.
- Record identity/authentication/pipeline evidence without secrets.
- Demonstrate one intentional denial or mutable-reference failure and diagnose the correct layer.
- Document same-bytes promotion and exact cleanup/retention choice.
1. Scenario
You own a synthetic CLI payload built in a disposable GitLab
project. A staging consumer and a production manifest must refer to
the same verified bytes. You will publish version
0.0.0-ch23-lab.<pipeline-id>, verify it from a
clean download, create a promotion manifest, and then diagnose
either a cross-project authorization denial or an intentionally
mutable alias mismatch.
glci-ch23-demo with a
guarded 0.0.0-ch23-lab. version. Container publication
is optional. No production registry, broad PAT, or privileged shared
runner is required.
2. Assumptions and preflight
| Item | Mandatory path assumption |
|---|---|
| GitLab tier | Free-compatible Generic Package workflow. |
| Runner |
Any runner able to execute shell + curl +
SHA-256 tool; no privileged Docker required.
|
| Identity |
Same-project CI_JOB_TOKEN for package
publish/download.
|
| Data | Synthetic text only. |
| External systems | None. Dependency Proxy/container portions may be inspected/simulated. |
set -eu
git status --short
git rev-parse HEAD
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"
PACKAGE_NAME="glci-ch23-demo"
PACKAGE_VERSION="0.0.0-ch23-lab.${CI_PIPELINE_ID}"
case "$PACKAGE_VERSION" in 0.0.0-ch23-lab.*) ;; *) exit 2;; esac
3. Predict before mutation
| Prediction | How you will verify independently |
|---|---|
| A new exact package version will exist and be tied to this producer pipeline/SHA. | Package coordinate/API/UI + evidence file containing producer pipeline/job/SHA. |
| Consumer bytes will equal producer bytes. | Independent SHA-256 after download. |
| Promotion will not rebuild. | Staging/production manifest contains same exact package version + checksum. |
| Unauthorized cross-project request will remain denied until target authorization is changed. | Preserved HTTP status + target allowlist/role inspection. |
4. Exact checkpoint pipeline
stages: [build, publish, verify, promote]
default:
image: alpine:3.20
build_payload:
stage: build
script:
- mkdir -p dist evidence
- printf 'chapter=23\nsha=%s\npipeline=%s\n' "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" > dist/payload.txt
- sha256sum dist/payload.txt | tee evidence/producer.sha256
artifacts:
paths: [dist/, evidence/]
expire_in: 7 days
publish_package:
stage: publish
needs: [build_payload]
script:
- export PACKAGE_NAME="glci-ch23-demo"
- export PACKAGE_VERSION="0.0.0-ch23-lab.${CI_PIPELINE_ID}"
- export URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/payload.txt"
- curl --fail --location --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file dist/payload.txt "$URL"
verify_package:
stage: verify
needs: [build_payload, publish_package]
script:
- export PACKAGE_NAME="glci-ch23-demo"
- export PACKAGE_VERSION="0.0.0-ch23-lab.${CI_PIPELINE_ID}"
- export URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/payload.txt"
- curl --fail --location --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --output payload.downloaded "$URL"
- test "$(sha256sum payload.downloaded | cut -d' ' -f1)" = "$(cut -d' ' -f1 evidence/producer.sha256)"
promote_manifest:
stage: promote
needs: [build_payload, verify_package]
script:
- mkdir -p promotion
- printf 'package=glci-ch23-demo\nversion=0.0.0-ch23-lab.%s\nsha256=%s\n' "$CI_PIPELINE_ID" "$(cut -d' ' -f1 evidence/producer.sha256)" > promotion/staging.env
- cp promotion/staging.env promotion/production-candidate.env
- diff -u promotion/staging.env promotion/production-candidate.env
artifacts:
paths: [promotion/]
expire_in: 7 days
The checkpoint uses Alpine as a small execution image; for strict supply-chain environments, replace the moving image tag with an organization-approved image digest and record it in the evidence packet. This runner image is tooling, not the artifact being promoted.
5. Expected observations
| Step | Expected evidence | What it proves |
|---|---|---|
| Build | producer.sha256 + source SHA |
Exact bytes were identified before publication. |
| Publish | Successful upload for unique version | Registry state changed at a bounded coordinate. |
| Verify | Downloaded hash equals producer hash | Consumer receives the same bytes. |
| Promote | Staging and production-candidate manifests identical | Promotion changed eligibility/reference, not bytes. |
6. Inject exactly one failure
Option A — Authorization denial
Attempt a read from a private disposable target project that has not allowlisted the source project. Preserve the denial, inspect target job-token settings and actor permission, then either leave it intentionally denied or make the smallest authorized allowlist change. Do not use a broad PAT.
Option B — Mutable alias mismatch
Locally create an alias file named latest.pointer that
first points to the checkpoint checksum, then intentionally change
it to another synthetic checksum while the immutable version remains
unchanged. Diagnose that the alias is mutable and restore consumers
to the exact version/checksum rather than “fixing” the package.
7. Optional container extension
If the project has a safe container-capable runner, build the
scratch image from Lesson 2, push tag
ch23-lab-$CI_PIPELINE_ID, capture its repository
digest, then create staging/production manifests that record the
same digest. The checkpoint passes without this extension.
8. Required evidence packet
| Evidence | Required fields |
|---|---|
source.env |
Pipeline source/ref/SHA, pipeline ID, job IDs; no secrets. |
producer.sha256 |
Producer checksum. |
| Package coordinate | Project, package name, exact version, filename. |
| Publication result | HTTP/API success and producer job identity. |
consumer.sha256 |
Independent downloaded checksum. |
| Authorization evidence | Token class, source/target project, allowlist assumption, denial status if exercised. |
| Promotion manifest | Exact version/checksum or image digest for stage/prod candidate. |
| Retention/cleanup note | Keep/delete decision and exact guarded object identity. |
| Assumptions | GitLab offering/tier, runner/executor/image/tool versions, optional features used. |
9. Cleanup / rollback
For maximum auditability, you may retain the lab package until its
evidence artifact expires. If deleting it, locate the exact package
record corresponding to PACKAGE_NAME +
PACKAGE_VERSION, record its ID, assert the version
prefix, and delete only that record. If request forwarding is
enabled, document dependency-confusion implications before deleting
a package that downstream clients might request.
case "$PACKAGE_VERSION" in
0.0.0-ch23-lab.*) ;;
*) echo 'Refusing cleanup outside Chapter 23 lab namespace' >&2; exit 2 ;;
esac
printf 'approved cleanup coordinate=%s/%s\n' "$PACKAGE_NAME" "$PACKAGE_VERSION"
# Resolve one exact package ID through authorized UI/API, review it, then delete only that ID.
10. Verification checklist
- Source SHA and producer pipeline/job IDs are recorded.
- Package version is unique and guarded.
- Producer checksum exists before publication.
- Consumer downloads the exact version.
- Consumer checksum equals producer checksum.
- Promotion manifests reference identical bytes.
- No credential value appears in traces/artifacts.
- Any authorization failure is explained by allowlist/permission evidence.
- Cleanup targets one exact lab object or is intentionally skipped.
Knowledge check
Which two independent facts are the minimum proof that the registry did not change the artifact?
The exact package coordinate resolves successfully, and the independently downloaded SHA-256 equals the producer SHA-256.
Why does the production-candidate manifest copy the staging coordinate instead of rebuilding?
The checkpoint is proving same-bytes promotion. A rebuild would create a new artifact identity and break the chain of evidence.
An unauthorized cross-project download fails. Which configuration should you inspect before tokens?
The target project job-token allowlist/resource visibility and the permissions of the user who triggered the source job.
If a human-readable tag moves but the digest remains stored, what should the deployment use?
The exact retained digest. The mutable tag should be treated as a label, not the artifact identity.
Why might you intentionally skip cleanup?
The package can be useful retained evidence/rollback material. Cleanup is a storage/governance decision and should not destroy evidence automatically.
11. What Chapter 23 adds to the production operating model
You now have a distribution contract linking source SHA, producer pipeline, authentication identity, registry/package coordinate, checksum or manifest digest, consumer verification, same-bytes promotion, and retention. That means a release or deployment can point to a durable object whose bytes can be independently reproduced and audited later.
Chapter 24 builds on this evidence discipline by teaching test reports, coverage, JUnit, code-quality and browser/accessibility feedback as structured pipeline evidence rather than unstructured log text.
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. Generic Packages, Package Registry, Container Registry, Dependency Proxy, protected packages, and protected container tags are documented for Free/Premium/Ultimate unless noted otherwise. Current immutable container-tag rules are Ultimate-only. Dependency Proxy is group-level and supports Docker Hub images. The checkpoint is fully completable on Free with Generic Packages and CI_JOB_TOKEN. Container publication, protected-package/tag administration, external repository managers, and Ultimate tag immutability remain optional extensions.
- Package registry — official reference.
- Generic packages — official reference.
- Supported package functionality — official reference.
- Protected packages — official reference.
- Reduce package registry storage — official reference.
- Container registry — official reference.
- Container registry authentication — official reference.
- Protected container tags — official reference.
- Immutable container tags — official reference.
- Reduce container registry storage — official reference.
- Dependency Proxy — 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.