Jenkins Declarative Pipeline Syntax
Declarative Pipeline is the standard way to write Jenkins Pipelines. Its structured syntax makes pipelines readable, maintainable, and easy to validate. This topic covers every section of Declarative Pipeline in detail with practical examples.
The Top-Level Structure
Every Declarative Pipeline follows the same skeleton. Each block has a specific role and appears in a fixed order.
pipeline {
agent { ... } // WHERE to run
environment { ... } // VARIABLES available to all stages
options { ... } // SETTINGS for the pipeline
parameters { ... } // USER INPUTS before build starts
triggers { ... } // WHAT STARTS the pipeline
stages { // THE WORK to perform
stage('name') {
agent { ... } // Optional: override agent for this stage
steps { ... } // Commands to run
post { ... } // What to do after this stage
}
}
post { ... } // What to do after ALL stages finish
}
The agent Block
The agent block tells Jenkins where to run the pipeline or a specific stage. It is required at the top level.
// Run on any available Jenkins node
agent any
// Run on a specific labeled node
agent { label 'linux-builder' }
// Run inside a Docker container
agent {
docker {
image 'maven:3.9-eclipse-temurin-17'
args '-v /root/.m2:/root/.m2'
}
}
// No default agent (each stage must define its own)
agent none
The environment Block
The environment block defines variables available throughout the pipeline. Variables set here are accessible in every stage's steps.
environment {
APP_NAME = 'my-web-app'
BUILD_ENV = 'production'
DEPLOY_DIR = '/opt/apps/myapp'
DOCKER_TAG = "${env.BUILD_NUMBER}" // Use Jenkins built-in variable
}
// Access in steps:
steps {
sh "echo Deploying ${APP_NAME} to ${DEPLOY_DIR}"
sh "docker build -t myimage:${DOCKER_TAG} ."
}
The options Block
Options configure pipeline-level settings that control behavior.
options {
// Fail if pipeline runs longer than 30 minutes
timeout(time: 30, unit: 'MINUTES')
// Keep only last 10 build records
buildDiscarder(logRotator(numToKeepStr: '10'))
// Do not run two instances of this pipeline at the same time
disableConcurrentBuilds()
// Show timestamps in console output
timestamps()
// Retry the entire pipeline 2 times on failure
retry(2)
}
The stages and stage Blocks
Stages organize your pipeline into logical phases. Each stage has a name and a steps block containing the commands to run.
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/myteam/myapp.git',
credentialsId: 'github-token'
}
}
stage('Build') {
steps {
sh 'mvn clean compile -q'
echo 'Compilation complete'
}
}
stage('Unit Tests') {
steps {
sh 'mvn test'
}
}
stage('Package') {
steps {
sh 'mvn package -DskipTests'
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
stage('Deploy to Staging') {
steps {
sh 'scp target/*.jar deploy@staging-server:/opt/apps/'
sh 'ssh deploy@staging-server "systemctl restart myapp"'
}
}
}
Parallel Stages
Run multiple stages at the same time to speed up your pipeline. If unit tests and static analysis take 10 minutes each, running them in parallel cuts the wait time to 10 minutes total instead of 20.
stage('Parallel Testing') {
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test'
}
}
stage('Static Analysis') {
steps {
sh 'mvn checkstyle:check'
}
}
stage('Security Scan') {
steps {
sh 'dependency-check.sh --project myapp'
}
}
}
}
Timeline with parallel stages:
Without parallel:
Build: 5min → Unit Tests: 10min → Analysis: 10min → Total: 25min
With parallel:
Build: 5min → [Unit Tests: 10min] + [Analysis: 10min] → Total: 15min
(runs simultaneously)
Conditional Execution with when
The when block runs a stage only when a condition is true. Use this to deploy only on the main branch, or to skip stages for pull requests.
stage('Deploy to Production') {
when {
branch 'main' // Only run when on the main branch
}
steps {
sh 'deploy-production.sh'
}
}
stage('Deploy to Staging') {
when {
anyOf {
branch 'develop'
branch 'release/*'
}
}
steps {
sh 'deploy-staging.sh'
}
}
stage('Skip on Weekends') {
when {
expression { return new Date().getDay() in [1,2,3,4,5] }
}
steps {
sh 'run-report.sh'
}
}
The post Block
The post block runs steps after all stages complete. It contains conditions that define what to do based on the build result.
post {
always {
// Runs regardless of success or failure
junit 'target/surefire-reports/*.xml'
cleanWs() // Clean the workspace
}
success {
// Runs only when all stages passed
slackSend channel: '#builds',
message: "Build PASSED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
}
failure {
// Runs only when any stage failed
mail to: 'team@company.com',
subject: "FAILED: ${env.JOB_NAME}",
body: "See details: ${env.BUILD_URL}"
}
unstable {
// Runs when build is unstable (e.g., some tests failed)
slackSend channel: '#builds',
message: "Build UNSTABLE — tests failing"
}
changed {
// Runs when build result is different from the previous build
echo 'Build status has changed from previous run'
}
}
A Complete Declarative Pipeline Example
pipeline {
agent any
environment {
APP_NAME = 'order-service'
DOCKER_IMAGE = "myregistry/${APP_NAME}:${env.BUILD_NUMBER}"
}
options {
timeout(time: 45, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
disableConcurrentBuilds()
}
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
steps { sh 'mvn test' }
}
stage('Code Quality') {
steps { sh 'mvn checkstyle:check' }
}
}
}
stage('Docker Build') {
steps {
sh "docker build -t ${DOCKER_IMAGE} ."
}
}
stage('Deploy') {
when { branch 'main' }
steps {
sh "docker push ${DOCKER_IMAGE}"
sh "kubectl set image deployment/${APP_NAME} app=${DOCKER_IMAGE}"
}
}
}
post {
always {
junit 'target/surefire-reports/*.xml'
cleanWs()
}
failure {
mail to: 'devteam@company.com',
subject: "Pipeline failed: ${env.JOB_NAME}",
body: "Check: ${env.BUILD_URL}"
}
}
}
Key Points
- Declarative Pipeline uses a strict structure: pipeline → agent → stages → stage → steps.
- The environment block defines variables available to all stages.
- Use the options block to set timeouts, disable concurrent builds, and configure logs.
- Parallel stages run simultaneously to speed up the pipeline.
- The when block makes stages conditional — run only on specific branches or conditions.
- The post block handles what happens after all stages: notifications, cleanup, and reporting.
