Jenkins · lesson 2 of 5
Jenkins: Credentials & Agents
Use Jenkins credentials without leaking secrets. Control execution using labels/agents and archive artifacts between stages.
By SkillByExample · Updated
This tutorial extends Jenkins pipeline knowledge with two enterprise essentials:
- credentials (secure secret management)
- agents/nodes (running the right stage in the right place)
1) Jenkins credentials: avoid hardcoding
Jenkins stores secrets in its credentials system.
You reference them by credentialsId.
Common credential types:
- Secret text (token)
- Username/password
- SSH keys
- Files (e.g., kubeconfig)
Example: secret text token
withCredentials([
string(credentialsId: 'API_TOKEN', variable: 'API_TOKEN')
]) {
sh '''
echo "Calling external API"
curl -sS -H "Authorization: Bearer $API_TOKEN" https://api.example.com
'''
}
Rules of thumb:
- do not print the token
- avoid
set -x - keep logs clean
2) Agents: choose where stages run
Declarative: label-based
pipeline {
agent none
stages {
stage('Build') {
agent { label 'linux-x64' }
steps {
sh 'npm ci'
sh 'npm run build'
}
}
stage('Test') {
agent { label 'linux-x64' }
steps {
sh 'npm test'
}
}
}
}
Declarative: Docker agent
pipeline {
agent {
docker {
image 'node:20-alpine'
args '-u node'
}
}
stages {
stage('Build') {
steps {
sh 'npm ci'
sh 'npm run build'
}
}
}
}
3) Artifacts: keep outputs for later
Archive artifacts so you can download them from the build page.
stage('Build') {
steps {
sh 'npm ci'
sh 'npm run build'
archiveArtifacts artifacts: 'dist/**', fingerprint: true
}
}
4) Practical pipeline example (theory + code)
A typical CI flow:
- checkout
- install deps
- build
- test
- archive artifacts
pipeline {
agent any
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Install') {
steps { sh 'npm ci' }
}
stage('Build') {
steps { sh 'npm run build' }
}
stage('Test') {
steps { sh 'npm test' }
}
stage('Archive') {
steps { archiveArtifacts artifacts: 'dist/**', fingerprint: true }
}
}
}
Practice
These solutions ran on a jenkins/jenkins:lts container with the Pipeline plugins. Its
built-in node carries the label linux-x64, and two credentials exist: secret text
API_TOKEN (value s3cr3t-token-123) and username/password REGISTRY_LOGIN (user
deploy-bot). Each task is a Pipeline job with the Jenkinsfile pasted as its script, run
with jenkins-cli.jar build -s -v, which waits for the build and streams its log.
-
See where log masking stops, on your own Jenkins and a throwaway credential. Inside the secret-text example, replace the
curlscript with one that pipes the token throughbase64and throughrev(which reverses a string).Solution
pipeline { agent any stages { stage('Call API') { steps { withCredentials([ string(credentialsId: 'API_TOKEN', variable: 'API_TOKEN') ]) { sh ''' echo "$API_TOKEN" | base64 echo "$API_TOKEN" | rev ''' } } } } }$ java -jar jenkins-cli.jar -s http://localhost:8080/ build masking-limits -s -v Started masking-limits #1 Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/masking-limits [Pipeline] { [Pipeline] stage [Pipeline] { (Call API) [Pipeline] withCredentials Masking supported pattern matches of $API_TOKEN [Pipeline] { [Pipeline] sh + echo **** + base64 ****wo= + echo **** + rev 321-nekot-t3rc3s [Pipeline] } [Pipeline] // withCredentials [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS Completed masking-limits #1 : SUCCESSThe
+trace lines appear without anyset -x, because Jenkins already runsshscripts with tracing on. Masking is a find-and-replace on the log. It caught the exact secret and even its base64 form, but the reversed token went into the log in the clear. Masking is a convenience, not a security boundary. Anyone who can edit a Jenkinsfile can get any credential that job can bind. So limit who can change pipelines, scope credentials to the folders that need them, and rotate any secret that reaches a log. -
Misspell the credential ID as
'API_TOKN'in the secret-text example, and replace the script withsh 'echo "calling API"'. Does theshstep run with an empty token?Solution
$ java -jar jenkins-cli.jar -s http://localhost:8080/ build wrong-id -s -v Started wrong-id #1 Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/wrong-id [Pipeline] { [Pipeline] stage [Pipeline] { (Call API) [Pipeline] withCredentials [Pipeline] // withCredentials [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline ERROR: Could not find credentials entry with ID 'API_TOKN' Finished: FAILURE Completed wrong-id #1 : FAILURENo.
withCredentialsfails before entering its block, so “calling API” never prints. A wrong ID is a loud failure that names the ID. If you see an empty secret instead, suspect the variable name in your script, not the ID. -
Bind the username/password credential
REGISTRY_LOGIN. UseusernamePassword(credentialsId: 'REGISTRY_LOGIN', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')in place ofstring(...), then print the username, the password’s length, and the password.Solution
pipeline { agent any stages { stage('Login') { steps { withCredentials([ usernamePassword(credentialsId: 'REGISTRY_LOGIN', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS') ]) { sh 'echo "logging in as $REG_USER with a ${#REG_PASS}-character password"' sh 'echo "password is $REG_PASS"' } } } } }$ java -jar jenkins-cli.jar -s http://localhost:8080/ build registry-login -s -v Started registry-login #1 Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/registry-login [Pipeline] { [Pipeline] stage [Pipeline] { (Login) [Pipeline] withCredentials Masking supported pattern matches of $REG_PASS [Pipeline] { [Pipeline] sh + echo logging in as deploy-bot with a 16-character password logging in as deploy-bot with a 16-character password [Pipeline] sh + echo password is **** password is **** [Pipeline] } [Pipeline] // withCredentials [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS Completed registry-login #1 : SUCCESSOne credential becomes two variables, and only the password is masked. The “Masking … of $REG_PASS” line lists exactly which variables Jenkins hides, and the username
deploy-botprints in the clear. -
In the label-based example, replace the
npmsteps in Build withsh 'echo "building on $NODE_NAME (labels: $NODE_LABELS)"'. Then delete theagentline from the Test stage, rename the stage toReport, and make its stepsh 'echo "reporting"'.Solution
pipeline { agent none stages { stage('Build') { agent { label 'linux-x64' } steps { sh 'echo "building on $NODE_NAME (labels: $NODE_LABELS)"' } } stage('Report') { steps { sh 'echo "reporting"' } } } }$ java -jar jenkins-cli.jar -s http://localhost:8080/ build agent-none -s -v Started agent-none #1 Started from command line by anonymous [Pipeline] Start of Pipeline [Pipeline] stage [Pipeline] { (Build) [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/agent-none [Pipeline] { [Pipeline] sh + echo building on built-in (labels: linux-x64 built-in) building on built-in (labels: linux-x64 built-in) [Pipeline] } [Pipeline] // node [Pipeline] } [Pipeline] // stage [Pipeline] stage [Pipeline] { (Report) [Pipeline] sh [Pipeline] error [Pipeline] } [Pipeline] // stage [Pipeline] End of Pipeline ERROR: Attempted to execute a step that requires a node context while ‘agent none’ was specified. Be sure to specify your own ‘node { ... }’ blocks when using ‘agent none’. Finished: FAILURE Completed agent-none #1 : FAILUREWith
agent none, a stage only gets a machine if it asks for one. In Build,[Pipeline] nodeappears inside the stage. That is where the label was matched to the built-in node. Report never allocated a node, so itsshhad no workspace to run in.
Next steps
Next tutorials:
- Monitoring & Observability fundamentals
- Terraform for reproducible environments
- Ansible for idempotent configuration