Home/Engineer/Git / CI·CD/Jenkins Pipeline Basics — Bu…
Git / CI·CDIntermediatelinuxjenkinscicdpipeline

Jenkins Pipeline Basics — Building CI/CD with Declarative Pipeline

This guide explains Declarative Pipeline syntax with a Jenkinsfile from the ground up. It covers the agent, stages, and steps structure, environment variables, parallel builds, post blocks, credential management, and shared libraries so you

Contents
  1. What Is a Jenkins Pipeline?
  2. Prerequisites
  3. The Most Basic Jenkinsfile
  4. agent — Specifying the Execution Environment
  5. environment — Environment Variables
  6. Using Credentials Safely
  7. Parallel Execution — parallel
  8. Conditional Execution — when
  9. post — After the Build
  10. options & triggers
  11. Complete Example — Building a Node App and Deploying with Docker
  12. Shared Libraries
  13. Troubleshooting
  14. Summary

What Is a Jenkins Pipeline?

Jenkins Pipeline lets you define the build, test, and deploy process as code (a Jenkinsfile). Unlike Freestyle jobs that you assemble by clicking in the web UI, you write the pipeline as code and keep it in Git with the rest of the project, so you get version control, code review, and reproducibility. This is called "Pipeline as Code."

There are two ways to write a pipeline.

StyleCharacteristics
DeclarativeStructured syntax that starts with a pipeline { } block. More readable and easier to validate—this is the recommended approach
ScriptedGroovy-based. More flexible, but more complex and a steeper learning curve

This guide covers Declarative Pipeline, the production standard.


Prerequisites

Jenkins should already be installed, and you need the following plugins.

  • Pipeline (included by default)
  • Git / GitHub Branch Source
  • Credentials Binding (for injecting credentials)

When you create a new job, choose the "Pipeline" or "Multibranch Pipeline" type and set the definition to Pipeline script from SCM. Jenkins will then read the Jenkinsfile at the repository root.


The Most Basic Jenkinsfile

GROOVY
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
 echo ' when'
                sh 'make build'
            }
        }
        stage('Test') {
            steps {
                sh 'make test'
            }
        }
        stage('Deploy') {
            steps {
                sh 'make deploy'
            }
        }
    }
}

The core structure looks like this.

BlockRole
pipelineTop-level block that wraps the entire pipeline (required)
agentSpecifies which node/environment to run on
stagesContainer that holds one or more stages
stageA logical step (Build, Test, Deploy, etc.). Visualized in the UI
stepsThe commands that actually run inside a stage

agent — Specifying the Execution Environment

agent decides where the pipeline (or an individual stage) will run.

GROOVY
// 1)
agent any

// 2)
agent { label 'linux && docker' }

// 3) Docker run
agent {
    docker {
        image 'node:20-alpine'
        args  '-v /tmp:/tmp'
    }
}

// 4) none, stage set
agent none

Using agent { docker { ... } } runs each build in a clean container, which reduces "it worked on my machine" problems. The agent node must have Docker installed.


environment — Environment Variables

Declare variables in an environment block. You can scope them to the whole pipeline or to a specific stage.

GROOVY
pipeline {
    agent any
    environment {
        APP_NAME   = 'my-service'
 BUILD_TAG = "v1.${BUILD_NUMBER}" //
        REGISTRY   = 'registry.example.com'
    }
    stages {
        stage('Info') {
            steps {
 sh 'echo ": $APP_NAME:$BUILD_TAG"'
            }
        }
    }
}

Built-in environment variables you will use often:

VariableMeaning
BUILD_NUMBERSequential build number
BUILD_IDBuild ID
JOB_NAMEJob name
WORKSPACEAbsolute path of the workspace
GIT_COMMITHash of the checked-out commit
BRANCH_NAME(Multibranch) Branch name

Using Credentials Safely

Never put passwords or tokens in a Jenkinsfile in plaintext. Register them under Manage Jenkins → Credentials and reference them by ID.

GROOVY
environment {
 // Secret Text
    DOCKER_TOKEN = credentials('docker-registry-token')
}

For Username/Password credentials, wrapping them explicitly with withCredentials is the safer approach.

GROOVY
steps {
    withCredentials([usernamePassword(
        credentialsId: 'docker-login',
        usernameVariable: 'DOCKER_USER',
        passwordVariable: 'DOCKER_PASS')]) {
        sh 'echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin $REGISTRY'
    }
}

Even if credentials show up in logs, Jenkins automatically masks them as ****. Still, never write code that prints them directly, such as echo "$DOCKER_PASS".


Parallel Execution — parallel

Independent work (for example, a test matrix across OSes or versions) can run at the same time with parallel to cut wall-clock time.

GROOVY
stage('Test Matrix') {
    parallel {
        stage('Unit') {
            steps { sh 'make test-unit' }
        }
        stage('Integration') {
            steps { sh 'make test-integration' }
        }
        stage('Lint') {
            steps { sh 'make lint' }
        }
    }
}

Conditional Execution — when

Run a stage only when a condition is met. Deploys usually run only on the main branch.

GROOVY
stage('Deploy') {
    when {
        branch 'main'
    }
    steps {
        sh 'make deploy'
    }
}

when supports many conditions, including branch, environment name: 'X', value: 'Y', expression { ... }, and changeset.


post — After the Build

Run cleanup or send notifications regardless of whether the build succeeded or failed.

GROOVY
post {
    always {
 junit 'reports/**/*.xml' //
 cleanWs //
    }
    success {
 echo '✅ '
    }
    failure {
        mail to: 'team@example.com',
 subject: "failed: ${JOB_NAME} #${BUILD_NUMBER}",
 body: " check: ${BUILD_URL}"
    }
}
ConditionWhen it runs
alwaysAlways, regardless of the result
successOnly on success
failureOnly on failure
unstableWhen the build is unstable (e.g. test failures)
changedWhen the result differs from the previous build

options & triggers

GROOVY
options {
 timeout(time: 30, unit: 'MINUTES') // 30 when
 retry(2) // failed when 2 when
 disableConcurrentBuilds // when
 timestamps //
}
triggers {
 cron('H 2 * * *') // 2when
 pollSCM('H/15 * * * *') // 15
}

The H in cron('H 2 * * *') is a hash. Jenkins automatically assigns a spread-out time per job so they do not all fire at once. Prefer this over a fixed minute (0) in a regular cron expression.


Complete Example — Building a Node App and Deploying with Docker

GROOVY
pipeline {
    agent { label 'docker' }

    environment {
        REGISTRY  = 'registry.example.com'
        IMAGE     = "${REGISTRY}/web-app"
        TAG       = "${GIT_COMMIT.take(8)}"
    }
    options {
        timeout(time: 20, unit: 'MINUTES')
        disableConcurrentBuilds()
    }
    stages {
        stage('Checkout') {
            steps { checkout scm }
        }
        stage('Install & Test') {
            agent { docker { image 'node:20-alpine'; reuseNode true } }
            steps {
                sh 'npm ci'
                sh 'npm test'
            }
        }
        stage('Build Image') {
            steps {
                sh "docker build -t ${IMAGE}:${TAG} ."
            }
        }
        stage('Push') {
            when { branch 'main' }
            steps {
                withCredentials([usernamePassword(
                    credentialsId: 'docker-login',
                    usernameVariable: 'U', passwordVariable: 'P')]) {
                    sh 'echo "$P" | docker login -u "$U" --password-stdin $REGISTRY'
                    sh "docker push ${IMAGE}:${TAG}"
                }
            }
        }
    }
    post {
        always  { cleanWs() }
 failure { echo ' failed — check ' }
    }
}

Shared Libraries

If several projects share the same build logic, put a shared library in a separate Git repository and reuse it.

GROOVY
@Library('my-shared-lib') _

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
 buildApp(lang: 'node') // vars/buildApp.groovy call
            }
        }
    }
}

The library repository is organized as vars/ (global functions), src/ (Groovy classes), and resources/ (static files). Register it under Manage Jenkins → System → Global Pipeline Libraries.


Troubleshooting

SymptomCause / Fix
Permission error in an sh stepGrant execute permission to the agent user; add the user to the Docker group
Pipeline appears stuckIt may be waiting on an input approval. Wrap it with timeout
Credentials come through emptyCheck for a typo in credentialsId or a folder-scope mismatch
Workspace missing on a Docker agentAdd the reuseNode true option

Catch syntax errors before you waste a build by using Pipeline Syntax → Declarative Directive Generator or jenkins-cli declarative-linter.


Summary

TopicKey takeaway
Definition stylePrefer Declarative Pipeline (pipeline { })
Required structureagent → stages → stage → steps
Environment variablesenvironment { }, built-ins BUILD_NUMBER/GIT_COMMIT
SecretsInject safely with credentials() and withCredentials
SpeedRun independent work concurrently with parallel
Conditionalswhen { branch 'main' }
Post-processingpost { always / success / failure }
ReuseShare build logic with a Shared Library

Declarative Pipeline is the starting point for defining CI/CD as code so you get reproducibility and easier collaboration. Start from the complete example above and adjust the stages to fit your project.

#jenkins#cicd#pipeline#jenkinsfile#automation
Editorial note

AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us

Topic-related official docs (auto-linked · not per-claim sources)Git documentation ↗

Questions & answers

Ask a question about this guide. We'll review and reply.