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.
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.
-
Create maven2 (hosted)
academy-ch04-dev: blob storedefault; version policy Mixed; layout Strict; strict content-type validation enabled. This is a disposable candidate repository. -
Create maven2 (hosted)
academy-ch04-release: blob storedefault; version policy Release; layout Strict; deployment/write policy Allow once. This prevents casual redeployment of the same release path. -
Create maven2 (proxy)
academy-ch04-centralwith remote storagehttps://repo1.maven.org/maven2/, blob storedefault, strict layout and auto-blocking enabled. Leave cache ages at the current documented/default values shown by your pinned build and record those values. -
Create maven2 (group)
academy-ch04-dev-readwith members in exact order:academy-ch04-dev,academy-ch04-release,academy-ch04-central. -
Create maven2 (group)
academy-ch04-public-readwith 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?
Because repository-scoped asset evidence can show whether Nexus cached the component. Timing alone cannot distinguish Nexus cache from client cache, filesystem cache or network variation.
Why is academy-ch04-public-read expected to return
404 for the synthetic component before promotion?
Its members are release then Central; dev is deliberately absent, so the group has no member that should contain that internal candidate.
What state changes when a proxy first downloads a remote component?
Nexus records cache/component/asset metadata and stores the retrieved binary in the proxy’s blob store. The upstream itself is not changed.
What is wrong with publishing the synthetic component to the group?
A group is a read aggregator, not an authoritative write target. Publication belongs in a hosted repository.
If the group order differs from your plan, when should you fix it?
Before relying on the endpoint or publishing dependent state. Member order is routing policy, so verify and correct configuration while the blast radius is still small.
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.
Official references and version notes
- Download Nexus Repository and Nexus Repository 3 Versions Status — re-check the self-hosted release line before running the pinned lab.
- Repository Types — hosted, proxy and group semantics, nested/transitive groups, ordered member lookup, and direct-member privilege boundaries.
- Configurable Repository Fields — proxy remote storage, component/metadata age, negative cache, blocking, and auto-blocking.
- Repository Actions — current cache invalidation behavior and what it does not delete.
- Repositories API — repository inventory and format/type-specific repository configuration endpoints.
- Components API, Assets API, and Search API — upload plus independent component/asset evidence.
- Maven Repositories — Maven hosted/proxy/group configuration semantics and Central proxy use.
- Routing Rules — controlling proxy requests and preventing internal namespaces from falling through to public remotes.
- Staging and the current feature matrix — Nexus staging/build-promotion is a Pro capability; the mandatory Community lab uses exact-byte copy semantics instead.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.