Chapter 04Lesson 02~170 minutes

Hosted, Proxy, and Group Repositories: Design Patterns, Routing, Caching, and Promotion Flows: Guided Hands-On Workflow and Core Operations

Build a disposable Community Edition Maven topology on loopback with hosted, proxy, and group repositories. Publish only to hosted, resolve through groups, observe proxy cache state with direct HTTP evidence, and verify member order before relying on it.

Community labMaven topologyCold/warm cacheHosted-only writesGroup order

Learning objectives

  • Create disposable Maven hosted, proxy and group repositories with explicit roles and ordering.
  • Publish a synthetic Maven component only to a hosted repository.
  • Prove group visibility before and after publication using direct HTTP and REST evidence.
  • Observe a proxy cold fetch and verify the cached asset exists in the proxy repository.
  • Reason from repository type and group order instead of memorizing UI clicks.

Disposable instance only. This lab assumes the Chapter 02 local baseline: self-hosted Nexus Repository Community Edition 3.94.1-06, Java 21, 127.0.0.1:8081, embedded H2 only for disposable learning, and the default file blob store. Use no production namespace, credential, repository or public endpoint other than the documented Maven Central dependency fetch.

1. Preflight: prove writable state and avoid name collisions

Repository configuration is persistent Nexus state. Before creating the topology, capture the current repository list and stop if any of the disposable names already exist unexpectedly. This protects you from overwriting a previous learner’s work.

# POSIX/Bash. Windows PowerShell guidance follows this block.
LAB="$HOME/nexus-ch04-lab"
NX_URL="http://127.0.0.1:8081"
mkdir -p "$LAB/evidence" "$LAB/payload" "$LAB/promote"
umask 077
read -rsp "Disposable Nexus admin password: " NX_PASS; printf "\n"
printf 'machine 127.0.0.1 login admin password %s\n' "$NX_PASS" > "$LAB/nexus.netrc"
unset NX_PASS
NETRC="$LAB/nexus.netrc"

# Never print, commit, or reuse this temporary credential file.
curl -fsS "$NX_URL/service/rest/v1/status/writable"   | tee "$LAB/evidence/01-writable.txt"

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

python3 - <<'PY'
import json, pathlib
wanted = {
  "academy-ch04-dev","academy-ch04-release","academy-ch04-central",
  "academy-ch04-dev-read","academy-ch04-public-read"
}
p = pathlib.Path.home()/"nexus-ch04-lab/evidence/02-before.json"
found = {r["name"] for r in json.loads(p.read_text())} & wanted
print("name collisions:", sorted(found))
PY

Windows PowerShell: keep the same loopback URLs and use curl.exe for the multipart examples. Store credentials in a temporary user-only file or Windows credential mechanism rather than embedding them in command history. Use Get-FileHash -Algorithm SHA256 instead of sha256sum. The repository and HTTP semantics are unchanged.

If the collision list is non-empty, do not delete anything unless you can prove it belongs to this disposable lab. Choose a new prefix or perform the explicit cleanup at the end of a known prior run.

2. Topology and predictions

Repository Type Reads from / writes to Purpose
academy-ch04-dev Maven2 hosted Write target; direct/group reads Disposable candidate origin.
academy-ch04-release Maven2 hosted Write target; direct/group reads Disposable immutable release origin.
academy-ch04-central Maven2 proxy Remote Maven Central; local cache Controlled public upstream boundary.
academy-ch04-dev-read Maven2 group dev → release → central Developer read endpoint.
academy-ch04-public-read Maven2 group release → central Release/public read endpoint; excludes dev.

Before creating anything, write down these predictions: creating the five repositories changes repository configuration but creates no promotion-demo assets; publishing the synthetic component to academy-ch04-dev should make it readable through academy-ch04-dev-read but not academy-ch04-public-read; and requesting one public artifact through a group should create proxy cache evidence in academy-ch04-central.

3. Create the five repositories in the UI

The chapter intentionally uses the UI for the topology creation step. Repository creation REST payloads are format/type-specific, and Chapter 20 teaches automated provisioning with the embedded OpenAPI/Swagger schema. Here, the purpose is to understand each field and its state effect.

  1. Create maven2 (hosted) academy-ch04-dev: blob store default; version policy Mixed; layout Strict; strict content-type validation enabled. This is a disposable candidate repository.
  2. Create maven2 (hosted) academy-ch04-release: blob store default; version policy Release; layout Strict; deployment/write policy Allow once. This prevents casual redeployment of the same release path.
  3. Create maven2 (proxy) academy-ch04-central with remote storage https://repo1.maven.org/maven2/, blob store default, strict layout and auto-blocking enabled. Leave cache ages at the current documented/default values shown by your pinned build and record those values.
  4. Create maven2 (group) academy-ch04-dev-read with members in exact order: academy-ch04-dev, academy-ch04-release, academy-ch04-central.
  5. Create maven2 (group) academy-ch04-public-read with members in exact order: academy-ch04-release, academy-ch04-central.

Creating repositories changes database-held configuration. The proxy does not pre-download Maven Central. The groups do not copy member blobs. That distinction is what the next evidence step verifies.

4. Verify configuration before publishing

curl --fail-with-body --silent --show-error --netrc-file "$NETRC"   "$NX_URL/service/rest/v1/repositorySettings"   > "$LAB/evidence/03-settings-after-create.json"

python3 - <<'PY'
import json, pathlib
wanted = {
  "academy-ch04-dev","academy-ch04-release","academy-ch04-central",
  "academy-ch04-dev-read","academy-ch04-public-read"
}
p = pathlib.Path.home()/"nexus-ch04-lab/evidence/03-settings-after-create.json"
for r in json.loads(p.read_text()):
    if r.get("name") in wanted:
        print(json.dumps(r, indent=2, sort_keys=True))
PY

In the output, prove the proxy remote URL and the group member order. If the settings endpoint on your pinned build returns a shape that differs from the example, use the embedded Swagger page and the repository UI to record the same evidence rather than guessing field names.

5. Build one harmless Maven component

The JAR is only a ZIP containing a text file. Its namespace is reserved for examples. Record the local checksum now; later lessons will distinguish the build checksum from the accepted hosted checksum and the promoted checksum.

python3 - <<'PY'
from pathlib import Path
from zipfile import ZipFile, ZIP_DEFLATED
root = Path.home()/"nexus-ch04-lab/payload"
root.mkdir(parents=True, exist_ok=True)
(root/"README.txt").write_text("Chapter 04 exact-byte promotion demo\n", encoding="utf-8")
with ZipFile(root/"promotion-demo-1.0.0.jar","w",ZIP_DEFLATED) as z:
    z.write(root/"README.txt","README.txt")
(root/"promotion-demo-1.0.0.pom").write_text("""<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.academy</groupId>
  <artifactId>promotion-demo</artifactId>
  <version>1.0.0</version>
</project>
""", encoding="utf-8")
PY

sha256sum "$LAB/payload/promotion-demo-1.0.0.jar"   | tee "$LAB/evidence/04-local-sha256.txt"

6. Publish only to the dev hosted repository

The Components API expresses the same semantic rule as the UI: upload is a write to a hosted repository. The authenticated administrator is acceptable only because this is a loopback disposable teaching instance; Chapter 15 replaces broad administration with least-privilege publishing identities.

curl --fail-with-body --silent --show-error --netrc-file "$NETRC"   -X POST "$NX_URL/service/rest/v1/components?repository=academy-ch04-dev"   -F "maven2.groupId=com.example.academy"   -F "maven2.artifactId=promotion-demo"   -F "maven2.version=1.0.0"   -F "maven2.asset1=@$LAB/payload/promotion-demo-1.0.0.jar"   -F "maven2.asset1.extension=jar"   -F "maven2.asset2=@$LAB/payload/promotion-demo-1.0.0.pom"   -F "maven2.asset2.extension=pom"   -o "$LAB/evidence/05-upload-body.txt"   -w "%{http_code}\n"   | tee "$LAB/evidence/05-upload-status.txt"

A successful component upload normally returns an empty body with a success status. Do not infer success from silence alone; record the HTTP status and independently search the repository.

curl --fail-with-body --silent --show-error --netrc-file "$NETRC"   "$NX_URL/service/rest/v1/search/assets?repository=academy-ch04-dev&group=com.example.academy&name=promotion-demo&version=1.0.0"   | tee "$LAB/evidence/06-dev-search.json"

7. Prove group visibility and exclusion

The Maven path is deterministic from the coordinates. Read it directly through both groups to isolate Nexus routing from a Maven client cache.

ART_PATH="com/example/academy/promotion-demo/1.0.0/promotion-demo-1.0.0.jar"

curl --fail-with-body --silent --show-error   "$NX_URL/repository/academy-ch04-dev-read/$ART_PATH"   -o "$LAB/evidence/07-from-dev-read.jar"
sha256sum "$LAB/evidence/07-from-dev-read.jar"

# Expected before promotion: 404 from public-read because dev is not a member.
curl --silent --show-error -o "$LAB/evidence/08-public-before-body.txt"   -w "%{http_code}\n"   "$NX_URL/repository/academy-ch04-public-read/$ART_PATH"   | tee "$LAB/evidence/08-public-before-status.txt"

The dev-read checksum should match the locally built artifact. The public-read request should be missing before promotion because its member list deliberately excludes the dev hosted repository. If it unexpectedly succeeds, stop and inspect group membership or an earlier duplicate artifact before continuing.

8. Observe a proxy cold fetch without timing folklore

Do not declare “cache hit” merely because a second request is faster. Instead, ask whether the artifact appears as an asset in the proxy repository itself before and after a request through the group. Use one small, stable Maven Central artifact.

PUBLIC_PATH="org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar"

curl --fail-with-body --silent --show-error --netrc-file "$NETRC"   "$NX_URL/service/rest/v1/search/assets?repository=academy-ch04-central&group=org.apache.commons&name=commons-lang3&version=3.12.0"   > "$LAB/evidence/09-proxy-before.json"

curl --fail-with-body --silent --show-error   "$NX_URL/repository/academy-ch04-dev-read/$PUBLIC_PATH"   -o "$LAB/evidence/10-central-first.jar"

curl --fail-with-body --silent --show-error --netrc-file "$NETRC"   "$NX_URL/service/rest/v1/search/assets?repository=academy-ch04-central&group=org.apache.commons&name=commons-lang3&version=3.12.0"   > "$LAB/evidence/11-proxy-after.json"

curl --fail-with-body --silent --show-error   "$NX_URL/repository/academy-ch04-dev-read/$PUBLIC_PATH"   -o "$LAB/evidence/12-central-second.jar"

sha256sum "$LAB/evidence/10-central-first.jar" "$LAB/evidence/12-central-second.jar"

If the “before” query is already populated, the lab is not truly cold—perhaps you ran it earlier. That is not an error; record it honestly. The important causal evidence is repository-scoped asset state plus identical retrieved bytes. The second request can be served by Nexus cache subject to current proxy cache rules, while a package manager could add yet another cache layer.

9. Small challenge: choose the control

A team wants production consumers to resolve com.example.academy only from accepted internal releases, while developers may see candidate builds. Which control solves that requirement with the smallest blast radius?

Do not create another universal endpoint. The appropriate design is already present: production-like consumers use academy-ch04-public-read, whose members exclude dev; developers use academy-ch04-dev-read. Later, routing rules can additionally prevent internal namespaces from being requested from the public proxy.

Knowledge check

Why did the lab query the proxy repository directly before and after the public dependency request?

Why is academy-ch04-public-read expected to return 404 for the synthetic component before promotion?

What state changes when a proxy first downloads a remote component?

What is wrong with publishing the synthetic component to the group?

If the group order differs from your plan, when should you fix it?

10. Summary

You created explicit write and read paths, proved that a group does not copy its members, demonstrated visibility differences between dev and public-read groups, and observed proxy cache state with direct evidence. Lesson 3 asks which topology choices survive larger teams and production constraints.

Next lesson

Configuration, design choices and tradeoffs

Turn the lab topology into deliberate architecture decisions about endpoints, policy separation, cache freshness, namespace ownership and promotion.

Official references and version notes

Version-sensitive statements were rechecked against Sonatype primary documentation on 2026-08-26. The mandatory path pins the same self-hosted Community lab baseline used by Chapters 02–03: Nexus Repository 3.94.1-06, Java 21, one loopback single-node instance, embedded H2 only for disposable learning, and the default file blob store. Production database/storage decisions are deferred to Chapter 05. Re-check the current download/status/release-note pages before execution.

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.