Artifacts & post actions

Archive results; act on success or failure.

Beginner10 min · lesson 10 of 16

A Jenkins build finishes on a Tuesday afternoon. It compiled your code, the tests all went green, and out the other end came one file: target/app.jar, the Java archive (a single zipped bundle of compiled Java code) that actually runs in production. You close the laptop. Two weeks later something goes wrong in production, and in the incident channel someone asks the only question that matters: what exactly did build 412 ship? If that jar only ever existed on a build agent (the throwaway machine that ran the build), and that machine has since been wiped and handed to another job, you have nothing to show anyone. You can rebuild from the commit and hope the bytes come out identical. Hoping is not the same as knowing. Archiving closes that gap. It staples the exact file a build produced onto that build's permanent record, so 'what did 412 ship' becomes a download link that still works a year later.

Archiving: hold on to the exact bytes a build made

A good bakery keeps one loaf from every batch on a shelf out back, labelled with the batch number. When a customer complains next week, there is something real to taste instead of a guess. Archiving does that for builds. An artifact is any file your build creates that is worth keeping: a compiled binary, a jar, a coverage report, a packaged image manifest. archiveArtifacts is the built-in Pipeline step that does the keeping. It copies the files you name out of the workspace (the scratch directory an agent uses for one build and then reuses for the next) and stores them on the Jenkins controller, under JENKINS_HOME/jobs/<job>/builds/<n>/archive/. The controller is the central Jenkins server that schedules work and holds the history; agents are the disposable machines that do the actual building. Because the copy sits on the controller, it outlives the agent that made it. Add fingerprint: true and Jenkins also records an MD5 hash (Message Digest 5, a short string calculated from a file's contents, so two different files effectively never produce the same one) alongside which build created that file. That is how you prove later that the app.jar running in production is the one build 412 made and not a look-alike.

Jenkinsfile
pipeline {
agent { label 'linux' }
stages {
stage('Build') {
steps {
sh 'mvn -B -DskipTests clean package' // produces target/app.jar
}
}
stage('Test') {
steps { sh 'mvn -B test' }
post {
always { junit 'target/surefire-reports/*.xml' } // publish JUnit report
}
}
}
post {
success {
// keep the jar with THIS build; record a hash so we can trace it later
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
}
Console output (build #412)
[Pipeline] { (Build)
[Pipeline] sh
+ mvn -B -DskipTests clean package
[INFO] Building payments-api 1.4.0
[INFO] Building jar: /home/jenkins/workspace/payments-api/target/app.jar
[INFO] BUILD SUCCESS
[Pipeline] }
...
[Pipeline] { (Declarative: Post Actions)
[Pipeline] archiveArtifacts
Archiving artifacts
Recording fingerprints
[Pipeline] }
Finished: SUCCESS

One thing catches everyone the first time. If the glob (a wildcard file pattern, like target/*.jar) matches nothing at all, archiveArtifacts does not shrug and carry on. It fails the build with 'No artifacts found that match the file pattern.' That is deliberate. A Package stage that archived zero jars has almost certainly broken quietly, and quiet breakage is the expensive kind. If an empty result really is acceptable, say for an optional report, pass allowEmptyArchive: true. To skip archiving on a build that already failed, add onlyIfSuccessful: true. Patterns are read relative to the workspace root and follow Ant-style rules (the wildcard syntax Jenkins inherited from the older Ant build tool): target/*.jar matches one directory deep, target/**/*.jar matches any depth.

So where does the file actually turn up? Open the build in the Jenkins web interface and there is now a 'Build Artifacts' section linking target/app.jar. Every archived file also gets a stable web address you can point a script at: JENKINS_URL/job/payments-api/lastSuccessfulBuild/artifact/target/app.jar. That link always resolves to the newest green build's jar, which is why deploy scripts and downstream jobs fetch from it instead of rebuilding everything themselves. The fingerprint lives somewhere else, on the job's 'Fingerprints' page, where Jenkins lists every build that has touched that exact sequence of bytes.

post: the tidy-up that reads the result first

You do different things at the end of a road trip depending on how it went. Arrived fine, you unpack. Broke down, you call the garage and photograph the damage for the insurer. Either way, you lock the car. A post block works like that. It runs after all the steps in a pipeline (or in a single stage) have finished, and the sub-blocks inside it fire according to how things turned out. success runs only when the whole build passed. failure runs only when it errored. unstable runs when tests failed but the build itself did not crash. changed runs when the result differs from the previous run. always runs no matter what. cleanup runs dead last, after every other condition, even if one of them threw an error on its way out. This is the home for outcome-driven housekeeping: archive on success, grab logs and ping a chat channel on failure, publish the test report always, wipe the workspace at the very end. cleanWs() is that wipe step, provided by the Workspace Cleanup plugin. It deletes the agent's workspace so the next build starts from a clean checkout instead of inheriting stale files from the last one.

Jenkinsfile (post block)
post {
always {
junit 'target/surefire-reports/*.xml'
}
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
failure {
// grab logs so a broken build is debuggable; allowEmptyArchive keeps the
// post step from failing if no log files were written
archiveArtifacts artifacts: 'logs/**', allowEmptyArchive: true
slackSend channel: '#ci-payments',
message: "FAILED ${env.JOB_NAME} #${env.BUILD_NUMBER} (${env.BUILD_URL})"
}
cleanup {
cleanWs() // always last: reclaim the agent's disk
}
}
Console output (failed run)
[Pipeline] { (Declarative: Post Actions)
[Pipeline] junit
Recording test results
[Pipeline] archiveArtifacts
Archiving artifacts
[Pipeline] slackSend
Slack Send Pipeline step running, values are - channel: #ci-payments
[Pipeline] cleanWs
[WS-CLEANUP] Deleting project workspace...
[WS-CLEANUP] done
[Pipeline] }
Finished: FAILURE

Read that failed run again and notice what is missing. There is no 'Recording fingerprints' line anywhere. The success block was skipped because the build failed, so the jar was never archived. Only the logs made it out. That is exactly the behaviour you want when green builds are the only ones you ever ship. It is a nasty surprise when you wanted the artifact whatever happened, which is the first self-check below.

Archived artifacts versus an artifact repository

A kitchen drawer and a warehouse are both places to keep things, and swapping their jobs goes badly. Archiving is the drawer. It exists for traceability and convenience, not for distribution. Everything sits on the controller's own disk inside JENKINS_HOME, scoped to one job's build history, and it disappears whenever Jenkins' build-retention settings say it should. Ideal for 'let me grab the jar build 412 made'. Hopeless for 'serve this library to fifty teams for the next three years'. That second job belongs to an artifact repository: a dedicated server such as JFrog Artifactory or Sonatype Nexus for versioned packages, or a container registry for images. It handles semantic versioning (the major.minor.patch numbering scheme), immutability (a published version can never be quietly swapped for different bytes), access control and retention on its own terms, independently of any CI (continuous integration) build. The pattern teams settle on: build the jar or image, push it to the repository tagged with the version and the commit SHA (the unique identifier Git gives every commit), and archive only the small, build-specific things in Jenkins, like the test report or a short manifest noting which digest you pushed. Let the repository distribute. Let Jenkins remember.

build -> archive -> downstream
1Build stage
mvn package runs on an agent and drops target/app.jar in the workspace
2archiveArtifacts (fingerprint)
copies the jar to the controller and records its MD5 hash
3Build record
jar attached under builds/<n>/archive/, plus a stable lastSuccessfulBuild URL
4Downstream / deploy
pulls that exact jar by permalink or copyArtifacts, no rebuild
Fingerprinting ties the deploy job back to the exact build that produced the jar, so 'which build shipped this?' always has an answer.
retrieve + trace (shell)
# Download the exact jar the newest green build produced (stable permalink)
curl -fsSL -u "$JENKINS_USER:$JENKINS_TOKEN" -O \
"https://jenkins.example.com/job/payments-api/lastSuccessfulBuild/artifact/target/app.jar"
# Trace that file across the whole controller by its fingerprint hash
curl -fsSL -u "$JENKINS_USER:$JENKINS_TOKEN" \
"https://jenkins.example.com/fingerprint/9f86d081884c7d659a2feaa0c55ad015/api/json" \
| jq '{fileName, original: .original.name, builds: [.usage[].name]}'
CLI response
{
"fileName": "app.jar",
"original": "payments-api",
"builds": [
"payments-api",
"payments-api-deploy"
]
}
Whatever you archive lands on the controller, so keep it small and secret-free
Archived files are written to JENKINS_HOME on the controller, and anyone who can view the build can download them. Archive node_modules or a whole build tree and you will fill the controller's disk within days, usually taking the entire Jenkins instance down with it. The worse failure is the quieter one. Archive a .env file (a plain-text list of environment variables, often holding tokens), a kubeconfig (the file holding credentials for a Kubernetes cluster), or a target/ directory that happened to sweep up a credentials file, and you have handed a live secret to every user with read access. Permanently, attached to the build and copied to every fingerprint. Archive the deliverable and small reports, nothing more. Set build retention so old artifacts expire. Better still, never let secrets reach the workspace in the first place.

The one input you deliberately keep out of every archive is credentials. That is where the next lesson goes, Credentials binding done right: how to hand a token or a password to a build so Jenkins masks it in the log, and it never lands in the workspace or inside a stored artifact.

Quick check
01Your Package stage produces target/app.jar, and then a later Test stage fails. Your archiving lives in post { success { archiveArtifacts 'target/*.jar' } } at the pipeline level. After that failed run, is the jar attached to the build?
Incorrect — No. archiveArtifacts only runs when the post condition wrapping it runs, and success does not run on a failed build, so nothing is archived.
Correct — post conditions gate their steps by outcome. success is skipped on failure, so move archiving to always or unsuccessful (or the stage-level post) when you need the artifact even after a later stage breaks.
Incorrect — No. There is no implicit archiving anywhere in Jenkins. Nothing is captured unless an archiveArtifacts step actually runs.
Incorrect — No. Fingerprinting records a hash of a file that has already been archived. It has no say in when or whether archiving happens.
02You add fingerprint: true to archiveArtifacts. What does that buy you on top of storing the file with the build?
Incorrect — No. Fingerprinting records a hash. It compresses nothing.
Correct — The fingerprint ties a file's byte sequence back to the build that created it, so 'which build shipped this?' stays answerable.
Incorrect — No. A fingerprint is a hash for tracing, not encryption and not access control.
Incorrect — No. Archiving keeps the file on the controller. Publishing to a repository is a separate step you write yourself.
03A teammate switches the step to archiveArtifacts artifacts: 'target/**', fingerprint: true so it will 'grab everything', and target/ happens to hold a generated .env file with a live token in it. What is the consequence?
Incorrect — No. Jenkins never scrubs archives. Whatever the glob matches is stored word for word.
Incorrect — No. The file is stored intact. The fingerprint is an extra hash sitting alongside it, not a replacement for it.
Correct — Archives live in JENKINS_HOME and are readable by anyone who can view the build, so archive only the deliverable and never let secrets reach the workspace.
Incorrect — No. archiveArtifacts has no secret detection at all. It will archive the .env happily and leak it.

Try this

Work through “Archived artifacts versus an artifact repository” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: whatever you archive lands on the controller, so keep it small and secret-free. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related