DevTools

Cheatsheet Jenkins

Servidor de automação CI/CD para builds, testes e deploys

Back to languages
Jenkins
72 cards found
Categories:
Versions:

Pipeline


9 cards
Declarative Pipeline
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'npm install'
                sh 'npm run build'
            }
        }
        stage('Test') {
            steps {
                sh 'npm test'
            }
        }
    }
}

# Fixed and readable structure.
# It is the recommended format.

The declarative pipeline has a fixed structure: pipeline > stages > stage > steps. It is the recommended format for readability. agent any defines where to run. Each stage groups logical CI/CD steps.

Environment
environment {
    APP_ENV = 'production'
    DB_HOST = 'db.example.com'
    VERSION = sh(
        script: 'git rev-parse --short HEAD',
        returnStdout: true).trim()
}

stages {
    stage('Build') {
        steps {
            echo "Version: ${env.VERSION}"
            sh 'echo $APP_ENV'
        }
    }
}

# Can also be per stage:
stage('Deploy') {
    environment { DEPLOY_TARGET = 'aws' }
}

environment defines environment variables. sh(returnStdout: true) captures command output (e.g. git hash). Access via env.NAME or $NAME in the shell. It can be global or per stage.

Milestone and Lock
options {
    disableConcurrentBuilds()
}

stages {
    stage('Deploy') {
        steps {
            // Lock: exclusivity on a resource
            lock('production-server') {
                sh './deploy.sh'
            }

            // Milestone: orders builds
            milestone(1)
            sh './notify.sh'
            milestone(2)
        }
    }
}

# lock requires the "Lockable Resources" plugin
# milestone aborts outdated builds

disableConcurrentBuilds prevents simultaneous builds of the same job. lock() (Lockable Resources plugin) gives exclusivity over a shared resource. milestone() guarantees order and aborts superseded builds. Essential for safe deploys.

Scripted Pipeline
node {
    stage('Checkout') {
        checkout scm
    }
    stage('Test') {
        try {
            sh 'npm test'
        } catch (Exception e) {
            echo 'Tests failed'
            throw e
        }
    }
}

# More flexible (pure Groovy):
# - native if/else, loops, try/catch
# - No fixed structure
# - Harder to maintain
#
# Declarative covers 95% of cases.

The scripted pipeline uses pure Groovy inside node. It is more flexible (native if/else, try/catch) but less readable. Declarative covers most cases. Use script { } inside declarative when you need Groovy logic.

Main Directives
pipeline {
    agent any              // where to run
    options { }            // behavior
    parameters { }         // build inputs
    environment { }        // variables
    tools { }              // tools
    triggers { }           // triggers

    stages {
        stage('X') {
            when { }       // condition
            input { }      // approval
            steps { }      // commands
            post { }       // post-actions
        }
    }

    post { }               // global post-actions
}

Declarative has fixed directives: agent, options, parameters, environment, tools, triggers, stages and post. Inside each stage: when, input, steps, post.

Options
options {
    timeout(time: 30, unit: 'MINUTES')
    disableConcurrentBuilds()
    buildDiscarder(
        logRotator(numToKeepStr: '10'))
    timestamps()
    retry(2)
    skipDefaultCheckout(true)
    ansiColor('xterm')
}

# timeout: aborts after 30 min
# disableConcurrentBuilds: 1 build at a time
# buildDiscarder: keeps only 10 builds
# timestamps: time on each log line
# retry: retries on failure

options defines global behavior. timeout aborts long builds. disableConcurrentBuilds prevents simultaneous runs. buildDiscarder limits stored builds. timestamps and ansiColor improve the logs.

Tools
# Pre-configure in:
# Manage Jenkins > Tools

tools {
    nodejs 'NodeJS-20'
    jdk 'JDK-17'
    maven 'Maven-3.9'
    gradle 'Gradle-8'
}

stages {
    stage('Build') {
        steps {
            // node, java, mvn are on the PATH
            sh 'node --version'
            sh 'mvn clean package'
        }
    }
}

# Jenkins installs/configures
# the tools automatically

tools adds tools to the build PATH. Configure in Manage Jenkins > Tools (name + version). Supports nodejs, jdk, maven, gradle. Jenkins can install them automatically.

Parameters
parameters {
    string(name: 'BRANCH',
        defaultValue: 'main',
        description: 'Branch to build')
    booleanParam(name: 'DEPLOY',
        defaultValue: false)
    choice(name: 'ENV',
        choices: ['dev', 'staging', 'prod'])
    password(name: 'PASSWORD',
        defaultValue: '')
}

stages {
    stage('Deploy') {
        steps {
            echo "Branch: ${params.BRANCH}"
            echo "Environment: ${params.ENV}"
        }
    }
}

parameters creates fields on the build form. Types: string, booleanParam, choice, password. Access via params.NAME. The first build creates the fields; subsequent ones show the form.

Script Block (Groovy)
stage('Logic') {
    steps {
        script {
            def result = sh(
                script: 'cat version.txt',
                returnStdout: true).trim()

            if (result.startsWith('2.')) {
                echo 'Newer version!'
            } else {
                echo 'Old version'
            }

            ['a', 'b', 'c'].each { item ->
                echo "Item: ${item}"
            }
        }
    }
}

# script { } allows full Groovy
# inside the declarative pipeline

script { } opens a Groovy block inside declarative. It allows if/else, loops and variables (def). Essential for complex logic that declarative does not cover. Use sparingly to keep readability.

Stages e Steps


9 cards
Common Steps
steps {
    sh 'bash command'          // Linux/Mac
    bat 'windows command'      // Windows
    powershell 'Get-ChildItem' // PowerShell

    echo "Message in the log"
    sleep(time: 10, unit: 'SECONDS')

    // Capture output:
    def out = sh(
        script: 'ls -la',
        returnStdout: true)

    // Fail on purpose:
    error("Something went wrong")
}

sh runs bash, bat runs Windows CMD, powershell runs PowerShell. echo prints. sh(returnStdout: true) captures output. error() fails the build with a message. sleep pauses.

Artifacts
stage('Build') {
    steps {
        sh 'npm run build'
        // Save build artifacts:
        archiveArtifacts artifacts: 'dist/**',
            fingerprint: true
    }
}

post {
    success {
        archiveArtifacts 'build/*.jar'
    }
}

// In another job, download:
// copyArtifacts projectName: 'job-build',
//     filter: 'dist/**'

// Artifacts appear on the build page

archiveArtifacts saves build files (binaries, reports) in Jenkins. fingerprint: true enables tracking. copyArtifacts (plugin) downloads them in another job. Useful for passing binaries between deploy stages.

Retry and Timeout in Steps
steps {
    // Retry up to 3 times:
    retry(3) {
        sh 'npm install'
    }

    // Timeout only for this step:
    timeout(time: 5, unit: 'MINUTES') {
        sh './slow-tests.sh'
    }

    // Wait for a condition:
    waitUntil {
        def r = sh(script: 'curl -s http://app/health',
            returnStatus: true)
        return r == 0
    }
}

# retry: useful for flaky commands
# waitUntil: polling until success

retry(n) repeats a step up to n times — useful for flaky commands (npm, downloads). timeout limits a specific step. waitUntil polls until the condition is true (e.g. waiting for a service to start).

Checkout and Git
steps {
    // Checkout of the SCM configured in the job:
    checkout scm

    // Explicit Git:
    git branch: 'main',
        url: 'https://github.com/user/repo'

    // With credentials:
    git branch: 'main',
        url: 'git@github.com:user/repo.git',
        credentialsId: 'ssh-key-id'

    // Manual git commands:
    sh 'git log --oneline -5'
    sh 'git rev-parse --short HEAD'
}

checkout scm uses the repository configured in the job (default in Multibranch). git does an explicit checkout with branch and URL. credentialsId authenticates. Manual git commands give the commit hash and history.

Tests and Reports
stage('Tests') {
    steps {
        sh 'npm test -- --ci --reporters=jest-junit'
    }
    post {
        always {
            // Publish JUnit results:
            junit 'reports/junit.xml'

            // Coverage (Coverage plugin):
            publishHTML(target: [
                reportDir: 'coverage/lcov-report',
                reportFiles: 'index.html',
                reportName: 'Coverage'
            ])
        }
    }
}

# junit shows a test chart on the job
# Always in post > always (even on failure)

junit publishes test results (requires JUnit-format XML). It shows charts and trends on the job. Publish in post > always to capture them even when tests fail. publishHTML publishes coverage reports.

Parallel Stages
stage('Tests') {
    parallel {
        stage('Unit') {
            steps { sh 'npm run test:unit' }
        }
        stage('E2E') {
            steps { sh 'npm run test:e2e' }
        }
        stage('Lint') {
            steps { sh 'npm run lint' }
        }
    }
}

# The 3 stages run simultaneously.
# If one fails, the others continue
# (failFast: false by default).

# To abort all on the first error:
# failFast true

parallel runs stages simultaneously — ideal for independent tests. Greatly reduces total time. By default, a failure does not abort the others. failFast true aborts all on the first error. Each parallel stage can have its own agent.

Stash and Unstash
stage('Build') {
    steps {
        sh 'npm run build'
        // Save files for later:
        stash name: 'build-output',
            includes: 'dist/**'
    }
}

stage('Deploy') {
    agent { label 'prod-server' }
    steps {
        // Retrieve on another agent:
        unstash 'build-output'
        sh './deploy.sh'
    }
}

# stash/unstash shares files
# between different stages/agents

stash saves files temporarily; unstash retrieves them. Essential when stages run on different agents (the workspace is not shared). For large or persistent files prefer archiveArtifacts.

Input (manual approval)
stage('Deploy Prod') {
    input {
        message "Deploy to production?"
        ok "Yes, deploy!"
        submitter 'admin,devops'
        parameters {
            string(name: 'NOTE', defaultValue: '')
        }
    }
    steps {
        echo "Note: ${params.NOTE}"
        sh './deploy.sh prod'
    }
}

# The pipeline pauses until someone approves.
# submitter restricts who can approve.

input pauses the pipeline for manual approval — essential before production deploys. submitter restricts who can approve. It can accept additional parameters. The build waits until someone clicks ok.

Workspace and Directories
steps {
    // Current directory (workspace):
    echo "Workspace: ${env.WORKSPACE}"

    // Run in another directory:
    dir('frontend') {
        sh 'npm install'
        sh 'npm run build'
    }

    // Temporary directory:
    ws('/tmp/build-area') {
        sh 'make'
    }

    // Create a file:
    writeFile file: 'version.txt',
        text: "${env.BUILD_NUMBER}"

    // Read a file:
    def content = readFile('version.txt')
}

dir() runs steps in another directory. ws() changes the workspace. writeFile/readFile manipulate files without a shell. env.WORKSPACE is the build root directory. Useful for monorepos with several folders.

Triggers


9 cards
Cron (scheduling)
triggers {
    // Daily build at 2am (weekdays):
    cron('H 2 * * 1-5')

    // Every 15 minutes:
    cron('H/15 * * * *')

    // Sunday at midnight:
    cron('H 0 * * 0')
}

# Format: MINUTE HOUR DAY MONTH WEEKDAY
#
# H = hash — distributes the load
# (avoids all jobs at the same time)
#
# H/15 = every 15 min (variable minute)

cron schedules periodic builds. Format: MINUTE HOUR DAY MONTH WEEKDAY. The H (hash) distributes the load to avoid spikes. H/15 runs every 15 minutes. Ideal for nightly builds and regular tasks.

Jenkins Cron Syntax
# MINUTE HOUR DAY MONTH WEEKDAY

H/15 * * * *    # every 15 min
H 2 * * 1-5     # 2am, Monday to Friday
H 0 * * 0       # Sunday at midnight
H 8 1 * *       # 1st of each month, 8am
H */4 * * *     # every 4 hours
0 12 * * 1      # Monday at noon (exact)

# Fields:
# MINUTE: 0-59   HOUR: 0-23
# DAY: 1-31      MONTH: 1-12
# WEEKDAY: 0-7 (0 and 7 = Sunday)

# H = hash (distributes load)
# * = any value
# 1-5 = range   */4 = step

Jenkins cron syntax has 5 fields: MINUTE HOUR DAY MONTH WEEKDAY. H distributes the load; use 0 for an exact time. Supports ranges (1-5) and steps (*/4). Weekday: 0 and 7 are Sunday.

Disable and Silence
options {
    // No concurrent builds:
    disableConcurrentBuilds()

    // Disable the job (via UI or API):
    // http://jenkins/job/NAME/disable
    // http://jenkins/job/NAME/enable
}

// Silence a stage (no verbose log):
stage('Cleanup') {
    steps {
        sh 'rm -rf cache/'
    }
}

// Pause builds during maintenance:
// Manage Jenkins > "Quiet Down"
// (finishes active builds, starts no new ones)

disableConcurrentBuilds prevents simultaneous builds. Disable jobs via UI or API (/disable). Quiet Down puts Jenkins into maintenance — it finishes active builds and starts no new ones. Useful for server updates.

Poll SCM
triggers {
    // Check for Git changes
    // every 5 minutes:
    pollSCM('H/5 * * * *')
}

# pollSCM does a periodic "git ls-remote".
# It only triggers a build if there are changes.

# Comparison:
# - cron:    always builds (even without changes)
# - pollSCM: builds only on new commits

# pollSCM consumes server resources.
# Prefer webhooks when possible.

pollSCM checks the repository periodically and only triggers a build when there are changes. It uses the same syntax the cron. It consumes resources (polling). Prefer webhooks when the Git server supports them.

Generic Webhook
triggers {
    // GitLab:
    gitlab(triggerOnPush: true,
        triggerOnMergeRequest: true)

    // Bitbucket:
    bitbucketPush()
}

# Generic webhook (any service):
# Job URL + /build:
# http://jenkins/job/MY-JOB/build
#
# With a security token:
# http://jenkins/job/MY-JOB/build?token=SECRET
#
# In the job: Build Triggers >
#   "Trigger builds remotely"
#   Authentication Token: SECRET

For GitLab use gitlab(), for Bitbucket use bitbucketPush(). Any service can trigger via the URL /job/NAME/build with a security token. Enable "Trigger builds remotely" in the job and set the token.

GitHub Webhook
triggers {
    githubPush()
}

# Configuration on GitHub:
# Repo > Settings > Webhooks > Add webhook
# Payload URL:
#   http://jenkins:8080/github-webhook/
# Content type: application/json
# Events: Just the push event

# Jenkins needs the "GitHub" plugin.
# The URL ALWAYS ends in /github-webhook/

# Advantage over pollSCM:
# - Instant (push, not polling)
# - No load on the server

githubPush() triggers the build via webhook. Configure it on GitHub in Settings > Webhooks with the URL http://jenkins/github-webhook/. It is instant (push) and does not consume resources with polling. Requires the GitHub plugin.

Trigger with Parameters
# Trigger with parameters via API:
curl -X POST \
  -u "user:token" \
  "http://jenkins/job/DEPLOY/buildWithParameters?ENV=prod&BRANCH=main"

# In the Jenkinsfile, the parameters
# arrive automatically:
parameters {
    string(name: 'ENV', defaultValue: 'dev')
    string(name: 'BRANCH', defaultValue: 'main')
}

stages {
    stage('Deploy') {
        steps {
            echo "Deploy ${params.ENV} from ${params.BRANCH}"
        }
    }
}

Trigger parameterized builds via buildWithParameters on the API. Values arrive in params.NAME. Useful for integrating with external scripts, chatops and other tools. Combine with a token for security.

Upstream (another job)
triggers {
    // Run when 'parent-job' finishes:
    upstream(
        upstreamProjects: 'parent-job',
        threshold: hudson.model.Result.SUCCESS
    )
}

# threshold: minimum result
# - Result.SUCCESS
# - Result.UNSTABLE
# - Result.FAILURE

# Multiple projects:
# upstreamProjects: 'job-a, job-b'

# Creates job chains:
# build-lib -> build-app -> deploy

upstream triggers the job when another one finishes. threshold defines the minimum result (usually SUCCESS). It accepts multiple projects separated by commas. Allows creating dependency chains between jobs.

Multibranch Triggers
// In a Multibranch Pipeline, triggers
// are configured on the organization/folder,
// not in the Jenkinsfile.

// The Jenkinsfile detects the context:
when {
    branch 'main'           // main only
}

when {
    changeRequest()         // PRs only
}

when {
    branch pattern: 'release/.*',
        comparator: 'REGEXP'
}

// Multibranch creates one job per branch
// and periodically scans the repository
// (or via webhook)

In a Multibranch Pipeline, Jenkins creates one job per branch/PR automatically. The repository scan is configured on the folder. Use when in the Jenkinsfile to control which branches run. changeRequest() detects pull requests.

Agents and Nodes


9 cards
Agent Types
pipeline {
    // Any available node:
    agent any

    // No global node (define per stage):
    agent none

    // Node with a specific label:
    agent { label 'linux' }

    // Inside a Docker container:
    agent {
        docker { image 'node:20' }
    }

    // Specific node by name:
    agent { node { label 'build-server-1' } }
}

agent defines where the pipeline runs. any uses any node. none forces defining per stage. label selects nodes with that label. docker runs inside a container. It is required on the pipeline or on each stage.

Add an SSH Agent
# Manage Jenkins > Nodes > New Node

# Name: build-linux-1
# Type: Permanent Agent

# Configuration:
# - Remote root directory: /home/jenkins
# - Labels: linux docker
# - Launch method:
#   "Launch agents via SSH"
# - Host: 192.168.1.50
# - Credentials: SSH key or user/pass

# Jenkins installs the agent
# automatically via SSH and connects it.

# Check the connection in:
# Manage Jenkins > Nodes > (node) > Log

Add agents in Manage Jenkins > Nodes > New Node. Set the remote root directory, labels and the launch method (SSH for Linux). Jenkins installs and connects the agent automatically. Check the status in the node log.

Dockerfile Agent
// Build and use an image
// from a Dockerfile in the repo:
agent {
    dockerfile {
        filename 'Dockerfile.ci'
        dir 'docker'
        additionalBuildArgs '--build-arg VERSION=2'
    }
}

stages {
    stage('Build') {
        steps {
            // Environment defined in the Dockerfile
            sh 'my-tool --version'
        }
    }
}

// Jenkins builds the image
// and runs the pipeline inside it.

dockerfile builds an image from a Dockerfile in the repository and runs the pipeline inside it. filename and dir locate the file. Allows fully customized and versioned build environments.

Docker Agent
agent {
    docker {
        image 'node:20-alpine'
        args '-v /tmp/cache:/root/.npm'
        reuseNode true
        registryUrl 'https://registry.example.com'
        registryCredentialsId 'registry-creds'
    }
}

stages {
    stage('Build') {
        steps {
            // node/npm are already installed
            sh 'node --version'
            sh 'npm ci'
        }
    }
}

The docker agent runs the build inside a container — no tools installed on the server. args passes options (e.g. volumes for cache). reuseNode reuses the container between stages. registryUrl uses private registries.

Workspace and customWorkspace
agent {
    node {
        label 'linux'
        customWorkspace '/opt/builds/my-project'
    }
}

stages {
    stage('Build') {
        steps {
            // Default workspace:
            // /home/jenkins/workspace/JOB-NAME
            echo "In: ${env.WORKSPACE}"
        }
    }
}

# customWorkspace sets a fixed
# directory instead of the automatic workspace.
# Useful when the path matters
# (e.g. licenses, absolute configs).

The default workspace is ~/workspace/JOB-NAME. customWorkspace sets a fixed directory — useful when the absolute path matters. Requires agent { node { } }. The workspace is shared between stages on the same agent.

Agent per Stage
pipeline {
    agent none

    stages {
        stage('Build Frontend') {
            agent { docker { image 'node:20' } }
            steps { sh 'npm run build' }
        }

        stage('Build Backend') {
            agent { docker { image 'maven:3.9' } }
            steps { sh 'mvn package' }
        }

        stage('Deploy') {
            agent { label 'prod-server' }
            steps { sh './deploy.sh' }
        }
    }
}

With agent none at the top, each stage defines its own agent. This allows different tools per step (Node for frontend, Maven for backend). The deploy can run on a specific server via label.

Executors
# Each node has N executors —
# the number of parallel builds.

# Configure:
# Manage Jenkins > Nodes > (node) > Configure
# "# of executors": 4

# Rules of thumb:
# - Build server: 1-2 per CPU core
# - Heavy builds: fewer executors
# - Light builds: more executors

# If all executors are busy,
# builds queue up (Build Queue).

# Monitor in:
# Manage Jenkins > Nodes
# ("Build Queue" column)

Executors define how many builds run in parallel on a node. Configure per node (1-2 per core is reasonable). Heavy builds call for fewer executors. When all are busy, builds wait in the Build Queue. Monitor in Nodes.

Labels and Nodes
# Configure labels:
# Manage Jenkins > Nodes > (node) > Configure
# Labels: linux docker build

# Label expressions:
agent { label 'linux && docker' }   // both
agent { label 'linux || mac' }      // either
agent { label '!windows' }          // except

# Agents can be:
# - The server itself (built-in node)
# - SSH machines (Linux/Mac)
# - Windows machines (JNLP agent)
# - Kubernetes containers (plugin)

# See nodes in:
# Manage Jenkins > Nodes

Labels tag nodes for selection (linux, docker). Supports expressions: &&, ||, !. Agents can be SSH machines, Windows (JNLP) or Kubernetes pods. Manage in Manage Jenkins > Nodes.

Kubernetes Agents
// Plugin: Kubernetes
// Creates ephemeral pods per build

agent {
    kubernetes {
        yaml '''
            apiVersion: v1
            kind: Pod
            spec:
              containers:
              - name: node
                image: node:20
                command: ['sleep', 'infinity']
        '''
    }
}

stages {
    stage('Build') {
        steps {
            container('node') {
                sh 'npm ci && npm test'
            }
        }
    }
}

The Kubernetes plugin creates ephemeral pods for each build — it scales automatically. Define the pod in YAML with the required containers. container() selects the container. Pods are destroyed after the build. Ideal for large-scale CI.

Variables and Environment


9 cards
Built-in Variables
steps {
    echo "Build: ${env.BUILD_NUMBER}"
    echo "Job: ${env.JOB_NAME}"
    echo "URL: ${env.BUILD_URL}"
    echo "Workspace: ${env.WORKSPACE}"
    echo "Branch: ${env.BRANCH_NAME}"
    echo "Node: ${env.NODE_NAME}"
    echo "Jenkins: ${env.JENKINS_URL}"
}

// Build result (in post/script):
// currentBuild.result
// currentBuild.currentResult

// Full list:
// http://jenkins/pipeline-syntax/globals#env

Jenkins exposes automatic variables in env: BUILD_NUMBER, JOB_NAME, BUILD_URL, WORKSPACE, BRANCH_NAME. currentBuild gives the result. The full list is at pipeline-syntax/globals.

withCredentials
steps {
    // Block that exposes credentials
    // only during execution:
    withCredentials([
        usernamePassword(
            credentialsId: 'docker-creds',
            usernameVariable: 'USER',
            passwordVariable: 'PASS')
    ]) {
        sh 'docker login -u $USER -p $PASS'
    }

    // Secret text:
    withCredentials([
        string(credentialsId: 'api-token',
            variable: 'TOKEN')
    ]) {
        sh 'curl -H "Authorization: $TOKEN" ...'
    }
}

withCredentials exposes credentials only inside the block — safer than a global environment. usernamePassword sets user and pass. string for tokens. The variables are masked in the log and cleared at the end of the block.

.env File and Configs
steps {
    // Load variables from a file:
    script {
        def props = readProperties file: 'build.properties'
        env.APP_VERSION = props['version']
    }

    // Create a configuration file:
    writeFile file: '.env', text: """
APP_ENV=${params.ENV}
DB_HOST=${env.DB_HOST}
VERSION=${env.BUILD_NUMBER}
"""

    sh 'cat .env'

    // Clean up at the end:
    // post { always { sh 'rm -f .env' } }
}

// readProperties reads .properties files

readProperties (Pipeline Utility Steps) reads configuration files. writeFile generates .env files with build values. Remember to clean files with secrets in post > always. A common pattern for configuring applications.

Environment Block
environment {
    APP_ENV = 'production'
    DB_HOST = 'db.example.com'
    VERSION = '1.2.3'

    // Compute from a command:
    GIT_HASH = sh(
        script: 'git rev-parse --short HEAD',
        returnStdout: true).trim()

    // Use a credential:
    DB_PASS = credentials('db-password')
}

stage('Build') {
    environment {
        BUILD_TARGET = 'release'  // this stage only
    }
    steps {
        sh 'echo $APP_ENV $GIT_HASH'
    }
}

environment defines global or per-stage variables. sh(returnStdout: true) captures command output. credentials() injects secrets safely. In the shell access via $NAME; in Groovy via env.NAME.

Dynamic Build Variables
steps {
    script {
        // Define a dynamic variable:
        env.TIMESTAMP = sh(
            script: 'date +%Y%m%d%H%M%S',
            returnStdout: true).trim()

        env.IMAGE_TAG = "${env.BUILD_NUMBER}-${env.GIT_HASH}"

        // Read from a file:
        env.VERSION = readFile('version.txt').trim()
    }

    echo "Tag: ${env.IMAGE_TAG}"
    sh 'docker build -t app:$IMAGE_TAG .'
}

// env.X = ... creates variables at runtime
// (only inside script { })

Inside script { } you can create dynamic variables with env.NAME = value. Useful for timestamps, image tags and computed values. Combine sh(returnStdout) and readFile to get values from commands and files.

Credentials
# Create a credential:
# Manage Jenkins > Credentials >
#   System > Global credentials > Add
# Types: Username/password, Secret text,
#        SSH key, Certificate

environment {
    DOCKER_PASS = credentials('docker-pass')
    API_KEY = credentials('api-key-id')
    SSH = credentials('deploy-ssh-key')
}

steps {
    sh 'docker login -u user -p $DOCKER_PASS'
    sh 'curl -H "X-Key: $API_KEY" https://api'
}

# Jenkins masks the values in the log
# (they appear the ****)

Credentials store secrets safely. Create them in Manage Jenkins > Credentials. Types: username/password, secret text, SSH key. Inject with credentials(id). Jenkins masks the values in the logs automatically.

Masking Secrets
steps {
    // wrap that masks any value:
    wrap([$class: 'MaskPasswordsBuildWrapper']) {
        sh 'echo $MY_PASSWORD'  // appears the ****
    }

    // Best practices:
    // 1. Never echo secrets
    // 2. Use credentials() / withCredentials
    // 3. Do not pass secrets via build
    //    parameters (they stay in the history)
    // 4. Clean up files with secrets:
    sh 'rm -f .env'

    // Secrets in artifacts or logs
    // remain visible forever!
}

Secrets require care: use credentials()/withCredentials (masked automatically). Never echo passwords. Do not pass secrets via build parameters (they stay in the history). Remove .env files before archiving.

Parameters in Steps
parameters {
    string(name: 'BRANCH', defaultValue: 'main')
    choice(name: 'ENV', choices: ['dev', 'prod'])
    booleanParam(name: 'DEPLOY', defaultValue: false)
}

stages {
    stage('Deploy') {
        steps {
            sh "git checkout ${params.BRANCH}"
            sh "./deploy.sh ${params.ENV}"

            script {
                if (params.DEPLOY) {
                    echo 'Deploying...'
                } else {
                    echo 'Deploy disabled'
                }
            }
        }
    }
}

Access parameters via params.NAME. In Groovy strings use ${params.BRANCH}. Booleans work in if. Parameters appear in the "Build with Parameters" form. Validate inputs before using them in commands.

Build Properties
steps {
    script {
        // Current build info:
        echo "Number: ${currentBuild.number}"
        echo "Result: ${currentBuild.currentResult}"
        echo "Duration: ${currentBuild.duration}"
        echo "URL: ${currentBuild.absoluteUrl}"

        // Build cause:
        def cause = currentBuild.getBuildCauses()
        echo "Cause: ${cause}"

        // Set the result manually:
        // currentBuild.result = 'UNSTABLE'

        // Change the build description:
        currentBuild.description = "Deploy ${params.ENV}"
    }
}

currentBuild gives build metadata: number, currentResult, duration, getBuildCauses(). You can set result manually (e.g. UNSTABLE) and change the description. Useful for reports and conditional logic.

Conditions and Post-Actions


9 cards
when (Conditions)
stage('Deploy') {
    when {
        branch 'main'
    }
    steps { sh './deploy.sh' }
}

stage('Deploy PR') {
    when {
        changeRequest()
    }
    steps { echo 'PR build' }
}

stage('Release') {
    when {
        tag 'v*'
    }
    steps { sh './release.sh' }
}

// The stage only runs if the condition
// is true (otherwise it is "skipped")

when runs the stage conditionally. Common conditions: branch, tag, changeRequest() (PRs). If false, the stage shows the "skipped". Essential for pipelines that behave differently per branch.

Slack Notifications
post {
    failure {
        slackSend channel: '#dev',
            color: 'danger',
            message: "FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER} - ${env.BUILD_URL}"
    }
    success {
        slackSend channel: '#dev',
            color: 'good',
            message: "OK: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
    }
}

# Configure:
# Manage Jenkins > System >
#   Slack > Workspace + Token
# The "Slack Notification" plugin is required

slackSend sends messages to Slack. Configure the workspace and token in Manage Jenkins > System. Use color (good/danger) and include BUILD_URL for a direct link. Put it in post to notify per result.

Build Results
steps {
    script {
        // Mark the unstable
        // (does not fail the pipeline):
        currentBuild.result = 'UNSTABLE'

        // Fail with a message:
        // error('Something went wrong')
    }
}

// Meaning of the results:
// SUCCESS  - everything went well
// UNSTABLE - tests failed, but
//            the build "passed"
// FAILURE  - something failed
// ABORTED  - cancelled manually

// unstable is typical when tests
// fail but you want to continue the deploy

Results: SUCCESS, UNSTABLE, FAILURE, ABORTED. UNSTABLE indicates failed tests without blocking — useful to continue the pipeline. Set it with currentBuild.result. error() forces FAILURE.

when with Expressions
stage('Deploy') {
    when {
        expression { params.DEPLOY == true }
    }
    steps { sh './deploy.sh' }
}

stage('Prod') {
    when {
        allOf {
            branch 'main'
            expression { params.ENV == 'prod' }
        }
    }
    steps { sh './deploy-prod.sh' }
}

stage('Tests') {
    when {
        not { branch 'main' }
    }
    steps { sh 'npm test' }
}

// allOf = AND, anyOf = OR, not = negation

expression { } allows arbitrary Groovy conditions. Combine with allOf (AND), anyOf (OR) and not (negation). expression gives total flexibility — compare parameters, variables and any logic.

Email Notifications
post {
    failure {
        emailext(
            subject: "FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
            body: """Build failed.
            See: ${env.BUILD_URL}""",
            to: 'dev@company.com',
            attachLog: true
        )
    }
    success {
        emailext subject: 'Build OK',
            to: 'dev@company.com'
    }
}

# Configure SMTP:
# Manage Jenkins > System > E-mail
# Plugin: "Email Extension"

emailext (Email Extension plugin) sends rich emails. Configure SMTP in Manage Jenkins > System. attachLog attaches the build log. Supports HTML templates, attachments and recipient lists. Notify in post > failure.

when with Changes
// Only run if certain files
// changed since the last build:
stage('Backend') {
    when {
        changeset "src/main/**"
    }
    steps { sh 'mvn package' }
}

stage('Frontend') {
    when {
        changeset "frontend/**"
    }
    steps { sh 'npm run build' }
}

// Compare with a branch:
stage('Deploy') {
    when {
        branch pattern: "release/.*",
            comparator: "REGEXP"
    }
    steps { sh './deploy.sh' }
}

changeset runs the stage only if specific files changed — ideal for monorepos (build only what changed). branch pattern with REGEXP does advanced matching. Reduces build time in large repositories.

Retry and Timeout
options {
    // Global pipeline timeout:
    timeout(time: 1, unit: 'HOURS')
    // Retry the pipeline on failure:
    retry(2)
}

stages {
    stage('Install') {
        steps {
            // Retry only this step:
            retry(3) {
                sh 'npm install'
            }
        }
    }
    stage('Long tests') {
        options {
            timeout(time: 20, unit: 'MINUTES')
        }
        steps { sh 'npm run test:e2e' }
    }
}

timeout aborts builds that exceed the time (global or per stage). retry repeats on failure (useful for flaky steps). Both can be applied in global options, per stage, or the steps. They prevent stuck builds.

Post Actions
pipeline {
    agent any
    stages { /* ... */ }

    post {
        success {
            echo 'Build OK!'
        }
        failure {
            echo 'Build failed!'
        }
        unstable {
            echo 'Tests failed'
        }
        always {
            echo 'Always runs'
            cleanWs()
        }
        aborted {
            echo 'It was cancelled'
        }
    }
}

post defines actions per build result. Conditions: success, failure, unstable, aborted, always. always always runs (ideal for cleanup). Can be global or per stage.

Cleanup (cleanWs)
post {
    always {
        // Clean the workspace:
        cleanWs()

        // Or delete only certain patterns:
        cleanWs(patterns: [
            [pattern: 'node_modules/**', type: 'INCLUDE'],
            [pattern: '.git/**', type: 'EXCLUDE']
        ])
    }
}

// Manual alternative:
// sh 'rm -rf ${env.WORKSPACE}/*'

// cleanWs() frees disk space.
// Recommended on agents with little space.

cleanWs() cleans the workspace after the build — it frees disk space. Put it in post > always. It accepts patterns to include/exclude paths. Essential on agents with limited storage or large builds.

Docker and Advanced


9 cards
Docker Build and Push
stage('Docker') {
    steps {
        script {
            def img = docker.build("myapp:${env.BUILD_NUMBER}")

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

// docker.build() builds the image
// withRegistry authenticates to the registry
// push() sends with a specific tag and latest

docker.build() builds the image. docker.withRegistry() authenticates to a private registry using credentials. img.push() sends it — pushes with the BUILD_NUMBER and with latest. A classic CI pattern for containers.

Complete Pipeline (Example)
pipeline {
    agent { docker { image 'node:20' } }

    options {
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
    }

    stages {
        stage('Install') {
            steps { sh 'npm ci' }
        }
        stage('Test') {
            steps { sh 'npm test' }
            post { always { junit 'reports/*.xml' } }
        }
        stage('Build') {
            steps { sh 'npm run build' }
        }
        stage('Deploy') {
            when { branch 'main' }
            steps { sh './deploy.sh' }
        }
    }

    post {
        failure { slackSend channel: '#dev', color: 'danger',
            message: "Failed: ${env.JOB_NAME}" }
        always { cleanWs() }
    }
}

Complete example: docker agent, timeout and concurrency options, install/test/build/deploy stages, when to deploy only on main, junit for reports, Slack notification and cleanWs. A good starter template.

Best Practices
// 1. Jenkinsfile in the repo (Pipeline the Code)
// 2. Fast builds (< 10 min) — use cache
// 3. Test early (fail fast)
// 4. Parallel stages when possible
// 5. Never store secrets in the Jenkinsfile
// 6. cleanWs() to free space
// 7. timeout on everything (avoids stuck builds)
// 8. Notify only on failures (avoids noise)
// 9. Use docker agents (clean environments)
// 10. Version and review the Jenkinsfile

// Dependency cache:
// agent { docker {
//   image 'node:20'
//   args '-v cache-npm:/root/.npm'
// }}

Best practices: Pipeline the Code, fast builds with cache, test early (fail fast), parallel stages, never store secrets in the Jenkinsfile, cleanWs(), timeout on everything, notify only on failures, docker agents for clean environments.

Docker Compose
stage('Integration tests') {
    steps {
        // Bring up the dependency stack:
        sh 'docker compose up -d db redis'

        // Wait for the DB to be ready:
        sh 'sleep 10'

        // Run tests:
        sh 'docker compose exec -T web npm test'

        // Clean up:
        sh 'docker compose down -v'
    }
}

// -T in exec (no TTY, required in CI)
// down -v removes volumes
// Always clean up in post > always

docker compose brings up dependencies (DB, Redis) for integration tests. Use -T in exec (no TTY in CI). Clean up with down -v in post > always. Allows realistic tests without installing services on the agent.

Debug and Logs
steps {
    // See environment variables:
    sh 'printenv'

    // Check tools:
    sh 'node --version && npm --version'
    sh 'which docker'

    // Colored logs (AnsiColor plugin):
    // options { ansiColor('xterm') }

    // See the generated pipeline:
    // Job > "Pipeline Syntax" >
    //   "Declarative Directive Generator"

    // Replay: edit and re-run
    // a failed build (Job > Build > Replay)

    // Validate the Jenkinsfile:
    // Job > "Pipeline Syntax" >
    //   "Validate Pipeline"
}

For debugging: printenv shows variables, check tool versions. Replay lets you edit and re-run a failed build. The Declarative Directive Generator generates snippets. Validate the Jenkinsfile before committing.

Shared Libraries
// Lib repo: vars/deploy.groovy
def call(String environment) {
    echo "Deploy to ${environment}"
    sh "./deploy.sh ${environment}"
}

// Jenkinsfile:
@Library('my-lib') _

pipeline {
    agent any
    stages {
        stage('Deploy') {
            steps {
                deploy('production')
            }
        }
    }
}

// Configure:
// Manage Jenkins > System >
//   Global Pipeline Libraries

Shared Libraries allow reusing code across pipelines. Create vars/name.groovy files in a separate repo. Import with @Library and call the a function. Configure in Global Pipeline Libraries. Eliminates duplicated deploy logic.

Pipeline Security
// 1. Review Jenkinsfile changes (PR)
//    before merge — it is code!

// 2. Protect branches:
//    properties([
//      [$class: 'BranchProtectionProperty']
//    ])

// 3. Do not run code from PRs
//    without a sandbox:
//    Manage Jenkins > Security >
//    "Script Security"

// 4. Credentials with limited scope:
//    (Job-specific instead of Global)

// 5. Principle of least privilege:
//    build agents should not have
//    access to production

// 6. Audit with the Audit Trail plugin

Security: treat the Jenkinsfile the code (review it in PRs). Use Script Security to sandbox untrusted code. Credentials scoped per job. Build agents should not access production. Audit with Audit Trail.

Multibranch Pipeline
# New Item > Multibranch Pipeline

# Branch Sources: GitHub/GitLab/Bitbucket
#   (with credentials)

# Jenkins:
# 1. Scans the repository
# 2. Creates one job per branch with a Jenkinsfile
# 3. Creates jobs for Pull Requests
# 4. Removes jobs of deleted branches

# In the Jenkinsfile, detect the context:
when { branch 'main' }        # main only
when { changeRequest() }      # PRs only
when { tag 'v*' }             # tags only

# Automatic scan + webhooks

Multibranch Pipeline automatically creates one job per branch and PR that has a Jenkinsfile. It scans periodically and reacts to webhooks. Removes jobs of deleted branches. Use when to differentiate behavior. The modern CI pattern.

Jenkins Backup
# Everything is in JENKINS_HOME:
# - Docker: jenkins_home volume
# - Linux: /var/lib/jenkins

# Simple backup (stop the service):
sudo systemctl stop jenkins
tar -czf jenkins-backup-$(date +%F).tar.gz \
  /var/lib/jenkins
sudo systemctl start jenkins

# Or copy only the essentials:
# - jobs/ (job configurations)
# - config.xml (global config)
# - secrets/ and credentials.xml
# - users/

# Plugins: list in
# Manage Jenkins > Plugins > Installed

# Restore: extract into JENKINS_HOME
# and restart the service

All state lives in JENKINS_HOME (/var/lib/jenkins). Back up with the service stopped (tar of the directory). The essentials: jobs/, config.xml, credentials.xml, secrets/. Restore by extracting and restarting.

Installation and Setup


9 cards
Install with Docker
# Simplest way — Docker:
docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts

# Access:
# http://localhost:8080

# Get the initial password:
docker exec jenkins \
  cat /var/jenkins_home/secrets/initialAdminPassword

# The jenkins_home volume persists
# all configuration and jobs

The easiest way is via Docker with the jenkins/jenkins:lts image. Port 8080 is the web interface and 50000 is for agents. The jenkins_home volume persists everything. The initial password is in secrets/initialAdminPassword.

Jenkinsfile in the Repository
# Best practice: Pipeline the Code
# A "Jenkinsfile" file at the repo root

# In the Jenkins job:
# Definition: Pipeline script from SCM
# SCM: Git
# Repository URL: https://github.com/user/repo
# Script Path: Jenkinsfile

# Advantages:
# - Versioned with the code
# - Reviewed in pull requests
# - Change history
# - Single source of truth

# Jenkins checks out automatically
# and runs the branch's Jenkinsfile

The best practice is Pipeline the Code — keep the pipeline in a Jenkinsfile at the repository root. In the job use Pipeline script from SCM. It is versioned, reviewed in PRs and is the single source of truth. Jenkins checks it out automatically.

Structure of a Job
# Anatomy of a Pipeline job:
#
# General:
#   - Description, GitHub project
# Build Triggers:
#   - When to run (cron, webhook)
# Pipeline:
#   - Definition, Script/SCM
# Post-build Actions:
#   - Notifications, artifacts

# Item types:
# - Freestyle project (legacy, simple)
# - Pipeline (recommended)
# - Multibranch Pipeline (per branch)
# - Folder (organize jobs)
# - Organization Folder (GitHub org)

# Organize jobs in Folders per team

A job has sections: General, Build Triggers, Pipeline and Post-build. Prefer Pipeline over Freestyle. Multibranch creates jobs per branch automatically. Organize with Folders per team or project.

Install on Linux
# Ubuntu/Debian:
# 1. Install Java (required):
sudo apt install openjdk-17-jre

# 2. Add the Jenkins repository:
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key \
  | sudo tee /usr/share/keyrings/jenkins-keyring.asc

echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian-stable binary/" \
  | sudo tee /etc/apt/sources.list.d/jenkins.list

# 3. Install:
sudo apt update
sudo apt install jenkins

# 4. Start:
sudo systemctl start jenkins
sudo systemctl enable jenkins

Jenkins requires Java (JRE 17+). Add the official repository with the GPG key. systemctl manages the service. Configuration lives in /var/lib/jenkins. Prefer the debian-stable channel (LTS).

Manage Plugins
# Manage Jenkins > Plugins

# Tabs:
# - Updates: available updates
# - Available: install new ones
# - Installed: manage existing

# Popular plugins:
# - Git, GitHub, GitLab
# - Docker Pipeline
# - Pipeline Utility Steps
# - Slack Notification
# - Email Extension
# - JUnit, Coverage (tests)
# - AnsiColor (colored logs)

# Manage Jenkins > Plugins > Available
# (a restart may be required)

Plugins extend Jenkins. Manage them in Manage Jenkins > Plugins. Essentials: Git, Docker Pipeline, Pipeline Utility Steps. For notifications: Slack and Email Extension. Keep plugins updated for security.

Initial Setup (wizard)
# 1. Go to http://localhost:8080
# 2. Enter the initial password:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

# 3. Install suggested plugins
#    (or select manually)

# 4. Create an admin user

# 5. Configure the Jenkins URL

# Essential post-install plugins:
# - Pipeline
# - Git
# - Docker Pipeline
# - Credentials Binding
# - Blue Ocean (modern UI, optional)

The initial wizard asks for the initialAdminPassword. Install the suggested plugins. Create an admin user instead of using the default admin. The Pipeline, Git and Docker Pipeline plugins are essential for CI/CD.

Users and Security
# Manage Jenkins > Security

# Realm (authentication):
# - Jenkins users (internal)
# - LDAP / Active Directory
# - GitHub OAuth

# Authorization (permissions):
# - Logged-in users can do anything
# - Matrix-based security (recommended)
# - Project-based Matrix

# Create a user:
# Manage Jenkins > Users > Create User

# Best practices:
# - Never use admin for builds
# - Create API tokens instead of passwords
# - Audit with the Audit Trail plugin

Configure security in Manage Jenkins > Security. The Realm defines authentication (internal, LDAP, OAuth). Matrix-based security gives fine-grained permission control. Use API tokens instead of passwords for automation.

Create Your First Pipeline
# 1. New Item > Pipeline > OK
# 2. Under "Pipeline":
#    Definition: Pipeline script
# 3. Paste into the editor:

pipeline {
    agent any
    stages {
        stage('Hello') {
            steps {
                echo 'Hello, Jenkins!'
            }
        }
    }
}

# 4. Save > Build Now
# 5. See output in "Console Output"

Create a Pipeline in New Item. Start with a Pipeline script pasted into the editor. agent any runs on any node. echo prints to the log. Build Now runs it and Console Output shows the result.

API Tokens
# Generate a token:
# User > Configure > API Token > Add new Token

# Trigger a build via API:
curl -X POST \
  -u "user:API_TOKEN" \
  "http://jenkins:8080/job/MY-JOB/build"

# With parameters:
curl -X POST \
  -u "user:API_TOKEN" \
  "http://jenkins:8080/job/MY-JOB/buildWithParameters?BRANCH=main"

# Check build status (JSON):
curl -u "user:API_TOKEN" \
  "http://jenkins:8080/job/MY-JOB/lastBuild/api/json"

API tokens replace passwords in automation. Generate them per user in Configure. Trigger builds with POST /job/NAME/build. buildWithParameters accepts parameters. The API returns JSON with the build status.