Artifacts & post actions
Archive results; act on success or failure.
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.
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 laterarchiveArtifacts artifacts: 'target/*.jar', fingerprint: true}}}
[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] archiveArtifactsArchiving artifactsRecording 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.
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 writtenarchiveArtifacts artifacts: 'logs/**', allowEmptyArchive: trueslackSend channel: '#ci-payments',message: "FAILED ${env.JOB_NAME} #${env.BUILD_NUMBER} (${env.BUILD_URL})"}cleanup {cleanWs() // always last: reclaim the agent's disk}}
[Pipeline] { (Declarative: Post Actions)[Pipeline] junitRecording test results[Pipeline] archiveArtifactsArchiving artifacts[Pipeline] slackSendSlack 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.
# 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 hashcurl -fsSL -u "$JENKINS_USER:$JENKINS_TOKEN" \"https://jenkins.example.com/fingerprint/9f86d081884c7d659a2feaa0c55ad015/api/json" \| jq '{fileName, original: .original.name, builds: [.usage[].name]}'
{"fileName": "app.jar","original": "payments-api","builds": ["payments-api","payments-api-deploy"]}
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.
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.