All topics
DevOps · Learning hub

Jenkins notes for developers

Master Jenkins with a curated set of 3 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore DevOps notes
Jenkins

Declarative Pipelines & Jenkinsfile

Jenkins: Declarative Pipelines & Jenkinsfile Jenkins is the most widely used open-source CI/CD server. Pipelines are defined as code in a Jenkinsfile (checked i

Jenkins: Declarative Pipelines & Jenkinsfile

Jenkins is the most widely used open-source CI/CD server. Pipelines are defined as code in a Jenkinsfile (checked into the repo) using either Declarative or Scripted syntax. Declarative is preferred — more structured and readable.

Declarative Pipeline Structure

// Jenkinsfile — declarative pipeline
pipeline {
    agent any              // run on any available agent

    options {
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
        buildDiscarder(logRotator(numToKeepStr: '10'))
        timestamps()
    }

    environment {
        APP_NAME = 'my-app'
        DOCKER_REGISTRY = 'registry.example.com'
        NODE_VERSION = '20'
    }

    triggers {
        githubPush()            // trigger on GitHub push
        cron('H 2 * * 1-5')   // nightly at ~2am Mon-Fri
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm   // check out source code
            }
        }

        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Lint & Test') {
            parallel {
                stage('Lint') {
                    steps { sh 'npm run lint' }
                }
                stage('Unit Tests') {
                    steps {
                        sh 'npm test -- --coverage'
                        junit 'test-results/**/*.xml'
                        publishCoverage adapters: [coberturaAdapter('coverage/cobertura-coverage.xml')]
                    }
                }
            }
        }

        stage('Build') {
            steps {
                sh 'npm run build'
            }
        }

        stage('Deploy') {
            when {
                branch 'main'   // only deploy from main branch
            }
            steps {
                sh './scripts/deploy.sh'
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'dist/**/*', fingerprint: true
            cleanWs()          // clean workspace after build
        }
        success {
            slackSend channel: '#deployments', message: "✅ ${env.JOB_NAME} #${env.BUILD_NUMBER} passed"
        }
        failure {
            slackSend channel: '#deployments', color: 'danger',
                message: "❌ ${env.JOB_NAME} #${env.BUILD_NUMBER} failed: ${env.BUILD_URL}"
            emailext to: 'team@example.com', subject: "Build Failed: ${env.JOB_NAME}",
                body: "Build ${env.BUILD_NUMBER} failed. See: ${env.BUILD_URL}"
        }
    }
}

Stages & Steps

stages {
    stage('Test') {
        steps {
            // Shell commands
            sh 'npm test'
            sh '''
                echo "Multi-line shell"
                npm run lint
                npm run build
            '''

            // Read/write files
            writeFile file: 'version.txt', text: env.BUILD_NUMBER
            def version = readFile('version.txt').trim()

            // Environment variables
            echo "Building ${env.BRANCH_NAME} on ${env.NODE_NAME}"
            echo "Build URL: ${env.BUILD_URL}"

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

            // Conditional
            script {
                if (env.BRANCH_NAME == 'main') {
                    sh 'npm run deploy:prod'
                } else {
                    sh 'npm run deploy:staging'
                }
            }

            // Input — wait for manual approval
            input message: 'Deploy to production?', ok: 'Deploy'
        }
    }
}

When Conditions

stage('Deploy Production') {
    when {
        // Run only when all conditions are true
        allOf {
            branch 'main'
            not { changeRequest() }   // not a PR
            environment name: 'CI', value: 'true'
        }
    }
    steps { sh './deploy.sh prod' }
}

stage('Deploy Preview') {
    when {
        anyOf {
            branch 'develop'
            changeRequest()          // pull request
        }
    }
    steps { sh './deploy.sh preview' }
}

// Other when conditions:
// tag 'v*'               — matches tag pattern
// expression { return params.DEPLOY == 'true' }
// beforeAgent true       — evaluate before allocating agent
Jenkins

Agents, Credentials & Docker

Jenkins: Agents, Credentials & Docker Agents // Top-level agent pipeline { agent { label 'linux && docker' } // run on agent with both labels } // Per-stage age

Jenkins: Agents, Credentials & Docker

Agents

// Top-level agent
pipeline {
    agent { label 'linux && docker' }   // run on agent with both labels
}

// Per-stage agent (overrides top-level)
stage('Build') {
    agent { label 'node-20' }
    steps { sh 'npm run build' }
}

// Docker agent — run inside container
stage('Test') {
    agent {
        docker {
            image 'node:20-alpine'
            args '-v /tmp:/tmp'
            reuseNode true            // reuse workspace from outer agent
        }
    }
    steps { sh 'npm test' }
}

// Kubernetes agent (Jenkins Kubernetes plugin)
agent {
    kubernetes {
        yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: node
    image: node:20
    command: ['cat']
    tty: true
"""
        defaultContainer 'node'
    }
}

// None — agents per stage
pipeline {
    agent none
    stages {
        stage('Build') { agent { label 'builder' }; steps { sh '...' } }
        stage('Test')  { agent { label 'tester'  }; steps { sh '...' } }
    }
}

Credentials

pipeline {
    environment {
        // Bind credential to env var (masked in logs)
        DOCKER_CREDS = credentials('docker-hub-credentials')  // username:password
        AWS_ACCESS_KEY_ID     = credentials('aws-access-key')
        AWS_SECRET_ACCESS_KEY = credentials('aws-secret-key')
    }

    stages {
        stage('Push Image') {
            steps {
                // DOCKER_CREDS_USR and DOCKER_CREDS_PSW are auto-set for username/password creds
                sh 'docker login -u $DOCKER_CREDS_USR -p $DOCKER_CREDS_PSW'
            }
        }

        stage('SSH Deploy') {
            steps {
                // SSH key credential
                sshagent(['my-server-ssh-key']) {
                    sh 'ssh user@server.example.com ./deploy.sh'
                    sh 'scp -r dist/ user@server.example.com:/var/www/app/'
                }
            }
        }

        stage('Use Secret File') {
            steps {
                withCredentials([file(credentialsId: 'gcp-service-account', variable: 'GCP_KEY')]) {
                    sh 'gcloud auth activate-service-account --key-file=$GCP_KEY'
                }
            }
        }
    }
}

Docker in Jenkins

pipeline {
    agent any

    stages {
        stage('Build Docker Image') {
            steps {
                script {
                    def image = docker.build("my-app:${env.BUILD_NUMBER}")

                    docker.withRegistry('https://registry.example.com', 'registry-credentials') {
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }

        stage('Test in Docker') {
            steps {
                script {
                    docker.image('node:20-alpine').inside('-v $PWD:/app -w /app') {
                        sh 'npm ci && npm test'
                    }
                }
            }
        }
    }
}

// Multi-stage Docker build and scan
stage('Security Scan') {
    steps {
        sh 'docker scout cves my-app:${env.BUILD_NUMBER} --exit-code'
        sh 'trivy image --exit-code 1 --severity HIGH,CRITICAL my-app:${env.BUILD_NUMBER}'
    }
}

Parameters & Build Triggers

pipeline {
    parameters {
        string(name: 'VERSION', defaultValue: '', description: 'Version to deploy')
        choice(name: 'ENVIRONMENT', choices: ['staging', 'production'], description: 'Target env')
        booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: 'Skip test suite')
        password(name: 'OVERRIDE_TOKEN', defaultValue: '', description: 'Override token')
    }

    stages {
        stage('Deploy') {
            when { not { expression { params.SKIP_TESTS } } }
            steps {
                echo "Deploying ${params.VERSION} to ${params.ENVIRONMENT}"
                sh "./deploy.sh ${params.ENVIRONMENT} ${params.VERSION}"
            }
        }
    }
}

// Trigger from upstream job
triggers {
    upstream(upstreamProjects: 'my-app/main', threshold: hudson.model.Result.SUCCESS)
}

// Webhook trigger (GitHub plugin)
triggers { githubPush() }
Jenkins

Shared Libraries, Plugins & Best Practices

Jenkins: Shared Libraries, Plugins & Best Practices Shared Libraries Shared libraries allow you to extract common pipeline logic into reusable Groovy code store

Jenkins: Shared Libraries, Plugins & Best Practices

Shared Libraries

Shared libraries allow you to extract common pipeline logic into reusable Groovy code stored in a separate repo. Import with @Library annotation.

// In Git repo: jenkins-shared-libs/
// vars/deployApp.groovy — global variable (callable as a step)
def call(Map config) {
    def env = config.environment ?: 'staging'
    def image = config.image

    sh "kubectl set image deployment/${config.app} app=${image} -n ${env}"
    sh "kubectl rollout status deployment/${config.app} -n ${env}"
}

// vars/notify.groovy
def slack(String channel, String message, String color = 'good') {
    slackSend channel: channel, color: color, message: message
}

// src/com/example/Docker.groovy — class (imported explicitly)
package com.example

class Docker implements Serializable {
    def steps

    Docker(steps) { this.steps = steps }

    def build(String name, String tag) {
        steps.sh "docker build -t ${name}:${tag} ."
    }
}

// Usage in Jenkinsfile:
@Library('jenkins-shared-libs@main') _

pipeline {
    stages {
        stage('Deploy') {
            steps {
                deployApp(app: 'my-app', image: "my-app:${env.BUILD_NUMBER}", environment: 'production')
                notify.slack('#deploys', "Deployed my-app ${env.BUILD_NUMBER}")
            }
        }
    }
}

Essential Plugins

  • Pipeline: Declarative — declarative pipeline syntax (core)

  • Blue Ocean — modern UI for pipelines (optional, still useful for visualization)

  • Git / GitHub Branch Source — SCM integration, multibranch pipelines

  • Credentials Binding — inject credentials as env vars

  • Docker Pipeline — docker.build(), docker.withRegistry()

  • Kubernetes — run agents as Kubernetes pods

  • Slack Notification — slackSend() step

  • JUnit — publish test results (junit plugin)

  • HTML Publisher — publish HTML coverage/test reports

  • Workspace Cleanup — cleanWs() step

  • Timestamper — add timestamps to console output

  • AnsiColor — colorize terminal output

  • Job DSL — generate jobs from code (seed jobs)

Best Practices

  • Always use Declarative over Scripted — clearer syntax, better error messages, syntax validation

  • Jenkinsfile in the repo — treat pipeline as code; version it alongside the application

  • Parallel stages for independent tasks — lint + test + security scan in parallel saves significant time

  • Fail fast: run cheapest/fastest checks first (lint before integration tests)

  • Clean workspace: use cleanWs() in post { always } to prevent disk fill-up on agents

  • Pin plugin versions: use a pinned Plugin Installation Manager file to ensure reproducible builds

  • Use credentials plugin — never hardcode secrets in Jenkinsfile; they'll be visible in git history

  • Timeouts everywhere — add timeout() to pipeline and long-running steps to prevent hung builds

  • Artifacts: archive build artifacts with fingerprinting for traceability across jobs

  • Multibranch pipeline: automatically creates jobs for every branch/PR in the repo

Multibranch Pipeline

// Automatically scans all branches + PRs in a repo
// Each branch gets its own build history and workspace

pipeline {
    agent any

    stages {
        stage('Test') {
            steps { sh 'npm test' }
        }

        stage('Deploy') {
            when {
                anyOf {
                    branch 'main'
                    branch 'develop'
                }
            }
            steps {
                script {
                    def env = env.BRANCH_NAME == 'main' ? 'production' : 'staging'
                    sh "./deploy.sh ${env}"
                }
            }
        }
    }

    post {
        // GitHub/GitLab PR status checks
        success { githubNotify status: 'SUCCESS', context: 'Jenkins CI' }
        failure { githubNotify status: 'FAILURE', context: 'Jenkins CI' }
    }
}

Keep your Jenkins knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever