Artifact & Dependency Management
Every CI/CD pipeline produces something: a compiled .jar file, a Docker image, an npm package, or a zipped bundle of your website. These outputs are called artifacts (the finished, ready-to-ship results of a build). At the same time, your build consumes dependencies (third-party libraries your code needs, like Spring Boot or React). Managing both — storing them, versioning them, and serving them reliably — is what artifact management is about. Get this right and your deployments become fast, repeatable, and trustworthy.
Artifacts vs dependencies
These two words get mixed up, so let’s pin them down clearly.
A dependency is code written by someone else that your project pulls in to build. When you run npm install or mvn package, your tool downloads dependencies from a registry (a server that hosts packages). Examples: express, lodash, junit.
An artifact is the thing your build produces. After compiling and packaging, you get a single deployable unit. Examples: myapp-1.4.2.jar, a Docker image myapp:1.4.2, or a dist.tar.gz.
| Term | Who makes it | Example | Where it lives |
|---|---|---|---|
| Dependency | A third party | [email protected] | A public/private registry |
| Artifact | Your pipeline | myapp-1.4.2.jar | An artifact repository |
The same tool — an artifact repository (a storage server for build outputs and packages) — usually handles both jobs. It caches the dependencies you download and stores the artifacts you produce.
Why store and version artifacts
It is tempting to just rebuild your app on every server. Don’t. Here is why you store artifacts instead.
- Reproducibility. If you rebuild from source on each machine, tiny differences (a newer library, a different OS patch) can change the output. A stored artifact is byte-for-byte identical everywhere.
- Speed. Downloading a finished 40 MB jar is far faster than recompiling for five minutes.
- Traceability. Every version is kept, so you can answer “what exactly is running in production?” and roll back instantly.
- Offline safety. If a public registry goes down (it happens), your cached dependencies keep your builds working.
Versioning means giving each artifact a unique, ordered name like 1.4.2. The common scheme is semantic versioning (MAJOR.MINOR.PATCH): bump PATCH for bug fixes, MINOR for new features, MAJOR for breaking changes. Never overwrite a released version — once 1.4.2 is published, it stays frozen forever.
Never reuse a version number for a changed artifact. If
1.4.2means two different things on two days, every rollback and audit becomes a guessing game. Publish a new version instead.
Build once, promote everywhere
This is the single most important principle on this page.
Build once, promote everywhere means you build your artifact a single time, then move that exact same file through your environments — staging, then production — without rebuilding. You “promote” it by deploying the same artifact to the next stage once it passes tests.
The opposite (rebuilding for each environment) is dangerous: the artifact you tested in staging is not the one you ship to production, so your tests prove nothing about what users actually run.
[ Build once ] --> staging --> production
myapp-1.4.2.jar (same file, promoted, never rebuilt)
In practice: your CI pipeline builds myapp-1.4.2.jar, uploads it to the artifact repository, and your deploy jobs download that exact version for each environment.
The main tools
You have several solid options. Pick based on what you ship and where.
| Tool | Best for | Hosting | Notes |
|---|---|---|---|
| Sonatype Nexus | Mixed stacks (Java, npm, Docker, PyPI) | Self-hosted | Free OSS version, easy on Ubuntu |
| JFrog Artifactory | Large enterprises, many formats | Self-hosted or cloud | Powerful, paid tiers |
| GitHub Packages | Teams already on GitHub | Cloud (managed) | Tight GitHub Actions integration |
| Language registries | A single ecosystem | Cloud (managed) | npm, Maven Central, PyPI, Docker Hub |
Installing Nexus on Ubuntu
Nexus is the most common self-hosted choice. Here is a real install on Ubuntu 22.04/24.04 LTS. It needs Java, so we install that first.
sudo apt update
sudo apt install -y openjdk-17-jre-headless
sudo useradd -r -m -d /opt/nexus-home nexus
cd /opt
sudo curl -L -o nexus.tar.gz https://download.sonatype.com/nexus/3/latest-unix.tar.gz
sudo tar xzf nexus.tar.gz
sudo mv nexus-3* nexus
sudo chown -R nexus:nexus /opt/nexus /opt/sonatype-work
Now create a systemd service (systemd is Ubuntu’s tool for managing background services) so Nexus starts on boot.
# /etc/systemd/system/nexus.service
[Unit]
Description=Nexus Repository Manager
After=network.target
[Service]
Type=forking
User=nexus
ExecStart=/opt/nexus/bin/nexus start
ExecStop=/opt/nexus/bin/nexus stop
Restart=on-abort
[Install]
WantedBy=multi-user.target
Start it and open the firewall:
sudo systemctl daemon-reload
sudo systemctl enable --now nexus
sudo ufw allow 8081/tcp
sudo systemctl status nexus
Output:
● nexus.service - Nexus Repository Manager
Loaded: loaded (/etc/systemd/system/nexus.service; enabled)
Active: active (running) since Mon 2026-06-15 10:12:04 UTC; 30s ago
Main PID: 4187 (java)
Nexus is now reachable at http://your-server:8081. The first admin password is stored on disk:
sudo cat /opt/sonatype-work/nexus3/admin.password
Nexus listens on all interfaces by default. On a public server, put it behind a reverse proxy (a server that sits in front and forwards requests) with HTTPS, and never expose port 8081 to the open internet via
ufw.
Publishing and pulling artifacts
Once a repository exists, your tools talk to it. For Maven (Java), point at your repo in pom.xml:
<distributionManagement>
<repository>
<id>nexus</id>
<url>http://your-server:8081/repository/maven-releases/</url>
</repository>
</distributionManagement>
Then publish from CI:
mvn deploy -DskipTests
For npm, set the registry and authenticate (this also works with GitHub Packages):
npm config set registry http://your-server:8081/repository/npm-group/
npm publish
For Docker images, you log in and push to the repository’s hosted registry:
docker login your-server:8082
docker tag myapp:1.4.2 your-server:8082/myapp:1.4.2
docker push your-server:8082/myapp:1.4.2
Best Practices
- Use immutable versions. Once published, never change an artifact under the same version number.
- Apply build-once-promote-everywhere. Deploy the same artifact to every environment; never rebuild per stage.
- Cache dependencies through a proxy repository. It speeds up builds and protects you from upstream outages.
- Separate snapshot and release repos. Keep work-in-progress (
-SNAPSHOT) builds out of your stable release repo. - Set retention/cleanup policies so old snapshots don’t fill the disk — Nexus and Artifactory both support automatic cleanup.
- Store secrets safely. Publish credentials belong in your CI secret store, never in
pom.xmlor committed config. - Pin dependency versions in lockfiles (
package-lock.json,pom.xml) so builds are reproducible.