Jenkins Pipeline â Declarative & Scripted Pipeline Guide
In this tutorial, you'll learn about Jenkins Pipeline. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Jenkins Pipeline is a suite of plugins that supports implementing and integrating continuous delivery pipelines into Jenkins, using a Groovy-based DSL to define build, test, and deploy stages as code.
What You'll Learn
Why It Matters
Manual build and deployment processes are slow, error-prone, and untraceable. Jenkins Pipeline gives you a single source of truth for your entire delivery process â version-controlled in your repository, automatically triggered on code changes, and visible through an intuitive Blue Ocean interface. DodaTech uses Jenkins Pipeline to orchestrate builds across 40+ Microservices, running 300+ daily pipeline executions with zero manual intervention.
Real-World Use
Durga Antivirus Pro's CI pipeline compiles kernel modules across 6 operating system versions, runs 15,000+ unit and integration tests, performs Static Analysis with SonarQube, packages installers for Windows, macOS, and Linux, and signs binaries â all defined in a single Jenkinsfile.
flowchart LR
A[Code Push] --> B[SCM Poll / Webhook]
B --> C[Jenkinsfile Declared]
C --> D[Stage: Build]
D --> E[Stage: Test]
E --> F[Stage: Static Analysis]
F --> G[Stage: Package]
G --> H[Stage: Deploy to Staging]
H --> I[Stage: Approval Gate]
I --> J[Stage: Production Deploy]
style C fill:#D24939,color:#fff
Prerequisites: Basic Groovy or Java syntax familiarity. A running Jenkins instance with Pipeline plugin installed.
Pipeline Types
Declarative Pipeline
Declarative syntax provides a simpler, structured way to define pipelines with a predefined pipeline block:
// Jenkinsfile â Declarative Pipeline
pipeline {
agent any
tools {
maven 'Maven-3.9'
jdk 'JDK-21'
}
environment {
DOCKER_REGISTRY = 'registry.dodatech.com'
APP_NAME = 'user-service'
}
parameters {
string name: 'DEPLOY_ENV', defaultValue: 'staging', description: 'Target environment'
}
triggers {
pollSCM('H/5 * * * *')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile -q'
}
post {
failure {
slackSend color: '#CC0000', message: "Build failed: ${env.BUILD_URL}"
}
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test -q'
}
}
stage('Integration Tests') {
steps {
sh 'mvn verify -q -Pintegration'
}
}
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('Package') {
when {
branch 'main'
}
steps {
sh 'mvn package -DskipTests'
sh "docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} ."
}
}
stage('Deploy') {
input {
message "Deploy ${APP_NAME} to ${params.DEPLOY_ENV}?"
ok "Deploy"
}
steps {
sh "kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} -n ${params.DEPLOY_ENV}"
}
}
}
post {
always {
cleanWs()
}
success {
emailext(
subject: "Pipeline succeeded: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
to: 'team@dodatech.com',
body: "The pipeline completed successfully. See ${env.BUILD_URL}"
)
}
failure {
emailext(
subject: "Pipeline failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
to: 'team@dodatech.com',
body: "The pipeline failed. See ${env.BUILD_URL}"
)
}
}
}
Expected behavior: On every push to main, Jenkins checks out the code, runs unit and integration tests in parallel, packages the application, waits for manual approval, then deploys to the target environment.
Scripted Pipeline
Scripted syntax gives full control with a more imperative Groovy style:
// Jenkinsfile â Scripted Pipeline
def buildApp() {
sh 'mvn clean compile'
}
def testApp() {
sh 'mvn test'
}
def packageApp() {
sh 'mvn package -DskipTests'
}
node('docker-agent') {
try {
stage('Checkout') {
checkout scm
}
stage('Build') {
buildApp()
}
stage('Test') {
testApp()
}
stage('Package') {
packageApp()
}
stage('Deploy') {
if (env.BRANCH_NAME == 'main') {
input "Deploy to production?"
sh 'kubectl apply -f k8s/deployment.yaml'
}
}
} catch (Exception e) {
currentBuild.result = 'FAILURE'
echo "Pipeline failed: ${e.message}"
error e.message
} finally {
cleanWs()
}
}
Multi-Branch Pipeline
Automatically creates pipelines for each branch in your repository:
pipeline {
agent any
triggers {
// Build on push events
eventTrigger {
events([
push(),
pullRequest {
actions(['opened', 'synchronize'])
}
])
}
}
stages {
stage('Build') {
steps {
script {
// Branch-specific build logic
if (env.BRANCH_NAME == 'main') {
sh 'mvn package -Pproduction'
} else if (env.BRANCH_NAME.startsWith('feature/')) {
sh 'mvn package -Ptest'
}
}
}
}
}
}
Pipeline Global Variables
Share data across stages using the env object or custom variables:
pipeline {
agent any
environment {
VERSION = readFile('version.txt').trim()
}
stages {
stage('Set Version') {
steps {
script {
env.GIT_COMMIT_HASH = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
echo "Building version: ${env.VERSION}-${env.GIT_COMMIT_HASH}"
}
}
}
stage('Tag Release') {
when {
branch 'main'
}
steps {
sh "git tag v${env.VERSION}-${env.BUILD_NUMBER}"
sh 'git push origin --tags'
}
}
}
}
Common Configuration Mistakes
Not using
whenconditions correctly: Withoutwhendirectives, all stages run on every branch. Usewhen { branch 'main' }to restrict deployment stages.Storing secrets in Jenkinsfile: Credentials should use Jenkins credential store with
credentialsIdreferences, never plain text.Missing
parallelboundaries: Test stages that don't useparallelrun sequentially, doubling pipeline duration. Wrap independent test suites inparallel {}.Using
inputwithout timeout: Aninputstep without a timeout can block the pipeline indefinitely. Always addtimeoutparameter:time(time: 1, unit: 'DAYS').Ignoring
checkout scmin shared library pipelines: When using shared libraries, the pipeline must explicitly check out the source code withcheckout scm.
Practice Questions
What is the difference between Declarative and Scripted Pipeline syntax? Answer: Declarative is a structured syntax with a predefined
pipelineblock, simpler for most use cases. Scripted is a more flexible, imperative Groovy-based syntax suitable for complex logic.How does the
whendirective control stage execution? Answer: Thewhendirective evaluates conditions before a stage runs. It supportsbranch,environment,expression,not,allOf, andanyOfconditions.What is the purpose of
postin Declarative Pipeline? Answer: Thepostsection defines actions that run after the pipeline or stage completes, with conditions likealways,success,failure,unstable, andchanged.How do you pass parameters to a pipeline run? Answer: Define a
parametersblock in Declarative or useproperties([parameters(...)])in Scripted. Parameters are accessible viaparams.PARAM_NAME.
Challenge
Create a Declarative Pipeline that builds a Node.js application, runs linting and unit tests in parallel, performs container scanning with Trivy, publishes the Docker image to a private registry, deploys to Kubernetes staging with canary rollout, runs smoke tests, and promotes to production on approval.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro