Artifact Repositories: Nexus and Artifactory
Introduction¶
An artifact repository (Sonatype Nexus, JFrog Artifactory, or lighter options) is a private server that sits between your builds and the world's package registries. It caches public packages, hosts your internal builds, and becomes the single place CI pulls from and pushes to.
Why bother¶
- Availability — public registry outages (npm, Docker Hub, PyPI) don't stop your builds; the cache serves them.
- Rate limits — Docker Hub's anonymous pull limits bite CI hard. A caching proxy pulls once.
- Speed — LAN-local cache vs internet round trips, especially for large Docker layers.
- Security / supply chain — one choke point to scan, block known-bad versions, and enforce that builds only use approved sources. An SBOM and vulnerability scan at ingest.
- Internal artifacts — publish your libraries/images privately with the same tooling.
- Retention — public registries keep everything forever; you decide what to keep.
Repository types¶
Both products use the same three concepts:
- Hosted — you push to it. Your internal releases, snapshots, Docker images.
- Proxy — mirrors a remote (registry.npmjs.org, docker.io, repo1.maven.org, pypi.org). Caches on first request, serves from cache after.
- Group / virtual — a single URL that aggregates several hosted + proxy repos, resolving in order. This is what CI points at:
https://nexus.example.com/repository/npm-group/ -> npm-internal (hosted) + npm-proxy (proxy)
https://artifactory.example.com/artifactory/api/npm/npm/ -> same idea
Configure ~/.npmrc, pip.conf, settings.xml, ~/.docker/config.json, or
.gradle to use the group URL with credentials.
Docker¶
# pull-through cache
docker pull nexus.example.com:8082/library/postgres:16 # proxies docker.io
# push internal
docker tag myapp nexus.example.com:8083/myapp:1.4.2
docker push nexus.example.com:8083/myapp:1.4.2
Nexus uses a port per Docker repo (or a reverse proxy with path routing); Artifactory uses subdomains or a path prefix.
Promotion¶
A build produces a snapshot/candidate; after tests/QA it's promoted to a release repo. Implement as:
- Separate repos (
docker-candidate→docker-release) and a copy/move on promotion, or - Repository properties/labels (
build.status = released) and a cleanup policy that keeps released ones.
Never rebuild for prod — promote the exact artifact you tested (ties into SBOM & supply chain and semantic versioning).
Retention and cleanup¶
The thing that bites: repos grow to terabytes.
- Proxy repos — cleanup policy: delete cached artifacts not downloaded in N days (they'll re-cache if needed).
- Snapshot/candidate repos — keep the last N per artifact, or last N days.
- Docker — delete untagged manifests, then run the blob GC / compact. Docker layer sharing means deleting a tag doesn't free space until GC.
- Release repos — usually keep forever, but at least monitor size.
Run cleanup as scheduled tasks and watch the actual disk usage — a misconfigured "keep all" policy fills the volume silently.
Auth for CI¶
- A dedicated CI service account with push to hosted repos it owns and read on groups.
- Token/API-key auth, not a password; scoped per team where possible.
- Store the token as a CI secret (see secrets in CI).
Verification and troubleshooting¶
curl -u ci:$TOKEN https://nexus.example.com/service/rest/v1/status
curl -sI https://nexus.example.com/repository/npm-group/lodash
docker pull nexus.example.com:8082/library/alpine:3.20
df -h /nexus-data # or the Artifactory filestore
- CI can pull public packages directly but not via the repo — the proxy repo's remote URL is wrong, the repo server has no internet egress (it needs it — it's the one making the upstream call), or auth to the group is missing.
401/403from CI — token expired/wrong, or the account lacks a role on that repo. Check the audit log.- Docker push "manifest blob unknown" — pushing to a group/proxy repo instead of a hosted one, or the repo doesn't allow the tag (release repo, immutable tags on).
- Disk full — cleanup policies not running or set to "keep all"; Docker blob GC never run. Nexus: "Admin - System - Tasks"; Artifactory: garbage collection
- "Cleanup Unreferenced" + storage summary.
- Slow first pull, fast after — expected (proxy cache miss then hit). If every pull is slow, the cache isn't persisting (blob store misconfig, or cleanup too aggressive).
- Public registry blocked a version you cached — you still have it (that's a feature and a risk); align with a policy to remove known-bad versions.
- Maven "could not resolve" for an internal snapshot — snapshot vs release
repo mismatch in
settings.xml, or<updatePolicy>caching an oldmaven-metadata.xml.
Related tools and reading¶
- On-site: JSON formatter.
- Related posts: SBOM and supply-chain security, GitHub Actions cache strategies.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in DevOps & CI/CD
OpenTelemetry for Distributed Tracing
A trace follows one request across services. OpenTelemetry is the vendor-neutral way to produce them — here's the model, the Collector, and sampling that won't bankrupt you.
October 24, 2026pre-commit Hooks That Actually Help
pre-commit runs checks before a commit lands, catching formatting, secrets, and syntax errors locally. Here's a config that helps without being annoying.
October 24, 2026Chaos Engineering Basics
Chaos engineering is controlled experiments that inject failure to verify your system tolerates it. Here's the method, safe blast radius, starter experiments, and game days.
October 24, 2026