DS DevShelfHub Projects · AI tools
Cheatsheets / Jenkins
Cheatsheet · Dev tooling

Jenkins: Declarative Pipelines, Stages, Agents and Secrets Reference Guide

By DevShelfHub

Jenkinsfile (declarative + scripted), stages, parallel, matrix, agents, pipeline DSL, shared libraries, credentials, triggers, JCasC, essential plugins.

116 items ◷ 10 min Jenkinsfile Pipelines Plugins

Start hereQuick start · 6 you’ll reach for daily

Config fileJenkinsfile (repo root)
Wrapperpipeline { agent any; stages {...} }
Stagestage('Build') { steps { sh '...' } }
ReplayRun → Replay — edit pipeline live
Linterjenkins-cli declarative-linter < Jenkinsfile
SecretswithCredentials([...]) {...}

Target versions · paceVersions

Targets: jenkins LTS ≥ 2.452 Java 17 / 21 Pipeline plugin (declarative) JCasC (Configuration as Code)

Always run the LTS line in production — weekly releases ship breakage. Java 17 is the floor since LTS 2.426; new LTS lines require Java 21. Declarative pipelines (the pipeline { ... } shape) are the strong default; scripted (node('...') { ... }) is the older Groovy DSL — use it only when declarative isn’t expressive enough.

Docker · plugins · JCasCSetup

bash
# 1. Run Jenkins LTS in Docker with persistent state
docker volume create jenkins_home

docker run -d --name jenkins \
    -p 8080:8080 -p 50000:50000 \
    -v jenkins_home:/var/jenkins_home \
    -v /var/run/docker.sock:/var/run/docker.sock \
    jenkins/jenkins:lts-jdk21

# 2. Initial admin password
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

# 3. Pre-install plugins from a plugins.txt (one per line: short-name[:version])
cat > plugins.txt <<'TXT'
workflow-aggregator
configuration-as-code
github-branch-source
docker-workflow
kubernetes
credentials-binding
htmlpublisher
junit
blueocean
TXT

docker exec jenkins \
    jenkins-plugin-cli --plugin-file /tmp/plugins.txt \
                       --plugin-download-directory /var/jenkins_home/plugins

# 4. Configuration-as-Code — declarative instance config
#    point Jenkins at it via env var
docker exec jenkins \
    env CASC_JENKINS_CONFIG=/var/jenkins_home/casc.yaml || true
# casc.yaml: define security realm, credentials, jobs, tools — all in YAML

# 5. CLI from outside
java -jar jenkins-cli.jar -s http://localhost:8080/ -auth admin:**** list-jobs

pipeline { ... }Declarative pipeline

pipeline { agent any; stages { ... } }Top-level wrapper. Required.
agent anyAny free executor on the controller or an agent.
agent { label 'linux && docker' }Match labels with boolean expressions.
agent { docker { image 'maven:3.9' } }Run the whole pipeline inside a container.
agent { kubernetes { yaml '...' } }Spin up a pod-per-build via the Kubernetes plugin.
agent noneNo global agent — each stage picks its own.
stages { stage('X') { steps { ... } } }Ordered execution blocks. Each must have steps.
steps { sh 'make build' }Shell step. bat on Windows, powershell for PowerShell.
steps { echo 'msg' }Print to console log.
environment { FOO = 'bar' }Pipeline / stage-scoped env vars.
tools { jdk 'jdk21'; maven 'maven-3.9' }Install tools from "Global Tool Configuration".
post { always { ... } success { ... } failure { ... } }Lifecycle hooks. Run regardless of stage outcome.
when { branch 'main' }Skip a stage unless the predicate matches.
parameters { choice(...) booleanParam(...) string(...) password(...) }Surface UI parameters on the "Build with Parameters" page.
triggers { cron('H 2 * * 1-5'); githubPush() }Schedule + SCM triggers. H spreads load across the hour.
javascript
// Jenkinsfile — declarative pipeline at repo root.
pipeline {
    agent { label 'linux && docker' }

    options {
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
        disableConcurrentBuilds()
        ansiColor('xterm')
    }

    parameters {
        choice(name: 'TARGET_ENV', choices: ['dev', 'stg', 'prod'], description: 'deploy target')
        booleanParam(name: 'RUN_E2E', defaultValue: false)
    }

    environment {
        REGISTRY = 'ghcr.io/my-org'
        APP      = 'web'
    }

    triggers {
        githubPush()
        cron('H 2 * * 1-5')              // weekday early-morning rebuilds
    }

    stages {
        stage('Build') {
            steps { sh 'make build' }
        }

        stage('Test') {
            parallel {
                stage('unit') { steps { sh 'pytest --junitxml=junit.xml' } }
                stage('lint') { steps { sh 'ruff check .' } }
            }
            post { always { junit 'junit.xml' } }
        }

        stage('Deploy') {
            when { branch 'main'; beforeAgent true }
            steps {
                input message: "Deploy to ${params.TARGET_ENV}?", ok: 'Go'
                sh "./deploy.sh ${params.TARGET_ENV}"
            }
        }
    }

    post {
        always  { archiveArtifacts artifacts: 'build/*.zip', fingerprint: true }
        failure { slackSend channel: '#ci-alerts', message: "Pipeline FAILED: ${env.BUILD_URL}" }
    }
}

parallel · matrix · whenStages

parallel { stage('a'){...} stage('b'){...} }Run sub-stages concurrently.
parallel(failFast: true) { ... }Cancel siblings on first failure.
matrix { axes { axis { name 'OS'; values 'linux','mac' } } stages {...} }Cartesian product of axes — one job per combo.
matrix { excludes { exclude { axis {...} } } }Drop specific combinations.
when { branch 'main' }Branch predicate (supports wildcards).
when { tag pattern: 'v.*', comparator: 'REGEXP' }Tag predicate.
when { changeset 'src/**' }Run only if matching paths changed.
when { expression { env.FOO == 'bar' } }Arbitrary Groovy predicate.
when { beforeAgent true; ... }Evaluate the when before grabbing the agent — saves an executor slot.
input message: 'Deploy?', ok: 'Go', submitter: 'admins'Manual approval step. Pauses the pipeline.
stash { name: 'binaries', includes: 'build/**' }Cache file set for downstream stages / nodes.
unstash 'binaries'Pull a stash into the current workspace.

labels · containers · k8sAgents

ControllerThe Jenkins server itself. Don’t run builds on it in prod.
Permanent agent (SSH / JNLP)Long-lived host. JNLP "inbound" agents avoid open SSH.
Docker / docker-pipeline pluginSpin up a container per stage with agent { docker }.
Kubernetes pluginOne pod per build — ephemeral, autoscaled.
EC2 / Cloud pluginsSpin up cloud VMs on demand; tear them down after.
label 'linux && docker && !arm64'Boolean expressions on labels — the standard targeting mechanism.
node('linux') { ... }Scripted-style agent grab. Reserve an executor for the block.
customWorkspace '/build/ws'Override the workspace path. Useful for path-length-limited Windows.
retainBuildExecutor falseFree the executor while paused on input.

Built-in stepsPipeline DSL

checkout scmClone the SCM that triggered the build.
git url: '...', branch: 'main', credentialsId: 'gh'Explicit Git clone.
sh 'cmd' · bat 'cmd' · powershell 'cmd'Shell on Linux/macOS, batch/PowerShell on Windows.
sh returnStdout: true, script: 'date'Capture stdout into a Groovy variable.
sh returnStatus: true, script: '...'Return exit code instead of failing on non-zero.
readFile 'pom.xml' · writeFileWorkspace file IO without shelling out.
readJSON / readYaml / readMavenPomStructured-file parsers (Pipeline-utility-steps plugin).
junit 'junit.xml'Parse JUnit XML — feeds the Tests tab + flaky-test data.
archiveArtifacts artifacts: 'build/*.zip', fingerprint: trueUpload build artifacts. Browseable + traceable.
publishHTML target: [reportDir: 'cov', reportFiles: 'index.html', ...]Attach an HTML report (coverage / lighthouse).
slackSend channel: '#alerts', message: 'msg'Slack notification (slack plugin).
emailext to: '...', subject: '...', body: '...'Rich email notifications (email-ext plugin).
timeout(time: 30, unit: 'MINUTES') { ... }Abort wrapped block after N units.
retry(3) { sh '...' }Re-run on failure up to N times.
catchError(...) { ... }Catch a failure but record it on the build.
error 'reason'Fail the build with a message.

node { ... } · full GroovyScripted pipeline

node('label') { ... }Acquire an executor and run the block on it.
stage('Name') { ... }Visual stage on the Stage View / Blue Ocean.
parallel a: { ... }, b: { ... }, failFast: trueRun named branches concurrently.
try / catch / finallyPlain Groovy control flow — precise error handling.
docker.image('img').inside('-v ...') { ... }Run nested steps inside a container with options.
script { ... }Inside declarative: drop into scripted mode for one block.
env.BUILD_NUMBER · env.JOB_NAME · env.BRANCH_NAMEBuilt-in env vars (always populated).
currentBuild.result = 'UNSTABLE'Mark the build manually.
javascript
// Scripted pipeline — Groovy with full programmability.
// Use when declarative isn't expressive enough (rare).
node('linux && docker') {

    try {
        stage('Checkout') {
            checkout scm
        }

        stage('Build') {
            // Run inside a container without losing the workspace
            docker.image('maven:3.9-eclipse-temurin-21').inside('-v $HOME/.m2:/root/.m2') {
                sh 'mvn -B -DskipTests package'
            }
        }

        // Parallel scripted stages
        stage('Verify') {
            parallel(
                unit:        { sh 'mvn test' },
                integration: { sh 'mvn verify -DskipUnitTests' },
                lint:        { sh 'mvn spotbugs:check' },
                failFast: true,
            )
        }

        stage('Publish') {
            if (env.BRANCH_NAME == 'main') {
                withCredentials([usernamePassword(
                    credentialsId: 'ghcr',
                    usernameVariable: 'U', passwordVariable: 'P')]) {
                    sh 'echo $P | docker login ghcr.io -u $U --password-stdin'
                    sh 'docker push ghcr.io/my-org/app:${BUILD_NUMBER}'
                }
            } else {
                echo "Skipping publish on branch ${env.BRANCH_NAME}"
            }
        }
    }
    catch (err) {
        currentBuild.result = 'FAILURE'
        throw err
    }
    finally {
        junit testResults: '**/target/surefire-reports/*.xml', allowEmptyResults: true
    }
}

Reusable Groovy codeShared libraries

@Library('platform-shared@v1.4.0') _Import a library at a specific ref. The trailing _ is required.
@Library(['lib-a@main', 'lib-b@v2']) _Multiple libraries in one annotation.
vars/myStep.groovyBecomes a global step myStep() callable from any pipeline.
def call(Map cfg) { ... }Standard signature for the global step.
src/org/example/Foo.groovyClassic Groovy classes. Reusable from vars/.
resources/templates/x.tplStatic resources fetched via libraryResource('templates/x.tpl').
Manage Jenkins → Global Pipeline LibrariesConfigure default library / repo / version.
@Library('lib') import org.example.*Pull in classes directly when using src/.
javascript
// Shared library layout (separate Git repo):
//   vars/
//     deployApp.groovy          // exposed as a global step
//   src/
//     org/example/Builder.groovy
//   resources/
//     templates/dockerfile.tpl  // libraryResource('templates/dockerfile.tpl')

// vars/deployApp.groovy
def call(Map cfg = [:]) {
    def env  = cfg.get('env',  'dev')
    def app  = cfg.get('app',  env.JOB_NAME)
    def wait = cfg.get('wait', true)

    stage("Deploy ${app} → ${env}") {
        withCredentials([file(credentialsId: "kube-${env}", variable: 'KUBECONFIG')]) {
            sh "kubectl apply -f k8s/${env}/"
            if (wait) {
                sh "kubectl rollout status deployment/${app} --timeout=5m"
            }
        }
    }
}

// Jenkinsfile in the consuming repo — pin the library to a tag for reproducibility.
@Library('platform-shared@v1.4.0') _

pipeline {
    agent any
    stages {
        stage('Build')  { steps { sh 'make build' } }
        stage('Deploy') {
            when { branch 'main' }
            steps {
                deployApp(app: 'checkout', env: 'prod', wait: true)
            }
        }
    }
}

withCredentials · maskingCredentials

Manage Jenkins → CredentialsCentral store. Scopes: system, global, per-folder, per-user.
usernamePassword(credentialsId, usernameVariable, passwordVariable)Binds U + P env vars for the block.
string(credentialsId, variable: 'TOKEN')Single-string secret — API tokens.
file(credentialsId, variable: 'KUBECONFIG')Mount a secret file; variable holds the path.
sshUserPrivateKey(credentialsId, keyFileVariable, usernameVariable)SSH key + username for ssh -i usage.
certificate(credentialsId, keystoreVariable, passwordVariable)PKCS12 keystore.
withCredentials([...]) { ... }Wrap the block needing the secrets. Vars are unset after.
credentials('id')Short-hand inside environment { FOO = credentials('id') } — expands to FOO_USR / FOO_PSW.
Mask Passwords pluginSecrets are masked in console logs automatically — never echo them.
Vault / AWS Secrets Manager pluginsResolve credentials at runtime from an external store.
javascript
// Credentials are stored centrally and bound to env vars at step time.
// Manage Jenkins → Credentials → System → Global credentials.

pipeline {
    agent any
    stages {
        stage('Docker login (username+password)') {
            steps {
                withCredentials([usernamePassword(
                        credentialsId: 'docker-hub',
                        usernameVariable: 'DOCKER_USER',
                        passwordVariable: 'DOCKER_PASS')]) {
                    sh 'echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin'
                }
            }
        }

        stage('API token (secret text)') {
            steps {
                withCredentials([string(credentialsId: 'gh-token', variable: 'GH_TOKEN')]) {
                    sh 'curl -H "Authorization: bearer $GH_TOKEN" https://api.github.com/user'
                }
            }
        }

        stage('Kubeconfig (secret file)') {
            steps {
                withCredentials([file(credentialsId: 'kube-prod', variable: 'KUBECONFIG')]) {
                    sh 'kubectl get pods -A'
                }
            }
        }

        stage('SSH key') {
            steps {
                withCredentials([sshUserPrivateKey(
                        credentialsId: 'deploy-key',
                        keyFileVariable: 'KEY_FILE',
                        usernameVariable: 'USER')]) {
                    sh 'ssh -i $KEY_FILE $USER@host "uptime"'
                }
            }
        }
    }
}
// Values are masked in the console log automatically.

cron · SCM · upstreamTriggers

triggers { cron('H 2 * * 1-5') }Cron schedule. H = hashed minute (spreads load).
triggers { pollSCM('H/5 * * * *') }Poll the SCM for changes. Avoid — prefer push-based hooks.
triggers { githubPush() }GitHub webhook trigger (github plugin).
triggers { gitlab() }GitLab webhook trigger.
triggers { upstream('parent-job', threshold: SUCCESS) }Trigger from another job’s result.
Multibranch pipeline + Branch SourcesAuto-discovers branches + MRs. Each branch gets its own pipeline.
parameters: build job: 'down', parameters: [string(...)]Trigger downstream from a pipeline step.

UX layersUI

Blue OceanModern UI for pipelines — stage graph, log drill-in. Plugin-based.
Stage View (Pipeline plugin)Stripped-down stage timeline in the classic UI.
Build with ParametersAuto-generated form from parameters {...}.
ReplayRe-run a build with a one-off edited pipeline — great for iteration.
Pipeline Syntax (Snippet Generator)UI form → generates Groovy for any installed step.
Console OutputRaw log per build. /log URL is bookmarkable.

Day-one setEssential plugins

workflow-aggregator"Pipeline" itself — declarative + scripted + steps.
credentials-bindingwithCredentials step + bindings.
docker-workflowdocker.image('...').inside { } + agent { docker }.
kubernetesPod-per-build agents on K8s.
github-branch-source / gitlab-branch-sourceMultibranch + PR/MR discovery.
junit / htmlpublisher / coverageTest reports, HTML reports, coverage UI.
configuration-as-code (JCasC)Preferred Declarative instance config — checked in YAML.
job-dslDefine seed jobs as Groovy. Complements JCasC.
slack / email-extNotifications.
blueoceanModern UI plugin bundle.
build-monitor-pluginBig-screen dashboard for radiators.

Configuration as CodeJCasC

CASC_JENKINS_CONFIG=/path/to/casc.yamlEnv var pointing at the YAML (or directory).
jenkins: securityRealm: local: ...Define the security realm and user store.
jenkins: authorizationStrategy: globalMatrix: ...Permissions model in YAML.
credentials: system: domainCredentials: [...]Bootstrap secrets from the env / external secret stores.
unclassified: globalLibraries: libraries: [...]Wire in shared libraries declaratively.
tool: jdk: installations: [...] maven: installations: [...]Global Tool Configuration as code.
Manage Jenkins → Configuration as Code → Reload existing configurationApply changes without restart.
jenkins.yaml + job-dsl → seed jobsStandard combo: JCasC for the instance, job-dsl for the catalogue of jobs.

Full pipeline · ~60 linesEnd-to-end · Matrix → build → deploy

Matrix test across Node versions and DB backends, image build with GHCR credentials, manual approval gate, and a Kubernetes deploy via a shared-library global step.

javascript
// End-to-end Jenkinsfile: matrix tests → image build → manual approval → k8s deploy.
@Library('platform-shared@v1.4.0') _

pipeline {
    agent none

    options {
        timeout(time: 45, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '30'))
        disableConcurrentBuilds()
        ansiColor('xterm')
    }

    parameters {
        choice(name: 'ENV', choices: ['stg', 'prod'])
    }

    stages {
        stage('Test matrix') {
            matrix {
                agent { label 'linux && docker' }
                axes {
                    axis { name 'NODE';    values '20', '22' }
                    axis { name 'BACKEND'; values 'postgres', 'mysql' }
                }
                stages {
                    stage('test') {
                        steps {
                            sh "docker compose -f compose.${BACKEND}.yml up -d"
                            sh "nvm exec ${NODE} npm test -- --ci --reporters=jest-junit"
                        }
                        post {
                            always { junit 'junit.xml' }
                            cleanup { sh 'docker compose down -v' }
                        }
                    }
                }
            }
        }

        stage('Build image') {
            agent { label 'linux && docker' }
            steps {
                withCredentials([usernamePassword(credentialsId: 'ghcr',
                                                  usernameVariable: 'U',
                                                  passwordVariable: 'P')]) {
                    sh 'echo $P | docker login ghcr.io -u $U --password-stdin'
                    sh "docker build -t ghcr.io/my-org/app:${BUILD_NUMBER} ."
                    sh "docker push  ghcr.io/my-org/app:${BUILD_NUMBER}"
                }
            }
        }

        stage('Deploy') {
            when { branch 'main' }
            steps {
                input message: "Deploy to ${params.ENV}?", ok: 'Go'
                deployApp(app: 'web', env: params.ENV)
            }
        }
    }

    post {
        failure { slackSend channel: '#ci-alerts', message: "FAILED: ${env.BUILD_URL}" }
        success { slackSend channel: '#deploys',   message: "✓ ${env.JOB_NAME} #${env.BUILD_NUMBER}" }
    }
}

Best practiceGood to know

Default to declarative pipelines. They’re linted, easier to review, and lock you into a sane structure. Drop into script { ... } only for genuinely dynamic logic — otherwise the next person maintaining the pipeline pays the price.
Configure everything through JCasC + job-dsl. Anything you click in the UI evaporates on the next disaster-recovery rebuild. Treat the YAML as the source of truth, run it through a PR like any other code.
Pin shared libraries to a tag, not main. @Library('platform-shared@v1.4.0') means a library change can’t silently break every pipeline at 3am. Bump deliberately, ride one repo at a time.

Common trapsWatch out for

Don’t run builds on the controller. Controller-side builds share JVM, disk, and security context with the dashboard itself. One bad sh 'rm -rf $WORKSPACE/..' can wipe configs. Set controller executors to 0 and use agents.
Approved-scripts sandbox bites. Scripted pipelines run in a Groovy sandbox — new methods need admin approval through "In-process Script Approval". Surprising for shared-library devs. Stick to whitelisted APIs or use trusted libraries.
Plugin versions drift fast. Jenkins is plugin-heavy. Upgrading one without others can break the dashboard. Lock plugin versions via JCasC + jenkins-plugin-cli, and stage upgrades in a non-prod controller.

Go deeperSee also

Jenkins FAQ

What is Jenkins and what is it used for?

Jenkins is an open-source automation server used for building, testing, and deploying software. It is the most widely used CI/CD tool, running on a Java server with hundreds of plugins for version control, build tools, cloud platforms, and notifications.

What is a Jenkinsfile?

A Jenkinsfile is a text file stored in the root of your repository that defines a Jenkins pipeline as code. It can use the declarative syntax (pipeline { agent any; stages { ... } }) or the more flexible scripted syntax (node { stage("Build") { ... } }).

What is the difference between declarative and scripted pipelines in Jenkins?

Declarative pipelines use a structured, opinionated syntax validated by Jenkins before execution — easier to read and lint. Scripted pipelines are plain Groovy and give you full programmatic control but require more discipline. Prefer declarative for new pipelines; use scripted blocks inside declarative with script {} when you need loops or conditionals.

How does Jenkins handle secrets and credentials?

Jenkins stores secrets in the Credentials store, encrypted at rest. In a Jenkinsfile, access them with withCredentials([usernamePassword(credentialsId: "id", usernameVariable: "U", passwordVariable: "P")]) { sh "curl -u $U:$P ..." }. Never echo credentials — Jenkins automatically masks them in the console.

How do I run parallel stages in Jenkins?

Use the parallel directive inside a stage. Each key becomes a sub-stage that runs concurrently: stage("Test") { parallel { stage("Unit") { steps { sh "..." } }; stage("Lint") { steps { sh "..." } } } }. Add failFast: true to abort all branches as soon as one fails.

What is a Jenkins agent and how do I configure one?

An agent is the machine or container that executes pipeline steps. Set agent any to run on any available agent, agent { label "docker" } to target a specific node label, or agent { docker "node:20" } to run steps inside a Docker container. Agents can be configured as permanent nodes or ephemeral cloud instances.