Chapter 08Lesson 02180–235 min

Docker and OCI Repositories, Connectors, Registry Paths, Authentication, Layers, and Image Metadata: Guided Hands-On Workflow and Core Operations

Build a disposable OCI hosted/proxy/group topology, authenticate with isolated client state, push a tiny synthetic image only to hosted storage, pull through a group, inspect manifests/digests, and observe proxy and layer reuse safely.

OCI hosted/proxy/groupPath routingPodman/DockerPush & pullEvidence

Learning objectives

  • Create a disposable Community-compatible OCI hosted/proxy/group topology using current Nexus UI semantics.
  • Keep container-client credentials in a disposable auth/config location and avoid secrets in command arguments.
  • Build and push a tiny synthetic image only to hosted storage, then consume it through the group.
  • Proxy a harmless public image and distinguish first-fetch proxy state from client-local cache.
  • Capture manifest digest, image metadata, repository topology, and layer-reuse evidence.

Lab choice. The mandatory path uses Podman when the Nexus endpoint is plain loopback HTTP because Podman supports per-command --tls-verify=false and an explicit auth file. Docker Engine can perform the same registry operations, but plain HTTP requires daemon-level insecure-registries configuration; that is shown as an optional, explicitly rolled-back alternative rather than silently changing a learner's global Docker daemon.

1. Preflight and disposable names

Use only a disposable local Nexus instance. Do not run this workflow against a production registry or organization namespace. Record the exact Nexus and client versions before changes. The lab uses three native OCI repositories: academy-ch08-hosted, academy-ch08-proxy, and academy-ch08-group. All names are lowercase because current OCI repository names must be lowercase.

export NX_URL="http://127.0.0.1:8081"
export NX_REGISTRY="127.0.0.1:8081"
export LAB="${TMPDIR:-/tmp}/nexus-ch08"
rm -rf "$LAB"
mkdir -p "$LAB/evidence" "$LAB/build" "$LAB/podman"
chmod 700 "$LAB" "$LAB/podman"

curl -fsS "$NX_URL/service/rest/v1/status" | tee "$LAB/evidence/status.txt"
podman version | tee "$LAB/evidence/podman-version.txt"

2. Enable the OCI bearer-token realm only if needed

For self-hosted native OCI repositories, Sonatype documents the OCI Bearer Token Realm under Administration/Settings → Security → Realms. Preserve the current active realm list before changing it. If the realm is already active, make no change. If you enable it only for this lab, record that fact so cleanup restores the previous realm state.

Security-sensitive: changing realms affects authentication behavior instance-wide. Do this only on the disposable lab instance. Do not disable unrelated realms to “make login work.”

3. Create the OCI repositories in current UI

In Settings → Repository → Repositories, create the following recipes. Using the UI here is deliberate: the exact REST schema for newly added format fields is version-sensitive and the instance Swagger is the authoritative API contract. Chapter 20 will automate repository provisioning after introducing idempotence and schema discovery.

Repository Recipe Configuration
academy-ch08-hosted oci (hosted) Blob store default; online; Disable redeploy to enforce tag immutability for the lab; enable path-based routing if the UI exposes the routing choice.
academy-ch08-proxy oci (proxy) Remote storage https://registry-1.docker.io; blob store default; path-based routing.
academy-ch08-group oci (group) Members in this exact order: hosted first, proxy second; blob store default; path-based routing.

The hosted-first order ensures an internal collision wins over the public proxy. The group is a read aggregation endpoint. Pushes in the mandatory Community workflow go directly to hosted.

4. Verify topology before pushing

Use Nexus UI to confirm type/format/member order. If your lab account has browse permission for the Repositories API, capture it read-only. Do not put an admin password in a curl command. A permission-restricted netrc file is acceptable for this disposable local proof.

read -r -p 'Disposable Nexus username: ' NX_USER
read -r -s -p 'Disposable Nexus password: ' NX_PASS; echo
NX_AUTH_FILE="$(mktemp)"
chmod 600 "$NX_AUTH_FILE"
printf 'machine 127.0.0.1 login %s password %s
' "$NX_USER" "$NX_PASS" > "$NX_AUTH_FILE"
unset NX_PASS

curl --fail --silent --show-error --netrc-file "$NX_AUTH_FILE"   "$NX_URL/service/rest/v1/repositories"   | tee "$LAB/evidence/repositories-before.json"

rm -f "$NX_AUTH_FILE"
unset NX_AUTH_FILE

5. Authenticate without creating a persistent global credential

Create a dedicated disposable Nexus user with only the minimum privileges needed to read the group/proxy and add/read the hosted lab repository. Use a real lab-only password typed interactively; do not copy it into this course, source control, screenshots, or evidence.

export REGISTRY_AUTH_FILE="$LAB/podman/auth.json"
read -r -p 'Disposable Nexus registry username: ' NX_USER
read -r -s -p 'Disposable Nexus registry password: ' NX_PASS; echo

printf '%s' "$NX_PASS" | podman login   --authfile "$REGISTRY_AUTH_FILE"   --tls-verify=false   --username "$NX_USER"   --password-stdin   "$NX_REGISTRY"
unset NX_PASS
chmod 600 "$REGISTRY_AUTH_FILE"

The password never appears in the process argument list. The auth file remains inside the disposable lab directory. The subsequent bearer token exchanged with Nexus is scoped by repository privileges.

6. Build a tiny synthetic image without pulling a base image

A FROM scratch image avoids introducing an external base dependency into the hosted-push exercise. It contains one harmless marker file and no executable runtime. This keeps the evidence focused on registry mechanics.

FROM scratch
COPY marker.txt /marker.txt
LABEL org.opencontainers.image.title="academy-ch08"
LABEL org.opencontainers.image.version="1.0.0"
cd "$LAB/build"
printf 'DevOps Academy Nexus Chapter 08
' > marker.txt
cat > Dockerfile <<'DOCKERFILE'
FROM scratch
COPY marker.txt /marker.txt
LABEL org.opencontainers.image.title="academy-ch08"
LABEL org.opencontainers.image.version="1.0.0"
DOCKERFILE

podman build --format oci   -t localhost/learner-example/ch08-app:1.0.0 .   | tee "$LAB/evidence/build.txt"

podman image inspect localhost/learner-example/ch08-app:1.0.0   > "$LAB/evidence/local-image-before-push.json"

7. Push only to hosted and capture the registry digest

With path-based routing, the repository name is part of the image reference. The hosted reference is 127.0.0.1:8081/academy-ch08-hosted/learner-example/ch08-app:1.0.0. The push output should show blob checks and uploads followed by a manifest digest. Save that digest as release evidence.

HOSTED_REF="$NX_REGISTRY/academy-ch08-hosted/learner-example/ch08-app:1.0.0"
podman tag localhost/learner-example/ch08-app:1.0.0 "$HOSTED_REF"

podman push   --authfile "$REGISTRY_AUTH_FILE"   --tls-verify=false   --digestfile "$LAB/evidence/hosted-manifest-digest.txt"   "$HOSTED_REF"   | tee "$LAB/evidence/hosted-push.txt"

printf 'registry manifest digest: '
cat "$LAB/evidence/hosted-manifest-digest.txt"

Do not substitute the local image ID for the remote manifest digest. They are related content-addressed identifiers but represent different serialized objects.

8. Pull the internal image through the group

The group request should resolve to the hosted first member because that image path exists internally. Pull it using the same tag, then compare the resolved digest with the hosted push evidence.

GROUP_REF="$NX_REGISTRY/academy-ch08-group/learner-example/ch08-app:1.0.0"
podman pull   --authfile "$REGISTRY_AUTH_FILE"   --tls-verify=false   "$GROUP_REF"   | tee "$LAB/evidence/internal-group-pull.txt"

podman image inspect "$GROUP_REF"   > "$LAB/evidence/internal-group-image.json"

In Nexus Browse/Search, confirm that the authoritative image assets are owned by the hosted repository. The group is a routing view, not a second publication target.

9. Proxy a harmless public image through the same group

Now request library/alpine:3.20 through the group. The hosted member should miss; the proxy member retrieves from Docker Hub and caches the content. This is a controlled public read, not a publication.

PUBLIC_REF="$NX_REGISTRY/academy-ch08-group/library/alpine:3.20"
podman pull   --authfile "$REGISTRY_AUTH_FILE"   --tls-verify=false   "$PUBLIC_REF"   | tee "$LAB/evidence/public-first-pull.txt"

podman image inspect "$PUBLIC_REF"   > "$LAB/evidence/public-image.json"

Inspect the proxy repository in Nexus after the pull. You should now see cached manifest/config/layer content associated with the public image. The upstream remains the authoritative public origin; Nexus holds cached copies.

10. Separate client layer cache from Nexus proxy cache

If you immediately pull again, the client may satisfy most content from its own local store, which proves little about Nexus. Remove only the local PUBLIC_REF image, then pull again. If shared layers remain in the client because other images reference them, some transfer may still be avoided locally. Therefore pair client output with Nexus request logs/proxy repository evidence rather than interpreting one “already exists” line as a complete cache proof.

podman image rm "$PUBLIC_REF" || true
podman pull   --authfile "$REGISTRY_AUTH_FILE"   --tls-verify=false   "$PUBLIC_REF"   | tee "$LAB/evidence/public-second-pull.txt"

11. Retag the same image and observe layer reuse

Create a second tag for the exact same local image and push it to hosted. Because the content graph is unchanged, the registry should not need to upload the same layer/config blobs again; only reference/manifest work should be needed. Client output wording varies, so prove reuse with digest comparison as well.

CANDIDATE_REF="$NX_REGISTRY/academy-ch08-hosted/learner-example/ch08-app:candidate"
podman tag "$HOSTED_REF" "$CANDIDATE_REF"
podman push   --authfile "$REGISTRY_AUTH_FILE"   --tls-verify=false   --digestfile "$LAB/evidence/candidate-manifest-digest.txt"   "$CANDIDATE_REF"   | tee "$LAB/evidence/candidate-push.txt"

diff -u "$LAB/evidence/hosted-manifest-digest.txt"         "$LAB/evidence/candidate-manifest-digest.txt"

For a pure retag of the same manifest, the manifest digest should match. If it does not, inspect the exact tool/media-type behavior rather than declaring the tags “the same image” from name alone.

12. Challenge: choose the correct endpoint

A developer wants to push learner-example/ch08-app:1.1.0 and then let builds consume both internal and public images. Which endpoint(s) should they use in this Community lab?

The correct design is push to hosted, pull from group. The proxy is read-through upstream cache, not your write target. A group is the read aggregation endpoint; deployment to Docker/npm groups is a Pro capability, and this lab does not rely on it.

Knowledge check

Why use FROM scratch for the hosted-push exercise?

Why can the group serve the internal image without storing a second copy as a new publication?

What proves exact identity better than “candidate points to the same thing”?

Why is a second pull not automatically proof that Nexus served from its proxy cache?

Why is the lab login file disposable?

Next lesson

Design the production registry boundary

Choose routing, repository format, tag policy, authentication, proxy scope, and digest-pinned deployment based on observable tradeoffs rather than convenience alone.

Official references and version notes

Version-sensitive statements were rechecked against Sonatype, Docker, and OCI primary documentation on 2026-08-26. The mandatory lab assumes self-hosted Nexus Repository Community Edition 3.95.0, Java 21 on the Nexus side, native OCI repositories introduced in Nexus 3.94.0, and a current Docker-compatible client. Record docker version or podman version locally; client behavior evolves independently of Nexus. Re-check the live release and format documentation before executing the lab.

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.