Chapter 23Lesson 04~185 minutes

Container Registry, Package Registry, Generic Packages, Dependency Proxy, and Artifact Promotion: Diagnostics, Failure Modes, Security, and Performance

Diagnose mutable tags, duplicate package versions, cross-project job-token denials, dependency-proxy provenance mistakes, leaked credentials, and unsafe cleanup from preserved evidence.

DiagnosticsSecurityMutable tagsAuthorizationRecovery

Learning objectives

  • Use an evidence-first sequence to distinguish registry identity, authorization, runner/network, and retention failures.
  • Diagnose a moving latest tag without rebuilding or overwriting evidence.
  • Explain why duplicate Generic Package versions can create ambiguous consumption unless policy/checksum is enforced.
  • Diagnose cross-project CI_JOB_TOKEN denial from allowlist and user permissions.
  • Prevent registry credentials from leaking into traces, shell history, URLs, artifacts, or debug dumps.

1. Evidence-first diagnostic sequence

  1. Preserve pipeline/job IDs and the first failing trace.
  2. Confirm CI_PIPELINE_SOURCE, ref, and CI_COMMIT_SHA.
  3. Inspect merged configuration and rule result.
  4. Record package/image coordinate and expected checksum/digest.
  5. Record token class, source/target project, allowlist assumption, and actor permission—never token value.
  6. Inspect queue, runner/executor, DNS/TLS/network only after configuration/authorization evidence.
  7. Compare registry/package metadata and consumer digest.
  8. Inspect cleanup/protection policies.
  9. Apply the least destructive correction and retry only the smallest scope.

2. Failure: latest silently moved

Pipeline A tested digest D1, but a later Pipeline B pushed latest → D2. Production redeploying latest now consumes D2 even if its deployment record still refers to Pipeline A.

Evidence Interpretation
Pipeline A producer SHA + recorded digest D1 What was actually tested.
Current registry resolution of latest → D2 Mutable reference drift.
Deployment record says “latest” only Insufficient artifact identity.

Repair: restore the deployment reference to repository@D1 if D1 is still retained and authorized. Do not rebuild D1 from source and call it the same artifact. Then add policy requiring digest evidence for deployments.

3. Failure: same Generic Package version accepts another file

Generic Packages allow duplicates by default at the format/settings level. If two producer pipelines publish the same package name/version/file semantics, a consumer may not have a unique human-readable version contract even though each file has a checksum.

Expected:
  glci-ch23-demo / 1.2.0 / payload.txt -> sha256:AAA
Observed after second publisher:
  same package name/version contains another or replaced-looking asset context -> sha256:BBB
Root cause layer:
  package naming / duplicate policy / publication governance, not runner cache

Repair: mint unique immutable versions, disable/configure duplicates where appropriate, protect release package names, and keep checksum verification mandatory.

4. Failure: cross-project job token denied

A valid CI_JOB_TOKEN from project B is not a universal GitLab credential. For private project A, A must allow B (or its group) when cross-project access is required, and the user who triggered B’s job must have the permissions needed in A.

Check Evidence Wrong shortcut
Target allowlist Target project CI/CD job-token settings/API. Disable allowlisting globally.
Triggering user permission Membership/role sufficient for requested target resource. Replace with Owner PAT.
Endpoint/resource visibility Package/container access settings. Make private registry public.
Token lifetime Job still running; token valid only during job. Persist job token for later automation.

5. Failure: Dependency Proxy treated as provenance

A pipeline uses ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/vendor/image:stable and assumes the cached result is immutable. The proxy is operating correctly; the design is wrong. A moving upstream tag can still resolve to different content over time.

Repair: record a verified upstream digest and consume that digest through the supported registry path. Keep the proxy for caching/rate-limit behavior, not for trust claims.

6. Failure: credential appears in trace

Stop immediately if a real credential is exposed. Preserve the job/pipeline ID and affected scope, then rotate/revoke according to your incident process. Do not paste the secret into tickets, screenshots, or course evidence.

Common causes include shell tracing, embedding token in a URL, using docker login -p, or archiving a generated Docker config. The safer pattern uses predefined credentials, --password-stdin, masked variables where appropriate, isolated runners, and artifacts that explicitly exclude auth/config directories.

7. Failure: cleanup policy removes needed rollback tag

Preserve the cleanup rule configuration, affected repository, tag list before/after, release/deployment references, and registry digest inventory. Current container cleanup excludes protected and immutable tags, which is useful only when critical rollback identities are actually protected. Cleanup deletes tags according to policy; storage reclamation of unreferenced layers is a separate concern.

8. Performance diagnosis: registry speed is not always registry storage

Symptom Likely layer to inspect Evidence
Slow pull after cache miss Network/registry/proxy path Transfer timing, runner location, proxy cache state.
Repeated Docker Hub pulls Dependency Proxy not used or wrong group prefix Image reference + proxy activity.
Registry storage grows despite tag cleanup Untagged layers/GC behavior Tag inventory versus registry storage metrics.
Package listing becomes slow Package/asset accumulation Usage quota + package count + cleanup policy.

9. Intentionally broken pipeline and causal repair

stages: [publish, consume]

publish_bad:
  stage: publish
  script:
    - echo "publishing only latest"
    - docker build -t "$CI_REGISTRY_IMAGE:latest" .
    - docker push "$CI_REGISTRY_IMAGE:latest"

consume_bad:
  stage: consume
  script:
    - docker pull "$CI_REGISTRY_IMAGE:latest"
    - echo "assume this is the artifact tested earlier"

The YAML can compile and both jobs can succeed, yet the pipeline has no immutable distribution evidence. The repair is architectural: publish a unique tag, capture the pushed manifest digest, emit it as evidence, and make the consumer pull @sha256:…. Job success was never the missing fact.

10. Recovery drill

Given a failed consumer, write down the exact expected coordinate/digest before touching the registry. Then answer: did the producer publish it, is the consumer authorized, can the runner reach it, did retention delete it, or did a mutable tag resolve elsewhere? Correct only the proven failing layer.

Knowledge check

A package upload returned 201 but the consumer hash differs. Is authentication the first suspect?

Why can a valid CI_JOB_TOKEN still receive 403/404 cross-project?

If latest moved from D1 to D2, what is the least destructive recovery?

Does deleting a container tag prove storage was reclaimed?

What evidence should never be attached to a diagnostic packet?

Next lesson

Checkpoint lab

Build a bounded evidence packet that proves exact producer SHA, package version/checksum, consumer verification, authorization behavior, promotion identity, and safe cleanup.

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. Current docs state job tokens are masked and valid only while the job runs, but runner isolation remains critical. The lesson therefore treats runner security as part of token security rather than assuming masking prevents theft.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.