Jenkins · lesson 1 of 5
Also part of: DevOps
Jenkins CI: Pipeline Basics
Learn what Jenkins is and how to use declarative pipelines: stages, agents, steps, and basic CI patterns.
By SkillByExample · Updated
Jenkins is a CI server that automates build/test/release workflows. Even if you use it less today, understanding Jenkins pipeline concepts helps you reason about CI systems generally.
Theory first: pipelines as executable delivery design
A CI pipeline is an executable model of your delivery process. Each stage encodes a quality boundary (build, test, security, deploy), and failures represent policy violations—not just broken scripts.
This framing keeps Jenkins practical: you are designing trustworthy flow control for change, where feedback speed and reproducibility matter as much as successful builds.
Learning outcomes
By the end you can:
- understand Jenkins pipeline stages
- run commands in steps
- use agents/nodes to control execution environment
1) Pipeline overview
A Jenkins pipeline is a script describing what to run and when. Today, declarative pipelines are most common.
2) Minimal declarative pipeline
Jenkinsfile:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Test') {
steps {
sh 'npm test'
}
}
}
}
3) Common stage patterns
Lint + test
stage('Lint') {
steps { sh 'npm run lint' }
}
stage('Test') {
steps { sh 'npm test' }
}
Build + archive
stage('Build') {
steps {
sh 'npm run build'
archiveArtifacts artifacts: 'dist/**', fingerprint: true
}
}
4) Choosing an execution environment
Declarative pipeline:
agent {
docker {
image 'node:20-alpine'
args '-u node'
}
}
Or run on specific labeled agents:
agent { label 'linux-x64' }
5) Secrets and credentials (conceptual)
Jenkins uses a credentials store so you don’t hardcode secrets in the Jenkinsfile.
Common usage pattern is withCredentials.
Example (concept):
withCredentials([string(credentialsId: 'API_TOKEN', variable: 'API_TOKEN')]) {
sh 'curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com'
}
Practice
These solutions ran on a jenkins/jenkins:lts container with the Pipeline plugins
installed and a secret-text credential with the ID API_TOKEN. Each task is a
Pipeline job (New Item → Pipeline) with the Jenkinsfile pasted into its Script box.
Each job was started with jenkins-cli.jar, whose build -s -v waits for the build and
streams its console log. Lines starting [Pipeline] are Jenkins tracing each step it runs.
-
Paste the “Minimal declarative pipeline” exactly as written into a new Pipeline job called
minimaland run it. Which stage fails, and why?Solution
$ java -jar jenkins-cli.jar -s http://localhost:8080/ build minimal -s -v | grep -v '^\[Pipeline\]' Started minimal #1 Started from command line by anonymous Running on Jenkins in /var/jenkins_home/workspace/minimal Stage "Install" skipped due to earlier failure(s) Stage "Test" skipped due to earlier failure(s) ERROR: ‘checkout scm’ is only available when using “Multibranch Pipeline” or “Pipeline script from SCM” Finished: FAILURE Completed minimal #1 : FAILUREcheckout scmmeans “check out the repository this Jenkinsfile came from”. A pasted script didn’t come from a repository, so there is nothing to check out. In a real project theJenkinsfilelives in the repo and the job is “Pipeline script from SCM”. For the remaining tasks, leave theCheckoutstage out. -
Build a two-stage pipeline from the “Lint + test” pattern, using
shcommands that need no project. Make Lint fail withsh 'echo "lint: 1 problem found"; exit 1'and Test pass withsh 'echo "tests passed"'. Does Test run?Solution
pipeline { agent any stages { stage('Lint') { steps { sh 'echo "lint: 1 problem found"; exit 1' } } stage('Test') { steps { sh 'echo "tests passed"' } } } }$ java -jar jenkins-cli.jar -s http://localhost:8080/ build lint-fails -s -v Started lint-fails #1 Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/lint-fails [Pipeline] { [Pipeline] stage [Pipeline] { (Lint) [Pipeline] sh + echo lint: 1 problem found lint: 1 problem found + exit 1 [Pipeline] } [Pipeline] // stage [Pipeline] stage [Pipeline] { (Test) Stage "Test" skipped due to earlier failure(s) [Pipeline] getContext [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline ERROR: script returned exit code 1 Finished: FAILURE Completed lint-fails #1 : FAILUREA non-zero exit from
shfails the stage, and every later stage is skipped. The order of stages is a policy: a lint failure here means the tests never get a turn. “tests passed” never prints. -
Use the “Build + archive” pattern with
sh 'mkdir -p dist && echo "app v1" > dist/app.txt && echo "body{}" > dist/app.css'in place ofnpm run build. Check what got archived. Then change themkdirand redirect to write intobuild/instead ofdist/, keep thearchiveArtifactsline unchanged, and run it again.Solution
pipeline { agent any stages { stage('Build') { steps { sh 'mkdir -p dist && echo "app v1" > dist/app.txt && echo "body{}" > dist/app.css' archiveArtifacts artifacts: 'dist/**', fingerprint: true } } } }$ java -jar jenkins-cli.jar -s http://localhost:8080/ build build-archive -s -v Started build-archive #1 Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/build-archive [Pipeline] { [Pipeline] stage [Pipeline] { (Build) [Pipeline] sh + mkdir -p dist + echo app v1 + echo body{} [Pipeline] archiveArtifacts Archiving artifacts Recording fingerprints [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS Completed build-archive #1 : SUCCESS $ curl -sg "http://localhost:8080/job/build-archive/1/api/json?tree=artifacts[relativePath]" {"_class":"org.jenkinsci.plugins.workflow.job.WorkflowRun","artifacts":[{"relativePath":"dist/app.css"},{"relativePath":"dist/app.txt"}]}With the build writing to
build/instead (sh 'mkdir -p build && echo "app v1" > build/app.txt'):$ java -jar jenkins-cli.jar -s http://localhost:8080/ build archive-miss -s -v Started archive-miss #1 Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/archive-miss [Pipeline] { [Pipeline] stage [Pipeline] { (Build) [Pipeline] sh + mkdir -p build + echo app v1 [Pipeline] archiveArtifacts Archiving artifacts ‘dist/**’ doesn’t match anything, but ‘**’ does. Perhaps that’s what you mean? [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline ERROR: No artifacts found that match the file pattern "dist/**". Configuration error? Finished: FAILURE Completed archive-miss #1 : FAILUREArchiving nothing is treated as a broken build, not an empty success. That catches a build script that quietly stopped producing output.
-
Change the pipeline’s agent to
agent { label 'windows' }on a Jenkins that has no node with that label. Start it without-s, read the console after a few seconds, and then stop the build.Solution
$ curl -s http://localhost:8080/job/label-windows/1/consoleText Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] node Still waiting to schedule task ‘Jenkins’ doesn’t have label ‘windows’ $ java -jar jenkins-cli.jar -s http://localhost:8080/ stop-builds label-windows Build '#1' stopped for job 'label-windows' $ curl -s http://localhost:8080/job/label-windows/1/consoleText | tail -2 org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: 0b047bd2-4362-4657-9a09-2bda2a04e076 Finished: ABORTEDAn unmatched label is not an error. The build simply waits in the queue for a node that may never come. A typo in a label shows up as a build stuck on “Still waiting to schedule task”, not as a red failure.
-
Put the
withCredentialsexample in a stage. Replace thecurlwithsh 'echo "single quotes: token is $API_TOKEN"'. Add a second step that uses Groovy interpolation:sh "echo \"double quotes: token is ${API_TOKEN}\"".Solution
pipeline { agent any stages { stage('Call API') { steps { withCredentials([string(credentialsId: 'API_TOKEN', variable: 'API_TOKEN')]) { sh 'echo "single quotes: token is $API_TOKEN"' sh "echo \"double quotes: token is ${API_TOKEN}\"" } } } } }$ java -jar jenkins-cli.jar -s http://localhost:8080/ build call-api -s -v Started call-api #1 Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/call-api [Pipeline] { [Pipeline] stage [Pipeline] { (Call API) [Pipeline] withCredentials Masking supported pattern matches of $API_TOKEN [Pipeline] { [Pipeline] sh + echo single quotes: token is **** single quotes: token is **** [Pipeline] sh Warning: A secret was passed to "sh" using Groovy String interpolation, which is insecure. Affected argument(s) used the following variable(s): [API_TOKEN] See https://jenkins.io/redirect/groovy-string-interpolation for details. + echo double quotes: token is **** double quotes: token is **** [Pipeline] } [Pipeline] // withCredentials [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS Completed call-api #1 : SUCCESSBoth lines are masked in the log, but only the first is safe. With single quotes the shell reads
$API_TOKENfrom its environment. With double quotes Groovy pastes the secret into the command text beforeshruns, and Jenkins warns about exactly that.
Next steps
Next tutorials:
- Jenkins credentials + artifacts
- Multi-stage releases
- Pair with monitoring: alerts on pipeline failures