Chapter 11Lesson 02190–250 min

APT, Yum, Raw, RubyGems, Composer, and Operating-System or Generic Artifact Formats: Guided Hands-On Workflow and Core Operations

Inspect five repository formats safely, build a disposable RubyGem native package plus a Raw immutable-file path, use Nexus REST/UI without exposing credentials, verify generated versus package-owned metadata, and keep client state isolated.

RubyGems native pathRaw hostedComponents APIChecksumsDisposable lab

Learning objectives

  • Create a disposable RubyGems hosted/proxy/group topology and a Raw hosted repository.
  • Build a harmless synthetic gem and generic immutable files without using real organization namespaces.
  • Upload through the documented Components API using a temporary permission-restricted credential file.
  • Verify native RubyGems component identity separately from Raw path identity.
  • Inspect APT, Yum, and Composer behavior safely through current repository recipes/fixtures without forcing unsupported clients onto the workstation.

Lab scope. Use a disposable self-hosted Community instance at 127.0.0.1:8081. The verified feature floor is Nexus 3.95.0; the required live repositories are RubyGems and Raw, both available in CE. RubyGems current docs recommend client 3.4.12+. Commands are POSIX/Bash; PowerShell learners should use a temporary credential file and Get-FileHash equivalents. Do not use production repositories, keys, or namespaces.

1. Preflight: inspect first and isolate every local file

export NX_URL="http://127.0.0.1:8081"
export LAB="${TMPDIR:-/tmp}/nexus-ch11"
rm -rf "$LAB"
mkdir -p "$LAB/evidence" "$LAB/gem-src/lib" "$LAB/raw-src" "$LAB/downloads" "$LAB/gem-home"
chmod 700 "$LAB"
export GEM_HOME="$LAB/gem-home"
export GEM_PATH="$GEM_HOME"

curl -fsS "$NX_URL/service/rest/v1/status" | tee "$LAB/evidence/status.txt"
curl -fsS "$NX_URL/service/rest/v1/repositories" | tee "$LAB/evidence/repositories-before.json"
ruby --version 2>&1 | tee "$LAB/evidence/ruby-version.txt" || true
gem --version 2>&1 | tee "$LAB/evidence/rubygems-version.txt" || true

If Ruby/RubyGems is absent, the learner can still complete the Nexus format comparison and Raw path; the native RubyGems commands become a fixture walkthrough. Do not install system-wide packages merely to satisfy the lesson.

2. Create four disposable repositories

In Nexus Settings → Repository → Repositories, create:

Name Recipe Purpose
academy-ch11-gem-hosted rubygems (hosted) Authoritative synthetic gem publication target; Disable redeploy if available.
academy-ch11-gem-proxy rubygems (proxy) Remote https://rubygems.org/; harmless public-resolution comparison.
academy-ch11-gem-group rubygems (group) Members hosted first, proxy second; client read aggregation.
academy-ch11-raw-hosted raw (hosted) Path-addressed generic files; strict content-type validation left enabled unless a demonstrated MIME mismatch requires a documented exception.

Create a disposable publisher identity with read/browse on the group and read/browse/add on both hosted repositories. Do not grant repository administration to the package publisher.

3. Build a temporary Basic-auth boundary without leaking a password

read -r -p 'Disposable Nexus username: ' NX_USER
read -r -s -p 'Disposable Nexus password: ' NX_PASS; echo
export NX_AUTH_FILE="$LAB/nexus.netrc"
umask 077
printf 'machine 127.0.0.1 login %s password %s
' "$NX_USER" "$NX_PASS" > "$NX_AUTH_FILE"
unset NX_PASS

curl --netrc-file keeps the password out of the command arguments. The file is inside the disposable workspace with restrictive permissions and is deleted during cleanup.

4. Build a synthetic RubyGem locally

cd "$LAB/gem-src"
cat > learner_ch11_native.gemspec <<'EOF'
Gem::Specification.new do |s|
  s.name        = "learner_ch11_native"
  s.version     = "1.0.0"
  s.summary     = "Disposable Nexus format lab"
  s.authors     = ["DevOps Academy Lab"]
  s.files       = ["lib/learner_ch11_native.rb"]
  s.require_paths = ["lib"]
  s.license     = "MIT"
end
EOF
cat > lib/learner_ch11_native.rb <<'EOF'
module LearnerCh11Native
  IDENTITY = "learner_ch11_native/1.0.0"
end
EOF

gem build learner_ch11_native.gemspec   | tee "$LAB/evidence/gem-build.txt"
python - <<'PYI' | tee "$LAB/evidence/gem-sha256.txt"
from pathlib import Path
import hashlib, os
p = Path(os.environ['LAB'], 'gem-src', 'learner_ch11_native-1.0.0.gem')
print(hashlib.sha256(p.read_bytes()).hexdigest(), p.name)
PYI

The .gem contains the package metadata the RubyGems ecosystem expects. Nexus will identify the component by gem name and version, not by an arbitrary Raw path.

5. Upload through the format-aware Components API

curl --fail --silent --show-error   --netrc-file "$NX_AUTH_FILE"   -X POST "$NX_URL/service/rest/v1/components?repository=academy-ch11-gem-hosted"   -F "rubygems.asset=@$LAB/gem-src/learner_ch11_native-1.0.0.gem"   -o /dev/null -w 'HTTP %{http_code}
'   | tee "$LAB/evidence/gem-upload.txt"

curl -fsS --netrc-file "$NX_AUTH_FILE"   "$NX_URL/service/rest/v1/components?repository=academy-ch11-gem-hosted"   | tee "$LAB/evidence/gem-components.json"

A successful upload returns HTTP 204. The component list should show learner_ch11_native version 1.0.0. Nexus also updates the repository information required for RubyGems clients.

6. Consume the native package without changing the user's normal Gem home

If anonymous read is enabled on the disposable instance, the cleanest native protocol demonstration is:

export GEM_GROUP="$NX_URL/repository/academy-ch11-gem-group/"
gem install learner_ch11_native   --version 1.0.0   --source "$GEM_GROUP"   --clear-sources   --no-document   2>&1 | tee "$LAB/evidence/gem-install.txt"
ruby -e 'require "learner_ch11_native"; puts LearnerCh11Native::IDENTITY'   | tee "$LAB/evidence/gem-runtime.txt"

If anonymous read is disabled, do not put credentials into the source URL or command history. Use the authenticated download path below, then let the native Gem client validate/install the local package:

curl -fsS --netrc-file "$NX_AUTH_FILE"   "$NX_URL/repository/academy-ch11-gem-group/gems/learner_ch11_native-1.0.0.gem"   -o "$LAB/downloads/learner_ch11_native-1.0.0.gem"

{ gem specification "$LAB/downloads/learner_ch11_native-1.0.0.gem" name; gem specification "$LAB/downloads/learner_ch11_native-1.0.0.gem" version; }   | tee "$LAB/evidence/gem-specification.txt"
gem install --local "$LAB/downloads/learner_ch11_native-1.0.0.gem" --no-document   | tee "$LAB/evidence/gem-local-install.txt"

This fallback proves the native package semantics without teaching credential-in-URL behavior. It does not claim that gem install --local exercised Nexus resolution; the preceding authenticated download is the repository-boundary evidence.

7. Create generic immutable files and publish them to Raw

cat > "$LAB/raw-src/learner-tool-1.0.0.txt" <<'EOF'
name=learner-tool
version=1.0.0
purpose=generic path-addressed training artifact
EOF
python - <<'PYI'
from pathlib import Path
import json, os
p=Path(os.environ['LAB'],'raw-src','learner-tool-1.0.0.json')
p.write_text(json.dumps({'name':'learner-tool','version':'1.0.0','kind':'generic'}, indent=2)+'
')
PYI
python - <<'PYI' | tee "$LAB/evidence/raw-sha256-before.txt"
from pathlib import Path
import hashlib, os
for p in sorted(Path(os.environ['LAB'],'raw-src').iterdir()):
    print(hashlib.sha256(p.read_bytes()).hexdigest(), p.name)
PYI
curl --fail --silent --show-error   --netrc-file "$NX_AUTH_FILE"   -X POST "$NX_URL/service/rest/v1/components?repository=academy-ch11-raw-hosted"   -F "raw.directory=releases/learner-tool/1.0.0"   -F "raw.asset1=@$LAB/raw-src/learner-tool-1.0.0.txt"   -F "raw.asset1.filename=learner-tool-1.0.0.txt"   -F "raw.asset2=@$LAB/raw-src/learner-tool-1.0.0.json"   -F "raw.asset2.filename=learner-tool-1.0.0.json"   -o /dev/null -w 'HTTP %{http_code}
'   | tee "$LAB/evidence/raw-upload.txt"

Raw keeps each file as a separate component. The shared directory is a naming convention created by the operator, not a native package identity understood by Nexus.

8. Retrieve by exact path and prove byte identity

for f in learner-tool-1.0.0.txt learner-tool-1.0.0.json; do
  curl -fsS --netrc-file "$NX_AUTH_FILE"     "$NX_URL/repository/academy-ch11-raw-hosted/releases/learner-tool/1.0.0/$f"     -o "$LAB/downloads/$f"
done
python - <<'PYI' | tee "$LAB/evidence/raw-sha256-after.txt"
from pathlib import Path
import hashlib, os
base=Path(os.environ['LAB'])
for name in ['learner-tool-1.0.0.txt','learner-tool-1.0.0.json']:
    a=hashlib.sha256((base/'raw-src'/name).read_bytes()).hexdigest()
    b=hashlib.sha256((base/'downloads'/name).read_bytes()).hexdigest()
    print(name, a, b, 'MATCH' if a==b else 'MISMATCH')
PYI

9. Inspect the other native formats without forcing a workstation conversion

Format Read-only inspection exercise What to verify
APT Create or inspect an apt (proxy) recipe and view Distribution/Flat/Signing fields. There is no apt (group) recipe; hosted requires signing configuration; metadata and package signatures are distinct.
Yum Create/inspect a disposable yum (hosted) or use a JSON fixture of its settings. Repodata Depth exists for hosted; group is supported; metadata signing is proxy/group-specific.
Composer Inspect recipe list only unless exact build/edition is verified. Proxy is v2-only; hosted/group require 3.95.0+; disable Packagist for Nexus-only client routing.
RubyGems Already exercised live. Native component name/version and group aggregation.
Raw Already exercised live. Path and bytes only; no dependency/index semantics.

10. Challenge: select the repository before you create it

For each artifact, choose native or Raw and explain the client-visible evidence that proves your choice:

  1. An Ubuntu agent needs dependency-resolved .deb packages.
  2. A firmware updater already knows an exact HTTPS path for a signed binary bundle.
  3. A Ruby service uses Bundler and needs internal plus public gems through one endpoint.
  4. A PHP service needs a private Composer package and must not reach Packagist directly.

The correct answer is not “native is always better.” The correct answer follows the consumer protocol and required metadata.

11. Cleanup and evidence

Keep the evidence directory until review. Then delete only academy-ch11-gem-group, academy-ch11-gem-proxy, academy-ch11-gem-hosted, and academy-ch11-raw-hosted through Nexus UI/API. Do not remove blob files or database rows manually.

rm -f "$NX_AUTH_FILE"
unset NX_USER NX_AUTH_FILE GEM_HOME GEM_PATH
# After evidence review only:
# rm -rf "$LAB"

Knowledge check

Why did the gem upload use rubygems.asset rather than raw.asset1?

What does the Raw directory releases/learner-tool/1.0.0 mean to Nexus?

Why is the authenticated RubyGems fallback split into curl plus gem install --local?

What should be deleted during cleanup?

Why is Composer not required in this live lab?

12. Summary and next step

The live workflow demonstrated the distinction the chapter is built around: RubyGems has native name/version/index semantics, while Raw exposes operator-defined paths and unchanged bytes. APT, Yum, and Composer require their own metadata and trust rules rather than being variations of the same upload pattern.

Lesson 3 uses those observations to make architecture choices deliberately.

Official references and version notes

Version-sensitive statements were rechecked on 2026-08-26. The chapter uses Nexus Repository 3.95.0 as the verified feature floor because current Sonatype release notes explicitly list it as released on 2026-08-05 and document Composer hosted/group support there. Some adjacent Sonatype download/version-status pages still lag at 3.94.1, so record the exact version/edition on your lab instance before applying version-specific steps. If you are continuing with a later 3.95.x instance from a prior chapter, it satisfies this chapter's 3.95.0 feature floor. Mandatory labs use Community-compatible RubyGems and Raw features and do not depend on the documentation-sensitive Composer entitlement boundary.

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.