Upgrade Planning, Version Support, Breaking Changes, Java Runtime Upgrades, and Rollback Strategy: Guided Hands-On Workflow and Core Operations
A safe upgrade is rehearsed with the same evidence gates you will use in production. This lesson builds a local, fixture-driven runbook that records the baseline, calculates crossed thresholds, checks recovery readiness, injects one compatibility failure, and validates the target before acceptance.
Learning objectives
- Capture an upgrade baseline without mutating Nexus.
- Calculate threshold actions from source and target versions.
- Model recovery, Java/truststore, plugin, disk, and database gates.
- Run post-upgrade smoke/client/security checks against deterministic fixtures.
- Use a failed gate to prove that the runbook stops before mutation.
1. Create the disposable upgrade fixture
from pathlib import Path
import json, shutil, hashlib
root=Path('ch27-lab')
if root.exists(): shutil.rmtree(root)
(root/'source/blobs').mkdir(parents=True)
asset=root/'source/blobs/app-1.0.0.jar'
asset.write_bytes(b'CH27 APP 1.0.0\n')
state={
'nexus':'3.84.2','java':17,'database':'H2','edition':'Community',
'blobStore':'file','freeGiB':20,'communityPlugins':['legacy-example-plugin'],
'customTrustAnchors':['repo-ca'],
'assetSha256':hashlib.sha256(asset.read_bytes()).hexdigest()
}
(root/'source/state.json').write_text(json.dumps(state,indent=2)+'\n')
print(state)
The fixture deliberately starts before the 3.85 and 3.87 thresholds so the gate engine has work to do.
2. Calculate crossed upgrade gates
from pathlib import Path
import json
from packaging.version import Version
root=Path('ch27-lab')
s=json.loads((root/'source/state.json').read_text())
source=Version(s['nexus']); target=Version('3.95.2')
gates=[]
def crossed(v):
v=Version(v); return source < v <= target
if crossed('3.85.0'): gates.append('run Repair - Rebuild repository search after upgrade')
if crossed('3.87.0'): gates.append('Java 21 bundled runtime; re-provision custom trust anchors')
(root/'crossed-gates.json').write_text(json.dumps(gates,indent=2)+'\n')
print(*gates,sep='\n')
3. Gate the change before mutation
from pathlib import Path
import json
root=Path('ch27-lab'); s=json.loads((root/'source/state.json').read_text())
preflight={
'backupVerified':True,
'targetReleaseNotesReviewed':True,
'java21Plan':True,
'trustAnchorsReprovisioned':False,
'communityPluginsResolved':False,
'diskHeadroomAdequate':s['freeGiB'] >= 10,
'databasePathSupported':True
}
blocked=[k for k,v in preflight.items() if not v]
(root/'preflight-1.json').write_text(json.dumps(preflight,indent=2)+'\n')
print('BLOCKED:',blocked)
assert blocked==['trustAnchorsReprovisioned','communityPluginsResolved']
The runbook fails early by design. The correct response is not to weaken the check; it is to resolve the dependencies.
4. Resolve the blockers explicitly
For the lab, remove the unsupported plugin from the planned target and model the custom CA as deliberately imported into the new Java 21 trust path. In a real environment, use your supported certificate-management mechanism and verify the remote endpoint after startup.
from pathlib import Path
import json
root=Path('ch27-lab')
p=json.loads((root/'preflight-1.json').read_text())
p['trustAnchorsReprovisioned']=True
p['communityPluginsResolved']=True
assert all(p.values())
(root/'preflight-2.json').write_text(json.dumps(p,indent=2)+'\n')
print('PRECHECK: PASS')
5. Clone the recovery checkpoint
The fixture uses a deterministic copy only to model separation. A real Nexus backup must follow the supported Chapter 25 database/blob procedure.
from pathlib import Path
import shutil, hashlib, json
root=Path('ch27-lab')
shutil.copytree(root/'source',root/'checkpoint')
manifest={}
for p in sorted((root/'checkpoint').rglob('*')):
if p.is_file(): manifest[p.relative_to(root/'checkpoint').as_posix()]=hashlib.sha256(p.read_bytes()).hexdigest()
(root/'checkpoint-manifest.json').write_text(json.dumps(manifest,indent=2)+'\n')
print('checkpoint files:',len(manifest))
6. Rehearse the target state
from pathlib import Path
import json, shutil
root=Path('ch27-lab')
shutil.copytree(root/'source',root/'rehearsal')
s=json.loads((root/'rehearsal/state.json').read_text())
s.update({'nexus':'3.95.2-01','java':21,'communityPlugins':[],'trustAnchorsReady':True})
(root/'rehearsal/state.json').write_text(json.dumps(s,indent=2)+'\n')
print('rehearsal target:',s['nexus'], 'java',s['java'])
7. Run required post-upgrade actions
Model the threshold action as explicit evidence rather than an undocumented click:
{
"postUpgradeTasks": [
{"name":"Repair - Rebuild repository search","reason":"crossed 3.85.0 threshold","status":"COMPLETE"}
]
}
On a live lab, confirm the exact task availability/name on the pinned Nexus version before running it.
8. Smoke and client validation
from pathlib import Path
import hashlib, json
root=Path('ch27-lab')
s=json.loads((root/'source/state.json').read_text())
asset=root/'rehearsal/blobs/app-1.0.0.jar'
checks={
'startup':'PASS','statusApi':'PASS','repoRead':'PASS','leastPrivilege':'PASS',
'proxyTls':'PASS','tasks':'PASS',
'assetHashMatches':hashlib.sha256(asset.read_bytes()).hexdigest()==s['assetSha256']
}
assert all(v=='PASS' or v is True for v in checks.values())
(root/'acceptance.json').write_text(json.dumps(checks,indent=2)+'\n')
print('ACCEPTANCE: PASS')
9. Optional live commands: read-only first
# Disposable loopback Nexus only.
curl -fsS http://127.0.0.1:8081/service/rest/v1/status
curl -fsS -u "$NEXUS_LAB_USER:$NEXUS_LAB_PASSWORD" http://127.0.0.1:8081/service/rest/v1/repositories
Do not print credentials with shell tracing. Verify the running version through UI/system information or supported status/system evidence for the pinned build.
10. Challenge: choose the correction
Your rehearsal starts, but a proxy to
repo.example.invalid fails TLS immediately after
crossing into bundled Java 21. The correct first hypothesis is the
runtime trust boundary—not “disable TLS verification.” Compare the
old and new trust-anchor provisioning and correct the new runtime.
11. Cleanup
Delete only the local ch27-lab fixture after preserving
sanitized evidence. In a real rehearsal, retain the recovery
checkpoint until the production change is accepted and the rollback
window is closed.
Knowledge check
Why did the lab intentionally begin with a failed preflight?
To prove the runbook blocks mutation when truststore/plugin dependencies are unresolved rather than discovering them after startup.
What does the 3.85 threshold add to the plan?
A required Repair - Rebuild repository search action when crossing into 3.85+ according to current upgrade-path guidance.
What should replace a production filesystem copy of active Nexus state?
The supported coherent backup/recovery workflow for the actual database and blob backend.
Why test proxy TLS after the upgrade?
The Java runtime/truststore boundary can change even when repository configuration is unchanged.
Why compare an artifact hash?
It proves the bytes served after upgrade match the pre-upgrade evidence, not merely that metadata counts are similar.
Summary and next step
The guided workflow proved that the upgrade can be blocked before mutation, rehearsed against cloned state, and accepted only after threshold actions and behavioral checks pass.
Lesson 3 converts these mechanics into design choices for different operating models.
Official references and version notes
- Sonatype: Upgrade Nexus Repository — standalone upgrade workflow, backups, install/data separation, vmoptions, TLS/custom configuration, and validation.
- Sonatype: Nexus Repository Upgrade Paths — version-crossing actions and compatibility gates.
- Sonatype: Nexus Repository 3 Versions Status — support status and community-plugin guidance.
- Sonatype: Nexus Repository 3.95.x Release Notes — current-line changes, known issues, and upgrade guidance.
- Sonatype: System Requirements — current Java 21 and operating requirements.
- Sonatype: Upgrade Nexus Repository Java Version — bundled Java behavior and external-JVM considerations.
- Sonatype: Java Runtime Compatibility Matrix — release-specific external Java compatibility.
- Sonatype: Prepare a Backup — coherent database/blob/configuration recovery preparation.
- Sonatype: Upgrading to 3.71.0 and Beyond — legacy OrientDB/H2 gates.
- Sonatype: Rolling Upgrades in High Availability — Pro/HA mixed-version and finalize-upgrade semantics.
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.