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.
Learning objectives
- Use an evidence-first sequence to distinguish registry identity, authorization, runner/network, and retention failures.
-
Diagnose a moving
latesttag 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_TOKENdenial from allowlist and user permissions. - Prevent registry credentials from leaking into traces, shell history, URLs, artifacts, or debug dumps.
1. Evidence-first diagnostic sequence
- Preserve pipeline/job IDs and the first failing trace.
-
Confirm
CI_PIPELINE_SOURCE, ref, andCI_COMMIT_SHA. - Inspect merged configuration and rule result.
- Record package/image coordinate and expected checksum/digest.
- Record token class, source/target project, allowlist assumption, and actor permission—never token value.
- Inspect queue, runner/executor, DNS/TLS/network only after configuration/authorization evidence.
- Compare registry/package metadata and consumer digest.
- Inspect cleanup/protection policies.
- 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
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?
No. Publication authorization succeeded. Compare exact package coordinate, duplicate/version behavior, downloaded object, and checksum evidence first.
Why can a valid CI_JOB_TOKEN still receive 403/404 cross-project?
The target allowlist, project visibility, endpoint rules, and triggering-user permissions still apply.
If latest moved from D1 to D2, what is the least destructive recovery?
Point the consumer/deployment back to retained digest D1, then fix the policy/reference model. Do not overwrite latest blindly or rebuild D1.
Does deleting a container tag prove storage was reclaimed?
No. Tag deletion and underlying unreferenced-layer garbage collection/storage reclamation are different layers.
What evidence should never be attached to a diagnostic packet?
Secret values: job token, deploy token, registry password, Docker auth config, or any credential material.
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.
- 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.