Chapter 18Lesson 02220–300 min

Cleanup Policies, Retention, Component Age, Last-Downloaded Rules, Preview, and Storage Reclamation: Guided Hands-On Workflow and Core Operations

Practice the complete retention workflow on disposable data: inventory first, model age and usage safely, build a narrow policy, preview it, execute logical cleanup, verify the surviving artifacts, and reclaim blob space only after evidence review.

Disposable labRaw hostedPreviewSoft deleteCompaction

Learning objectives

  • Create a disposable Raw hosted repository and synthetic retention dataset without touching global package-manager configuration.
  • Separate a deterministic live deletion exercise from an age/usage fixture that does not falsify Nexus internal timestamps.
  • Preview and execute a narrowly scoped cleanup policy using current supported UI behavior.
  • Prove logical deletion independently from physical blob-store reclamation.
  • Capture a reproducible evidence packet and remove only resources created by the lab.

1. Scenario and safety envelope

You operate a small build platform with fast-moving development artifacts. The team wants to learn retention mechanics today, but component-age and last-downloaded criteria are measured in days. Changing the server clock, database rows, or blob metadata to make artifacts “old” would teach an unsafe practice. This lab therefore has two synchronized tracks:

  1. Live Nexus track: a disposable Raw hosted repository uses an Asset Name Matcher to make candidate selection deterministic and immediate. You preview, execute, verify soft deletion, then compact only the dedicated lab blob store.
  2. Retention-model track: a local CSV fixture contains controlled published/last-downloaded dates. A tiny Python evaluator demonstrates exactly how age and usage interact before you later apply real day-based policies to naturally aged content.

Isolation rule. Use only a disposable local Nexus instance, a dedicated lab repository, a dedicated lab blob store if your edition/UI makes that practical, synthetic paths under learner-example/, and fake credentials. Never run this exercise on an employer repository, shared blob store, production database, or valuable backup.

Version baseline (26 August 2026). Sonatype's current 3.95.x release notes list Nexus Repository 3.95.2 (released 21 August 2026) as the newest patch in that line. The 3.95 release expanded cleanup-policy administration and retain-last-N coverage. Current Nexus Repository 3.95.x system requirements use Java 21; official bundles include the recommended runtime. Record the exact version, edition, database, and blob-store type of your lab before applying any cleanup rule because cleanup behavior and available criteria are version- and format-sensitive.

2. Preflight: prove what instance you are about to change

Record the exact version, edition, database type, blob-store name, repository list, and task state. For the mandatory path, Community Edition is sufficient for normal cleanup policies; do not depend on the Pro-only cleanup-policy REST management API or Retain Select Versions.

Check Expected lab condition Stop if
Nexus Disposable self-hosted 3.95.x; examples validated against 3.95.2 docs Production/shared instance
Runtime Supported Java 21 runtime Unsupported runtime or unknown version
Database Local H2 is acceptable for this small single-node lab You are about to manipulate DB files directly
Repository New cleanup-lab-raw hosted Raw repository Name already exists with unknown content
Blob store Prefer dedicated cleanup-lab-blobs file blob store It contains unrelated repositories
Auth Temporary admin only for setup; least-privilege reader for verification Real token/password would be copied into lesson commands
# POSIX/Bash — read-only preflight
BASE='http://127.0.0.1:8081'
curl -fsS "$BASE/service/rest/v1/status"
# Inspect Settings → Repository → Repositories, Blob Stores,
# Cleanup Policies, and System → Tasks in the UI as well.

3. Create the disposable repository boundary

In Settings → Repository → Blob Stores, create a file blob store named cleanup-lab-blobs if your lab does not already have a safe disposable store. Then create a Raw hosted repository named cleanup-lab-raw on that blob store. Keep deployment policy appropriate for a lab and do not enable anonymous write access.

Using a dedicated blob store makes the final reclamation observation understandable: compaction affects only disposable bytes. If you must use a shared lab blob store, stop before compaction and treat physical reclamation as a simulation because compacting a shared store crosses the lab's isolation boundary.

4. Populate synthetic “keep” and “expire” paths

Raw is useful here because the cleanup criterion can match asset paths without introducing Maven/npm/Docker metadata. Upload four tiny files through the supported repository HTTP endpoint. Use a credential from an environment variable rather than embedding it in shell history.

# POSIX/Bash — disposable lab only
BASE='http://127.0.0.1:8081'
REPO='cleanup-lab-raw'
: "${NEXUS_USER:?set a temporary lab user}"
: "${NEXUS_PASS:?set a temporary lab password}"

mkdir -p cleanup-lab-payloads
printf 'release 1\n' > cleanup-lab-payloads/release-1.txt
printf 'release 2\n' > cleanup-lab-payloads/release-2.txt
printf 'ci build 101\n' > cleanup-lab-payloads/build-101.txt
printf 'ci build 102\n' > cleanup-lab-payloads/build-102.txt

curl -fsS -u "$NEXUS_USER:$NEXUS_PASS" --upload-file cleanup-lab-payloads/release-1.txt   "$BASE/repository/$REPO/learner-example/releases/release-1.txt"
curl -fsS -u "$NEXUS_USER:$NEXUS_PASS" --upload-file cleanup-lab-payloads/release-2.txt   "$BASE/repository/$REPO/learner-example/releases/release-2.txt"
curl -fsS -u "$NEXUS_USER:$NEXUS_PASS" --upload-file cleanup-lab-payloads/build-101.txt   "$BASE/repository/$REPO/learner-example/ci-expire/build-101.txt"
curl -fsS -u "$NEXUS_USER:$NEXUS_PASS" --upload-file cleanup-lab-payloads/build-102.txt   "$BASE/repository/$REPO/learner-example/ci-expire/build-102.txt"

Verify all four paths with HEAD or GET requests and Browse. Capture checksums locally before cleanup. The repository should be authoritative for these synthetic bytes; no proxy/upstream state participates.

sha256sum cleanup-lab-payloads/*.txt
for p in  learner-example/releases/release-1.txt  learner-example/releases/release-2.txt  learner-example/ci-expire/build-101.txt  learner-example/ci-expire/build-102.txt; do
  curl -fsSI -u "$NEXUS_USER:$NEXUS_PASS" "$BASE/repository/$REPO/$p" | head -n 1
done

5. Build controlled age/usage evidence without falsifying Nexus

Create a local fixture that represents the lifecycle SLA you actually want: remove development artifacts only when they are at least 30 days old and have not been downloaded for 14 days; never use this fixture as proof that the live repository has those timestamps.

path,published_days_ago,last_downloaded_days_ago,class
learner-example/ci/build-097.txt,45,31,development
learner-example/ci/build-098.txt,40,2,development
learner-example/ci/build-099.txt,12,12,development
learner-example/releases/release-1.txt,180,120,release
learner-example/releases/release-2.txt,20,3,release
# evaluate_retention.py — local simulation, not a Nexus API
import csv

AGE_DAYS = 30
USAGE_DAYS = 14

with open("retention-fixture.csv", newline="", encoding="utf-8") as f:
    rows = list(csv.DictReader(f))

for row in rows:
    old = int(row["published_days_ago"]) >= AGE_DAYS
    idle = int(row["last_downloaded_days_ago"]) >= USAGE_DAYS
    development = row["class"] == "development"
    candidate = old and idle and development
    print(f"{row['path']}: {'DELETE-CANDIDATE' if candidate else 'KEEP'}")

The expected candidate is build-097.txt only. Build 098 is old but recently used; build 099 is idle-looking but too new; releases are protected by lifecycle class. Nexus policies may express release protection differently by format, but the AND reasoning is the same.

6. Create a deterministic live cleanup policy

In Settings → Repository → Cleanup Policies, create cleanup-lab-ci-expire for Raw. Use the Asset Name Matcher supported for Raw and match only the disposable subtree. For the H2/PostgreSQL baseline used here, current Sonatype examples represent the asset name with a leading slash, so use an anchored Java-regex pattern:

^/learner-example/ci-expire/.*$

Do not add age or usage to the live policy for this immediate exercise because the four files were just uploaded and should not be made artificially old. In 3.95, current UI documentation allows repositories to be associated from the policy configuration; if your exact patch/interface presents association on the repository instead, attach the same policy from the repository's Cleanup section. Record which UI path your version uses.

Prediction 1. Preview should select the two ci-expire assets and exclude both releases assets. Prediction 2. After logical cleanup, the deleted paths should return not-found while physical capacity may remain nearly unchanged until compaction.

7. Preview before execution

Open the policy's preview for cleanup-lab-raw. Confirm that every selected path is under learner-example/ci-expire/ and that both release paths are absent. If the preview contains anything unexpected, do not execute—fix the policy first.

Capture the policy name, repository, regex, preview timestamp, and candidate count. Remember that current documentation calls preview point-in-time evidence. Its job is to show that your intent and Nexus's interpretation agree before mutation.

8. Execute supported cleanup and verify logical deletion

Current Nexus maintains a system cleanup service task for repositories with associated policies. On a disposable lab you may run that task manually from System → Tasks after confirming the scope, or wait for its schedule. Record the task name, start/end status, and result. Do not create an ad-hoc filesystem deletion job.

# Verification after the cleanup service has completed
for p in  learner-example/ci-expire/build-101.txt  learner-example/ci-expire/build-102.txt; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' -u "$NEXUS_USER:$NEXUS_PASS"     "$BASE/repository/$REPO/$p")
  printf '%s -> %s\n' "$p" "$code"
done

for p in  learner-example/releases/release-1.txt  learner-example/releases/release-2.txt; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' -u "$NEXUS_USER:$NEXUS_PASS"     "$BASE/repository/$REPO/$p")
  printf '%s -> %s\n' "$p" "$code"
done

Expected: the two expired lab paths are unavailable; the two release paths remain available. Also verify Browse/search and task history. This proves repository semantics—not physical disk recovery.

9. Reclaim physical storage only after accepting the deletion set

If and only if cleanup-lab-blobs is dedicated to this exercise, create or use an Admin - Compact blob store task targeted at that blob store. Current versions allow a Blobs Older Than retention period so soft-deleted blobs can remain recoverable for a controlled window before hard deletion. For a training instance, use a value consistent with your lab intent and understand that hard deletion is the point after which repository-level recovery options become much more limited.

Destructive boundary. Compaction permanently removes eligible soft-deleted blob content. Do not run it on a shared or production blob store merely to make a storage graph move during a lesson. If the lab blob store is shared, stop here and document the expected operation instead.

Measure capacity at the volume/blob-store level before and after, not by deleting or rewriting internal blob files. A four-file lab may be too small for a visible filesystem change; correctness is established by task success and repository evidence, not by requiring a large number of bytes to disappear.

10. What changed at each step

Step Repository/database state Blob state Client observation
Upload Raw assets/components registered New blobs allocated 200 on GET
Create/attach policy Configuration changes only No content change All artifacts still readable
Preview Read-only candidate evaluation No deletion All artifacts still readable
Cleanup service Matched content logically removed/soft-deleted Soft-deleted bytes can remain Matched paths unavailable
Compaction Deletion already accepted Eligible soft-deleted blobs hard deleted No change to which paths are already absent

11. Challenge: choose the control, not the clicks

Your team says: “Delete build artifacts older than 30 days, but only if nobody downloaded them for 14 days; keep release artifacts regardless of age.” Write the policy logic you would want for a versioned format that supports release type. Explain why age alone is insufficient, why usage alone is insufficient, and whether a separate dev repository would make this policy safer. Do not execute a new rule until you can predict its candidate set from an inventory.

12. Lab cleanup and rollback

  1. Save the evidence packet first: screenshots/notes, fixture output, checksums, task result, and verification status.
  2. Delete the cleanup policy association from the disposable repository, then delete the policy if no other repository uses it.
  3. Delete cleanup-lab-raw through the Nexus UI/API supported for repository administration.
  4. If the dedicated blob store is now unused, delete it through Nexus after verifying no repository references it.
  5. Remove local payload/fixture files and unset NEXUS_USER/NEXUS_PASS.
  6. If compaction was intentionally skipped, record that physical reclamation was not performed.

Rollback for an accidentally selected component depends on whether hard deletion has occurred and on available supported recovery/backup evidence. This chapter does not normalize undelete as a substitute for safe preview; Chapter 25 will treat tested backup/restore as the recovery control.

13. Knowledge check

Why does the live lab use Asset Name Matcher instead of fabricating old timestamps?

What must be true before running compaction in this lab?

Build 098 is 40 days old but downloaded 2 days ago. Does a 30-day-age AND 14-day-usage rule delete it?

What evidence proves the release paths were protected?

Why might disk free space barely change in this tiny lab?

14. Summary and next step

You have now separated retention reasoning from destructive implementation: deterministic live cleanup proves policy → preview → logical deletion → compaction, while a fixture proves age/usage logic without corrupting Nexus metadata. Lesson 3 moves from one lab to production design choices: how long to retain releases, how to trust usage data, and how to schedule cleanup without harming reliability.

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.