Production Capstone: Design, Secure, Automate, Migrate, and Recover an Enterprise Artifact Platform: Implementation and Automation Build-Out
The capstone architecture becomes useful only when another operator can reproduce and verify it. This lesson builds a Community-compatible desired-state workflow around disposable repository definitions, service identities, client endpoints, synthetic artifacts, CI-like publication, exact-byte promotion, evidence capture, and idempotent reconciliation.
Learning objectives
- Express repository and security configuration as desired state separate from credentials.
- Reconcile disposable resources idempotently: inspect, diff, create/update, verify, and rerun safely.
- Publish synthetic artifacts with a CI-like service identity and preserve immutable identity evidence.
- Promote exact bytes without rebuilding and prove byte equality.
- Produce operator evidence and rollback/cleanup instructions for every capstone resource.
capstone-lab-*, and every change has
preflight, evidence, verification, and rollback.
1. Preflight before mutation
Record the running Nexus version/edition/runtime, database type, blob store used by the lab repositories, base URL, current repository names, and whether the API schema matches the examples. The current instance's OpenAPI document is authoritative for exact request fields. Never paste a production admin password into a script to “get started.”
CAPSTONE BASELINE
Nexus: 3.95.2-01 reference line
Java: 21
Edition: Community mandatory path
Base URL: http://127.0.0.1:8081
Namespace prefix: capstone-lab-
Package namespaces: com.example / @learner-example
Credentials: disposable lab identities injected at runtime
2. Desired state is data, secrets are not
{
"repositories": [
{"name":"capstone-lab-maven-releases","format":"maven2","type":"hosted"},
{"name":"capstone-lab-maven-central","format":"maven2","type":"proxy"},
{"name":"capstone-lab-maven-public","format":"maven2","type":"group",
"members":["capstone-lab-maven-releases","capstone-lab-maven-central"]},
{"name":"capstone-lab-npm-internal","format":"npm","type":"hosted"},
{"name":"capstone-lab-npmjs","format":"npm","type":"proxy"},
{"name":"capstone-lab-npm-public","format":"npm","type":"group",
"members":["capstone-lab-npm-internal","capstone-lab-npmjs"]},
{"name":"capstone-lab-release-evidence","format":"raw","type":"hosted"}
],
"serviceIdentities": ["capstone-publisher","capstone-reader","capstone-reconciler"]
}
The desired-state file contains names and intent, not passwords/tokens. Runtime secrets come from an environment/CI secret store and are redacted from logs.
3. Reconciliation loop
flowchart TD
D[Desired state] --> G[GET actual state]
G --> N[Normalize relevant fields]
N --> C{Equal?}
C -->|yes| NOOP[No operation]
C -->|no, missing| CREATE[Create]
C -->|no, exists| UPDATE[Update carefully]
CREATE --> VERIFY[Read back + verify]
UPDATE --> VERIFY
VERIFY --> EVIDENCE[Write redacted evidence]
Delete-and-recreate is not the default update strategy because
repositories can contain valuable content and identity
relationships. In the mandatory lab, deletion is permitted only for
empty/disposable capstone-lab-* resources during
cleanup.
4. Fixture reconciler: prove idempotence before touching Nexus
desired = {
"capstone-lab-maven-releases": {"format":"maven2","type":"hosted"},
"capstone-lab-maven-public": {"format":"maven2","type":"group"},
"capstone-lab-npm-internal": {"format":"npm","type":"hosted"},
"capstone-lab-npm-public": {"format":"npm","type":"group"},
"capstone-lab-release-evidence": {"format":"raw","type":"hosted"},
}
actual = {}
def reconcile(actual, desired):
actions=[]
for name,spec in desired.items():
if name not in actual:
actual[name]=spec.copy(); actions.append(("create",name))
elif actual[name] != spec:
actual[name]=spec.copy(); actions.append(("update",name))
else:
actions.append(("noop",name))
return actions
first = reconcile(actual, desired)
second = reconcile(actual, desired)
print(first)
print(second)
assert all(a == "create" for a,_ in first)
assert all(a == "noop" for a,_ in second)
When converted to REST automation, preserve the same logic and handle HTTP 400/401/403/404/409 deliberately. Query the instance-specific Swagger/OpenAPI document before assuming a request schema.
5. Least-privilege implementation order
- Create/verify blob store only if the lab requires a dedicated one.
- Create hosted repositories first.
- Create proxies only when their remote upstream and routing behavior are explicitly defined.
- Create groups after members exist and verify member order.
- Create narrowly scoped roles/privileges.
- Create disposable service users mapped only to required roles.
- Configure temporary clients to use Nexus endpoints.
- Run negative authorization tests before publication.
6. Read-only API preflight
# Safe lab read. Do not expose credentials in command history/logs.
NEXUS_URL='http://127.0.0.1:8081'
curl --fail --silent "$NEXUS_URL/service/rest/v1/status"
# Inspect repository inventory with a disposable authenticated identity.
curl --fail --silent --user "$NEXUS_USER:$NEXUS_PASSWORD" \
"$NEXUS_URL/service/rest/v1/repositories"
# Inspect the exact running API schema before writes.
curl --fail --silent --user "$NEXUS_USER:$NEXUS_PASSWORD" \
"$NEXUS_URL/service/rest/swagger.json" -o capstone-swagger.json
Use environment variables or a secret mechanism appropriate to the shell/CI runner and ensure command tracing is disabled. The fake examples are not production credential-management instructions.
7. Synthetic multi-format publication
Create three harmless release objects from the same fictional build:
| Format/evidence | Identity | Target |
|---|---|---|
| Maven | com.example:capstone-lib:1.0.0 |
capstone-lab-maven-releases |
| npm | @learner-example/capstone-lib@1.0.0 |
capstone-lab-npm-internal |
| Raw evidence | build-20260827/manifest.json |
capstone-lab-release-evidence |
The package-manager commands remain temporary-client configuration. Do not modify the learner's global Maven/npm settings without rollback.
8. Build once and record identity
from hashlib import sha256
build_id = "build-20260827-001"
artifacts = {
"capstone-lib-1.0.0.jar": b"synthetic-java-release\n",
"learner-example-capstone-lib-1.0.0.tgz": b"synthetic-npm-release\n",
"manifest.json": b'{"build":"build-20260827-001"}\n',
}
manifest={}
for name,data in artifacts.items():
manifest[name]={"buildId":build_id,"sha256":sha256(data).hexdigest(),"bytes":len(data)}
print(manifest)
assert len({v["buildId"] for v in manifest.values()}) == 1
Coordinates, build ID, SHA-256, publication time, and source commit are separate evidence. A checksum proves byte equality, not trusted origin or vulnerability safety.
9. Community-compatible promotion proof
Native Staging is Pro-only. The mandatory capstone uses a fixture that copies the exact already-built bytes into a release-stage directory and compares hashes:
from hashlib import sha256
candidate=b"capstone-release-1.0.0\n"
release=bytes(candidate) # transfer exact bytes; no rebuild
assert sha256(candidate).digest() == sha256(release).digest()
print("promotion-byte-identity: PASS")
If you have a licensed environment, Staging can be an optional extension, but the acceptance criterion remains exact artifact identity plus governance evidence.
10. Webhook/event automation boundary
A repository event can wake downstream automation, but receivers must treat delivery as untrusted input until authenticity is validated and duplicate deliveries are handled idempotently. Never let “event received” mean “release approved.” The approval decision depends on immutable identity plus test/policy evidence.
11. Operator evidence packet
capstone-evidence/
00-baseline.md
01-desired-state.json
02-actual-state-redacted.json
03-authz-matrix.csv
04-publication-manifest.json
05-promotion-proof.json
06-cleanup-preview.md
07-observability-baseline.json
08-backup-restore-proof.md
09-upgrade-migration-gates.md
10-runbooks/
No credentials, Authorization headers, private keys, real production URLs, or unreviewed support ZIPs belong in the evidence packet.
12. Rerun and drift challenge
After the second no-op reconciliation, manually alter the fixture so
capstone-lab-maven-public no longer includes the hosted
member. The reconciler should report one drift and restore only that
membership. It must not recreate unrelated repositories or delete
content.
13. Cleanup
Before cleanup, list resources and prove every target starts with
capstone-lab-. Delete only empty/disposable capstone
resources through supported Nexus mechanisms and remove temporary
client config. Never delete blob files/database rows directly. Keep
the redacted evidence packet if you want to use it in Lessons 3–5.
Knowledge check
What proves reconciliation is idempotent?
After desired and actual state match, rerunning the same automation produces no unintended mutation; the fixture second run is all no-op.
Why must repository credentials be absent from desired-state files?
Desired state is reviewable configuration; credentials belong in a secret store/runtime injection path and should not be committed or logged.
What is the correct publication target for internal Maven releases?
The internal Maven hosted repository, not the public group or proxy.
What proves Community-path promotion preserved identity?
Candidate and promoted release bytes have the same cryptographic hash and were not rebuilt.
Why is a webhook event insufficient to approve a release?
It is only an event signal; approval requires validated source/authenticity, idempotence, immutable artifact identity, and required test/policy evidence.
Summary and next step
The target architecture is now reproducible as desired state, with idempotent reconciliation, scoped identities, controlled clients, synthetic multi-format publication, build-once identity, exact-byte promotion proof, and an operator evidence packet.
Lesson 3 validates whether this implementation is actually secure, governable, recoverable, and supportable under production constraints rather than merely functional.
Official references and version notes
- Sonatype: Nexus Repository documentation — current self-hosted product entry point and supported-format documentation.
- Sonatype: 2026 self-hosted release notes — release-specific changes and known-issue/upgrade guidance.
- Sonatype: Self-Hosted Feature Matrix — Community versus Pro capability boundary.
- Sonatype: System Requirements — Java 21, H2 workload limits, PostgreSQL guidance, sizing, file handles, and deployment constraints.
- Sonatype: Repository Types — hosted, proxy, and group responsibilities.
- Sonatype: Content Selectors — fine-grained content authorization concepts.
- Sonatype: Cleanup Policies — retention criteria, preview/evaluation, deletion semantics, and blob reclamation boundary.
- Sonatype: REST and Integration API — documented REST/OpenAPI automation boundary.
- Sonatype: Webhooks — repository/global events and delivery semantics.
- Sonatype: Staging — current Pro-only staged component movement/promotion capability.
- Sonatype: Repository Firewall — separately licensed IQ-powered policy integration and supported Nexus editions.
- Sonatype: Prepare a Backup — coordinated database/blob backups and node-ID preservation.
- Sonatype: Database migration — current migration tooling and compatibility gates.
- Sonatype: Instance Migrator — current source/target migration workflow and constraints.
- Sonatype: Upgrade Paths — version thresholds and mandatory crossed-version procedures.
- Sonatype: Rolling Upgrades in HA — Pro/HA rolling-upgrade behavior and mixed-mode constraints.
- Sonatype: HA system requirements — Pro-only active-node topology, shared PostgreSQL/blob state, and failure-domain requirements.
- Sonatype: Logging — application/request/outbound/audit/JVM evidence.
- Sonatype: Prometheus — current metrics endpoint and privilege boundary.
- Sonatype: Support Features — support ZIP generation and evidence handling.
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.