# Jenkins Cheatsheet

- URL: https://karandeepsingh.ca/cheatsheets/devops/jenkins/
- Author: Karandeep Singh
- Published: 2025-06-11
- Tags: jenkins, cicd
- Summary: Quick-reference Jenkins pipeline syntax. Declarative skeletons, credentials binding, parallel stages, shared libraries, triggers, and troubleshooting fixes.

I write Jenkinsfiles weekly and still forget the exact `when` syntax every single time, so this page stays open in a pinned tab. Everything below is copy-paste ready and tested against a recent Jenkins LTS. Most of my pipelines end up calling [Ansible](https://karandeepsingh.ca/cheatsheets/devops/ansible/) or building [Docker](https://karandeepsingh.ca/cheatsheets/containers/docker/) images, so those two sheets pair well with this one.

## Declarative Pipeline Skeleton

> **Docs:** [Pipeline syntax (Jenkins docs)](https://www.jenkins.io/doc/book/pipeline/syntax/)

```groovy
pipeline {
    agent any
    options {
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
        disableConcurrentBuilds()
        timestamps()
    }
    environment {
        APP_ENV = 'staging'
    }
    stages {
        stage('Build') {
            steps {
                sh 'make build'
            }
        }
        stage('Test') {
            steps {
                sh 'make test'
            }
        }
        stage('Deploy') {
            steps {
                sh './deploy.sh'
            }
        }
    }
    post {
        always  { cleanWs() }
        success { echo 'Build passed' }
        failure { mail to: 'team@example.com', subject: "FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}", body: "${env.BUILD_URL}" }
    }
}
```

## Scripted vs Declarative

> **Docs:** [Pipeline overview (Jenkins docs)](https://www.jenkins.io/doc/book/pipeline/)

| Aspect | Declarative | Scripted |
|---|---|---|
| Syntax | `pipeline { ... }` | `node { ... }` |
| Structure | Opinionated, validated | Free-form Groovy |
| `post` / `when` / `options` | Built-in | DIY with try/catch |
| Best for | 95% of jobs | Complex dynamic logic |

Rule of thumb: start declarative, drop into a `script { }` block only when you need loops or dynamic stage generation.

## Environment & Credentials

**danger:**
`withCredentials` masks a secret in the log, but only the exact string. Echo
it base64-encoded, pass it through `jq`, or let it reach a child process that
prints it, and the mask does not apply — it appears in full. Never interpolate
a credential into a `sh` step with double quotes either: use single quotes so
Groovy leaves it to the shell's environment.

```diff
# Jenkinsfile
@@ -8,7 +8,8 @@ steps {
   withCredentials([string(credentialsId: 'api-token', variable: 'TOKEN')]) {
-    sh "curl -H 'Authorization: $TOKEN' https://api.example.com/deploy"
+    // single quotes: Groovy does not interpolate, the shell reads $TOKEN
+    sh 'curl -H "Authorization: $TOKEN" https://api.example.com/deploy'
   }
 }
```

> **Docs:** [Using credentials (Jenkins docs)](https://www.jenkins.io/doc/book/using/using-credentials/)

```groovy
environment {
    // From Jenkins credentials store (ID must exist)
    AWS_CREDS   = credentials('aws-prod')          // sets AWS_CREDS_USR / AWS_CREDS_PSW
    API_TOKEN   = credentials('api-token-secret')  // secret text -> single var
}
```

```groovy
// Scoped binding inside steps - preferred for secrets
steps {
    withCredentials([
        usernamePassword(credentialsId: 'nexus', usernameVariable: 'NEXUS_USER', passwordVariable: 'NEXUS_PASS'),
        string(credentialsId: 'slack-token', variable: 'SLACK_TOKEN'),
        file(credentialsId: 'kubeconfig-prod', variable: 'KUBECONFIG'),
        sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'SSH_KEY')
    ]) {
        sh 'curl -u "$NEXUS_USER:$NEXUS_PASS" https://nexus.internal/upload'
    }
}
```

Always use single quotes in `sh` so secrets are expanded by the shell, not interpolated into the Groovy string (which leaks them to the build log).

## Parameters

> **Docs:** [Pipeline syntax: parameters (Jenkins docs)](https://www.jenkins.io/doc/book/pipeline/syntax/#parameters)

```groovy
parameters {
    string(name: 'VERSION', defaultValue: '1.0.0', description: 'Release version')
    choice(name: 'ENV', choices: ['dev', 'staging', 'prod'], description: 'Target environment')
    booleanParam(name: 'SKIP_TESTS', defaultValue: false)
    text(name: 'RELEASE_NOTES', defaultValue: '')
}
// Access: params.VERSION, params.ENV, params.SKIP_TESTS
```

## when Conditions

> **Docs:** [Pipeline syntax: when (Jenkins docs)](https://www.jenkins.io/doc/book/pipeline/syntax/#when)

```groovy
stage('Deploy Prod') {
    when {
        allOf {
            branch 'main'
            environment name: 'APP_ENV', value: 'prod'
            expression { return params.ENV == 'prod' }
            not { changeRequest() }          // skip on PR builds
        }
    }
    steps { sh './deploy.sh prod' }
}
```

| Condition | Matches when |
|---|---|
| `branch 'main'` | Branch name matches (multibranch) |
| `tag 'v*'` | Build is for a matching tag |
| `changeRequest()` | Build is a PR |
| `changeset '**/*.tf'` | Commit touched matching files |
| `buildingTag()` | Any tag build |
| `triggeredBy 'TimerTrigger'` | Started by cron |
| `anyOf { } / allOf { } / not { }` | Boolean combinators |

## Parallel Stages

> **Docs:** [Pipeline syntax: parallel (Jenkins docs)](https://www.jenkins.io/doc/book/pipeline/syntax/#parallel)

```groovy
stage('Quality Gates') {
    parallel {
        stage('Lint')  { steps { sh 'make lint' } }
        stage('Unit')  { steps { sh 'make unit' } }
        stage('Integration') {
            agent { label 'docker' }
            steps { sh 'make integration' }
        }
    }
}
// failFast true  -> abort siblings on first failure (put inside options or parallel)
```

## Shared Libraries

> **Docs:** [Extending with shared libraries (Jenkins docs)](https://www.jenkins.io/doc/book/pipeline/shared-libraries/)

```groovy
// Jenkinsfile - library configured globally as "mylib"
@Library('mylib@main') _

standardBuild(language: 'go', notify: '#builds')   // vars/standardBuild.groovy
```

Repo layout:

```bash
(root)
├── vars/standardBuild.groovy      # def call(Map config) { ... } - callable as a step
├── src/org/acme/Utils.groovy      # classes, import as org.acme.Utils
└── resources/templates/pod.yaml   # libraryResource 'templates/pod.yaml'
```

## Stash/Unstash & Artifacts

> **Docs:** [Pipeline: Basic Steps reference (Jenkins docs)](https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/)

```groovy
stage('Build')  { steps { sh 'make dist'; stash name: 'dist', includes: 'dist/**' } }
stage('Deploy') { agent { label 'deployer' }
                  steps { unstash 'dist'; sh './push.sh' } }

// Keep artifacts on the build page
archiveArtifacts artifacts: 'dist/*.tar.gz', fingerprint: true, allowEmptyArchive: false
junit 'reports/**/*.xml'
```

Stash = pass files between stages/agents in the same build (temporary). Artifacts = keep files after the build (permanent, per retention policy).

## Triggers

> **Docs:** [Pipeline syntax: triggers (Jenkins docs)](https://www.jenkins.io/doc/book/pipeline/syntax/#triggers)

```groovy
triggers {
    cron('H 2 * * 1-5')          // weeknights, H spreads load
    pollSCM('H/5 * * * *')       // poll every ~5 min
    upstream(upstreamProjects: 'lib-build', threshold: hudson.model.Result.SUCCESS)
}
```

Webhook (preferred over polling): GitHub webhook to `https://jenkins.example.com/github-webhook/`, GitLab to `/project/<job>`. Generic jobs: enable "Trigger builds remotely" and hit `JENKINS_URL/job/NAME/build?token=TOKEN`.

## CLI & jenkins-cli.jar

> **Docs:** [Jenkins CLI (Jenkins docs)](https://www.jenkins.io/doc/book/managing/cli/)

```bash
curl -O https://jenkins.example.com/jnlpJars/jenkins-cli.jar   # from your own controller
alias jcli='java -jar jenkins-cli.jar -s https://jenkins.example.com -auth user:apitoken'

jcli list-jobs
jcli build my-job -p ENV=staging -f -v      # -f wait, -v stream console
jcli console my-job 42                      # console log of build 42
jcli reload-configuration
jcli safe-restart
jcli install-plugin blueocean -deploy
```

Blue Ocean lives at `/blue`. Best for visualising parallel stages and PR pipelines; classic UI is still where all the config lives.

## Common Groovy Snippets

> **Docs:** [Pipeline Utility Steps reference (Jenkins docs)](https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/)

```groovy
// Capture command output
def sha = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()

// Exit code without failing the stage
def rc = sh(script: './smoke-test.sh', returnStatus: true)
if (rc != 0) { unstable('smoke tests failed') }

// JSON in/out (Pipeline Utility Steps plugin)
def cfg = readJSON file: 'config.json'
echo "replicas: ${cfg.deploy.replicas}"
writeJSON file: 'out.json', json: [version: sha, env: params.ENV]

// YAML too
def vals = readYaml file: 'values.yaml'

// Retry and timeout wrappers
retry(3) { sh './flaky-fetch.sh' }
timeout(time: 5, unit: 'MINUTES') { input message: 'Deploy to prod?' }
```

## Agent Labels & Docker Agents

> **Docs:** [Using Docker with Pipeline (Jenkins docs)](https://www.jenkins.io/doc/book/pipeline/docker/)

```groovy
agent { label 'linux && docker' }        // label expression

agent {
    docker {
        image 'golang:1.22'
        args  '-v /var/cache/go:/go/pkg'
        reuseNode true
    }
}

agent {
    dockerfile { filename 'ci/Dockerfile'; label 'docker' }
}
```

`agent none` at the top + per-stage agents = no idle executor held between stages.

**tip:**
Use `H` instead of fixed minutes in cron triggers (`H 2 * * *` not `0 2 * * *`). Jenkins hashes the job name to pick a consistent minute, so a hundred nightly jobs don't all fire at 02:00 and flatten your agents.

## Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| `No such DSL method 'x'` | Missing plugin or typo | Install plugin (e.g. Pipeline Utility Steps), check spelling |
| Build stuck "Waiting for next available executor" | No agent matches label | Check label expression, agent online status, executor count |
| `script not permitted to use method` | Groovy sandbox block | Approve in Manage Jenkins → In-process Script Approval, or move logic to a shared library |
| Secrets appear as `****` but wrong value | Groovy string interpolation of creds | Use single-quoted `sh` strings, let the shell expand `$VAR` |
| `stash` not found in later stage | Different build or expired | Stash only lives within one build; use `archiveArtifacts`/`copyArtifacts` across builds |
| Webhook fires but no build | Branch not discovered | Re-scan multibranch pipeline, check branch filters |
| `Jenkinsfile not found` | Wrong script path or lightweight checkout issue | Fix "Script Path" in job config, verify branch |
| Docker agent: permission denied on socket | Jenkins user not in docker group | Add user to `docker` group on agent, restart agent |
| Workspace out of disk | No cleanup, fat workspaces | `cleanWs()` in `post { always }`, `buildDiscarder` in options |

## Related Cheatsheets

- [Docker](https://karandeepsingh.ca/cheatsheets/containers/docker/): image builds, registries, and the run flags behind every `agent { docker { ... } }` stage on this page
- [Ansible](https://karandeepsingh.ca/cheatsheets/devops/ansible/): playbooks, inventories, and Vault, which is my usual deploy step invoked from a Jenkins stage
- [Python Basics & CLI](https://karandeepsingh.ca/cheatsheets/python/python-basics-cli/): argparse, venv, and `json.tool` for the helper scripts your pipelines shell out to

More of these live in the [DevOps Tooling group](https://karandeepsingh.ca/cheatsheets/devops/), and the full [cheatsheet library](https://karandeepsingh.ca/cheatsheets/) has everything else.
