Chapter 24Lesson 02240–330 min

Docker Registry Operations, Garbage Collection, Retention, Search, Proxying, and Production Troubleshooting: Guided Hands-On Workflow and Core Operations

Run the smallest safe registry-operations workflow: create synthetic image references, prove shared-layer and digest relationships, retire selected tags through supported interfaces, observe logical deletion before physical reclamation, inspect a proxy request, and record every state transition.

Hands-onDisposable registryDigest verificationGC/compactionProxy inspection

Learning objectives

  • Prepare a disposable Community-compatible Docker repository topology with explicit version/runtime/database/blob assumptions.
  • Push or model several synthetic tags that reuse layers and record manifest digests before cleanup.
  • Retire only selected disposable content through supported Nexus/Docker APIs or UI and verify what remains.
  • Run only current supported Docker cleanup/reclamation tasks on disposable blob state and distinguish logical from physical changes.
  • Inspect one proxy fetch/failure path and produce a causal before/after evidence packet.
Version baseline (27 August 2026). The chapter uses Nexus Repository 3.95.2-01 with Java 21 as its dated reference line. Docker behavior has changed substantially across releases; re-check the exact task names, known issues, routing mode, and release notes on the instance you operate.
Current 3.94.0–3.95.2 Docker cleanup warning. Sonatype documents an open issue where Docker manifest HEAD requests do not refresh an asset's lastDownloaded timestamp. An actively used image can therefore look inactive. Do not use Last Downloaded–based Docker cleanup policies on an affected instance until Sonatype publishes a fixed version.
Safety boundary. All destructive steps must target explicitly named disposable repositories and synthetic images. Never delete production tags/manifests, edit blob-store files, remove database rows, disable TLS validation, or run garbage collection/compaction on valuable repositories merely to observe behavior.

1. Lab baseline and preflight

Reference environment: Nexus Repository 3.95.2-01, Java 21, self-hosted Community Edition, single disposable node, H2 database, file blob store, and path-based Docker routing. H2 is acceptable for this small archive-style learning instance; do not generalize it into a production design, and do not use a containerized H2 Nexus deployment where current Sonatype support rules prohibit it.

Client options: Docker or Podman for the live path; Python 3 for the deterministic fallback. If your local Docker client requires insecure-registry configuration to reach HTTP Nexus, do not weaken a production/global daemon. Reuse the trusted local TLS/reverse-proxy fixture from Chapter 17, or use the deterministic fallback for the protocol/state exercises.

Disposable names: ch24-docker-hosted, ch24-docker-proxy, learner-example/ch24-demo, tags 1.0, 1.1, stable, and evidence folder chapter24-evidence.

2. Create or verify the disposable repositories

Use Settings → Repositories on the disposable instance. Create a Docker hosted repository named ch24-docker-hosted backed by the disposable file blob store and enable path-based routing. For the proxy extension, create ch24-docker-proxy only if you have an approved harmless upstream fixture or are allowed to proxy a small public image. Do not use employer credentials or a private production registry.

Before publishing anything, record repository type, blob store, routing mode, write policy, online state, and current component count. Confirm the Docker Bearer Token Realm if Docker client authentication is required.

3. Build a tiny synthetic image set with shared layers

For a live Docker engine, create this harmless build context:

FROM scratch
COPY common.txt /common.txt
COPY version.txt /version.txt
LABEL org.example.course="chapter24"
mkdir -p ch24-image
cd ch24-image
printf 'shared-layer
' > common.txt
printf 'version=1.0
' > version.txt
# Build using your approved local Docker/Podman engine.
docker build -t ch24-demo:1.0 .
printf 'version=1.1
' > version.txt
docker build -t ch24-demo:1.1 .

The two images should share at least the layer corresponding to common.txt; exact layer construction can vary with builder implementation. The learning objective is to observe digest/reference relationships, not to force a specific layer count.

4. Tag and publish to the hosted repository

With trusted TLS/path routing from Chapter 17, set:

export REGISTRY_HOST='repo.example.invalid'
export HOSTED_PATH='ch24-docker-hosted/learner-example/ch24-demo'

docker tag ch24-demo:1.0 "$REGISTRY_HOST/$HOSTED_PATH:1.0"
docker tag ch24-demo:1.1 "$REGISTRY_HOST/$HOSTED_PATH:1.1"
docker tag ch24-demo:1.1 "$REGISTRY_HOST/$HOSTED_PATH:stable"

docker push "$REGISTRY_HOST/$HOSTED_PATH:1.0"
docker push "$REGISTRY_HOST/$HOSTED_PATH:1.1"
docker push "$REGISTRY_HOST/$HOSTED_PATH:stable"

Use a disposable least-privilege publisher credential through docker login or your client credential store. Do not put credentials in the image URL or copy ~/.docker/config.json into the evidence bundle.

5. Deterministic no-Docker fallback: model the same graph

If a trusted Docker endpoint is unavailable, the following fixture still teaches the retention/reclamation graph without pretending to modify Nexus:

import hashlib, json
from pathlib import Path

root = Path('chapter24-evidence')
root.mkdir(exist_ok=True)

def h(text): return 'sha256:' + hashlib.sha256(text.encode()).hexdigest()
common = h('common-layer')
layer10 = h('version=1.0')
layer11 = h('version=1.1')
manifest10 = h('manifest:' + common + ':' + layer10)
manifest11 = h('manifest:' + common + ':' + layer11)
state = {
  'tags': {'1.0': manifest10, '1.1': manifest11, 'stable': manifest11},
  'manifests': {
    manifest10: {'layers':[common, layer10]},
    manifest11: {'layers':[common, layer11]}
  },
  'blobs': [common, layer10, layer11]
}
(root/'registry-before.json').write_text(json.dumps(state, indent=2)+'\n')
print(json.dumps(state, indent=2))

This fixture is a simulation only. It must be labeled as such in your evidence packet; it does not prove a particular Nexus task executed.

6. Capture before-state evidence

For the live path, capture tags and exact digests. The docker image inspect result shows client-side repository digests after pull/push, while Registry API/Nexus search shows server-side metadata.

mkdir -p ../chapter24-evidence
curl -fsS "$NEXUS_URL/service/rest/v1/search?repository=ch24-docker-hosted&format=docker"   > ../chapter24-evidence/search-before.json

# Through the Docker route; authentication may require a token-aware client.
curl -fsS "$REGISTRY/v2/learner-example/ch24-demo/tags/list"   > ../chapter24-evidence/tags-before.json

docker pull "$REGISTRY_HOST/$HOSTED_PATH:1.1"
docker image inspect "$REGISTRY_HOST/$HOSTED_PATH:1.1"   > ../chapter24-evidence/client-inspect-1.1.json

Record the digest of 1.1 and stable. They should resolve to the same manifest if stable was tagged from exactly the same image.

7. Predict before deleting anything

  1. Removing tag/component 1.0 should not make the common layer reclaimable because 1.1/stable still reference it.
  2. Removing stable should not delete the 1.1 manifest if the 1.1 tag still references that same digest.
  3. Physical file-blob usage may not fall immediately after logical deletion; Docker GC and blob-store compaction are separate stages.

Write the predictions in chapter24-evidence/predictions.md before proceeding.

8. Retire one disposable tag/component through a supported interface

Use Nexus Browse/Search or the documented Components API to locate the exact disposable component associated with tag 1.0. Verify repository name, image name, tag, and component ID twice. Then delete only that component through supported Nexus UI/API. Do not construct a filesystem path and delete blob files.

# Read first. Replace the synthetic ID only after verifying it belongs to ch24-docker-hosted.
curl -fsS "$NEXUS_URL/service/rest/v1/search?repository=ch24-docker-hosted&format=docker&docker.imageName=learner-example/ch24-demo&docker.imageTag=1.0"

# Destructive action: run only against the verified disposable component ID.
# curl -fsS -X DELETE "$NEXUS_URL/service/rest/v1/components/$COMPONENT_ID"

The destructive command is intentionally commented in the course. The learner must obtain and inspect the actual disposable component ID first. Current Nexus also supports Docker Registry API deletion semantics, but operational teams should standardize one supported deletion path and understand how it maps to tags/manifests.

9. Run Docker garbage collection only after inspecting scope

Go to Settings → System → Tasks and verify that the current task type is Docker - Delete unused manifests and images. Confirm it targets only ch24-docker-hosted. Run it during this isolated lab, then capture task history/log output.

The task removes Docker tags/manifests/layers no longer referenced by tags/manifests. Sonatype explicitly warns that digest-only content can be treated as unused. The lab does not create valuable digest-only content.

10. Reclaim physical blob space separately

Inspect file blob-store usage before and after logical deletion/GC. Then, if the blob store is used only for the disposable lab or the exact scope is otherwise safe, run the supported Admin - Compact blob store task for that disposable blob store. Record the Blobs Older Than setting and expected effect.

# Filesystem-level measurement only; do not modify blob-store files.
df -h /path/to/disposable/blob-parent
# Or use the Nexus UI/metrics for blob-store size evidence.

If the disposable repository shares a blob store with valuable content, do not run compaction merely for the lab. Use the deterministic storage fixture instead and document why compaction was skipped.

11. Proxy one harmless image—or use a local upstream fixture

The safest pattern is an approved local upstream registry containing a tiny synthetic image. Configure ch24-docker-proxy to that upstream, pull once through Nexus, then capture Nexus search/asset evidence and the resolved digest. If organizational policy permits a public demonstration, use a small benign image and record the exact upstream/digest; never stress-test or repeatedly pull a public registry.

For an intentional proxy failure, block or stop the local upstream fixture rather than attacking a public registry. Pull the already-cached digest/tag and an uncached tag, then compare outcomes. This distinguishes cached availability from remote reachability.

12. Interpret a proxy rate-limit response rather than hiding it

If an upstream returns 429, record response headers, Nexus proxy logs, and whether the requested manifest/layers were already cached. Do not bypass Nexus, rotate random credentials, or disable policy/TLS to make the pull succeed. Production fixes include upstream authentication, approved mirrors, cache strategy, pull-rate planning, and build design that pins/reuses known digests.

13. Verify after-state

  • 1.0 no longer appears in Nexus search/tag listing.
  • 1.1 and stable still resolve and, if expected, share the same manifest digest.
  • The common layer remains referenced while 1.1 remains.
  • Task history shows the Docker GC result for the disposable repository.
  • Compaction, if safely run, is recorded separately from logical deletion.
  • The proxy test records cached-versus-uncached behavior and remote status.
  • No real credential appears in logs/evidence.

14. Challenge: choose the correct control

Your team wants to keep five rollback releases but remove old CI tags. Which control should you reach for first?

Answer in your evidence notes before revealing the explanation: start with a retention design that distinguishes release tags from ephemeral tags. Current Nexus retain-last-N is a Pro/PostgreSQL feature; Community environments can still implement deliberate supported deletion/task workflows, but must not simulate Pro entitlements as if present. The Docker GC task is not the retention policy—it only cleans data that has become unreferenced.

15. Knowledge check

You deleted tag 1.0 and disk usage did not change. Name three plausible reasons.

Why is the Docker GC task not a replacement for a retention policy?

What should happen to the shared common layer after tag 1.0 is retired but 1.1 still references it?

What is the safe way to simulate an upstream outage in this lab?

Why is the component-delete command commented in the lesson?

16. Summary and next step

You have now observed the essential registry lifecycle: publish/tag, resolve digests, inspect shared layers, retire only intended references, clean unreferenced Docker data, and reclaim physical blob space separately. Lesson 3 turns these mechanics into design choices for production retention, proxy freshness, trust zones, routing, and maintenance windows.

Official references and version notes

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.