Checkpoint Lab — Upgrade Planning, Version Support, Breaking Changes, Java Runtime Upgrades, and Rollback Strategy
The checkpoint lab treats an upgrade as an auditable change: define service objectives, freeze the exact source evidence, prove a recovery point, calculate version gates, rehearse, inject a failure, validate the corrected target, make a go/no-go decision, and demonstrate rollback without touching production infrastructure.
Learning objectives
- Build a complete upgrade runbook and evidence packet.
- Predict state changes before target startup.
- Block an upgrade on an injected compatibility failure and repair it safely.
- Validate repository bytes, permissions, tasks, runtime, and client behavior.
- Demonstrate rollback by restoring the untouched checkpoint rather than downgrading upgraded state.
1. Scenario and objectives
You operate a disposable single-node Community-style instance modeled as Nexus 3.84.2/H2/file blob. The desired target is the 3.95.2-01 line with Java 21. RTO for the drill is 45 minutes; no target-only publications are allowed until acceptance. You must prove the source can be restored before mutation.
2. Write the runbook contract
change:
source: 3.84.2
target: 3.95.2-01
database: H2
blobStore: file
edition: Community
rtoMinutes: 45
targetOnlyWritesBeforeAcceptance: forbidden
preflight:
- version-status-reviewed
- target-release-notes-reviewed
- crossed-upgrade-gates-recorded
- coherent-recovery-checkpoint-verified
- java21-and-truststore-plan
- plugins-resolved
- disk-and-file-handles-checked
acceptance:
- startup
- required-rebuilds
- repository-hash
- least-privilege
- proxy-tls
- representative-client
- logs-clean
rollback:
action: restore-pre-upgrade-checkpoint
3. Predict changes before performing them
| Prediction | Expected observation |
|---|---|
| Runtime moves to Java 21 | Target process reports/uses Java 21; old JVM truststore is not assumed |
| Crossing 3.85 triggers search rebuild action | Documented task scheduled/completed after target startup |
| Application binaries change | New install/image version; persistent data remains the same logical instance |
| Artifact bytes should not change | Pre/post SHA-256 matches for selected hosted assets |
4. Build checkpoint and baseline fingerprint
from pathlib import Path
import json, hashlib, shutil
root=Path('ch27-checkpoint')
if root.exists(): shutil.rmtree(root)
(root/'source/blobs').mkdir(parents=True)
(root/'source/blobs/a.bin').write_bytes(b'CH27 checkpoint bytes\n')
state={'nexus':'3.84.2','java':17,'db':'H2','repo':'ch27-hosted','role':'ch27-reader'}
(root/'source/state.json').write_text(json.dumps(state,indent=2)+'\n')
shutil.copytree(root/'source',root/'recovery')
def fingerprint(d):
out={}
for p in sorted(d.rglob('*')):
if p.is_file(): out[p.relative_to(d).as_posix()]=hashlib.sha256(p.read_bytes()).hexdigest()
return out
base=fingerprint(root/'source')
(root/'baseline.json').write_text(json.dumps(base,indent=2)+'\n')
assert base==fingerprint(root/'recovery')
print('RECOVERY CHECKPOINT: VERIFIED')
5. Gate engine with one injected failure
from pathlib import Path
import json
root=Path('ch27-checkpoint')
gates={
'releaseNotes':True,'upgradePath':True,'backup':True,'java21':True,
'truststoreReady':False,'pluginsResolved':True,'disk':True,'database':True
}
(root/'gates-1.json').write_text(json.dumps(gates,indent=2)+'\n')
blocked=[k for k,v in gates.items() if not v]
print('GO/NO-GO:', 'NO-GO' if blocked else 'GO', blocked)
assert blocked==['truststoreReady']
The injected failure represents a CA that existed only in the old JVM truststore. The correct decision is NO-GO.
6. Correct the failure without bypassing TLS
from pathlib import Path
import json
root=Path('ch27-checkpoint')
g=json.loads((root/'gates-1.json').read_text())
g['truststoreReady']=True
assert all(g.values())
(root/'gates-2.json').write_text(json.dumps(g,indent=2)+'\n')
print('GO/NO-GO: GO')
7. Model the upgrade and post-upgrade task
from pathlib import Path
import json, shutil
root=Path('ch27-checkpoint')
shutil.copytree(root/'source',root/'target')
s=json.loads((root/'target/state.json').read_text())
s.update({'nexus':'3.95.2-01','java':21,'searchRebuild':'COMPLETE','truststoreReady':True})
(root/'target/state.json').write_text(json.dumps(s,indent=2)+'\n')
print('target started:',s['nexus'])
8. Validate the target
from pathlib import Path
import json, hashlib
root=Path('ch27-checkpoint')
base=json.loads((root/'baseline.json').read_text())
target=root/'target'
checks={
'nexusVersion':json.loads((target/'state.json').read_text())['nexus']=='3.95.2-01',
'java21':json.loads((target/'state.json').read_text())['java']==21,
'searchRebuild':json.loads((target/'state.json').read_text())['searchRebuild']=='COMPLETE',
'artifactHash':hashlib.sha256((target/'blobs/a.bin').read_bytes()).hexdigest()==base['blobs/a.bin'],
'leastPrivilege':True,'proxyTls':True,'representativeClient':True,'logsClean':True
}
assert all(checks.values())
(root/'acceptance.json').write_text(json.dumps(checks,indent=2)+'\n')
print('TARGET ACCEPTANCE: PASS')
9. Rollback decision point
Because the drill forbids target-only writes before acceptance, rollback remains clean. To prove it, compare the recovery checkpoint to the original baseline:
from pathlib import Path
import hashlib, json
root=Path('ch27-checkpoint')
base=json.loads((root/'baseline.json').read_text())
out={}
for p in sorted((root/'recovery').rglob('*')):
if p.is_file(): out[p.relative_to(root/'recovery').as_posix()]=hashlib.sha256(p.read_bytes()).hexdigest()
assert out==base
print('ROLLBACK SOURCE: UNCHANGED AND VERIFIED')
10. Evidence packet
- source version/runtime/database/blob inventory
- version-status and release-note review record
- crossed-threshold list
- backup/recovery fingerprint
gates-1.jsonNO-GO evidencegates-2.jsoncorrected GO evidence- post-upgrade task evidence
acceptance.json- representative client/TLS/auth output
- rollback-source fingerprint
11. Cleanup / production handoff
After preserving sanitized evidence, delete only the disposable
ch27-checkpoint lab. For a real production upgrade,
keep the recovery set immutable until the defined rollback window
closes. Do not delete it merely because the target has been running
for a few minutes.
12. What this adds to the operating model
Chapter 27 adds a repeatable upgrade control: every release change has a source fingerprint, current documentation review, threshold evaluation, recovery proof, rehearsal, go/no-go gates, behavioral validation, and restore-based rollback. Chapter 28 can now build HA/resiliency on top of a supported, intentionally upgraded platform rather than multiplying nodes around an unknown version state.
Knowledge check
Why was target-only publication forbidden before acceptance?
It preserves a simple rollback boundary; otherwise rollback must account for writes that exist only on the target.
What caused the intentional NO-GO?
The custom trust anchor had not been provisioned for the new Java 21 runtime.
What did the artifact hash prove?
That selected hosted bytes remained identical across the modeled upgrade.
Why is the search rebuild part of acceptance?
The source→target path crosses the documented 3.85 threshold that requires it.
What is the rollback mechanism in the runbook?
Restore the verified pre-upgrade recovery checkpoint with the matching source version/runtime, not a blind downgrade of upgraded state.
Summary and next step
The checkpoint integrated the chapter into a complete runbook and proved both a NO-GO decision and a successful corrected upgrade without weakening security controls.
Next, Chapter 28 moves from a supported single-instance operating model to resiliency, HA topology, shared state, node coordination, and failure recovery.
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.