Jenkins Pipeline — Declarative CI/CD Syntax Complete Guide

Sanjeev SharmaSanjeev Sharma
4 min read

Advertisement

Introduction

Why This Matters

Jenkins remains one of the most widely deployed CI/CD platforms in enterprise environments. Declarative pipeline syntax — introduced to replace scripted pipelines — gives teams a structured, readable format that lives in version control alongside application code. Mastering Jenkinsfile authoring means your build process is reproducible, reviewable, and recoverable when things go wrong.

Declarative Pipeline Structure

Every declarative pipeline follows a required top-level structure:

pipeline {
  agent any
 
  environment {
    APP_NAME = 'my-service'
    REGISTRY = 'registry.example.com'
  }
 
  options {
    timeout(time: 1, unit: 'HOURS')
    buildDiscarder(logRotator(numToKeepStr: '10'))
    disableConcurrentBuilds()
  }
 
  stages {
    stage('Checkout') {
      steps {
        checkout scm
      }
    }
 
    stage('Build') {
      steps {
        sh 'npm ci'
        sh 'npm run build'
      }
    }
 
    stage('Test') {
      steps {
        sh 'npm test -- --reporter=junit'
        junit 'test-results.xml'
      }
    }
 
    stage('Deploy') {
      when {
        branch 'main'
      }
      steps {
        sh 'npm run deploy'
      }
    }
  }
 
  post {
    always {
      cleanWs()
    }
    failure {
      emailext(
        subject: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
        body: "Check console: ${env.BUILD_URL}",
        to: 'team@example.com'
      )
    }
    success {
      slackSend(color: 'good', message: "Build passed: ${env.JOB_NAME}")
    }
  }
}

Agent Configuration

Agents define where a pipeline or stage runs:

// Global agent: any available node
pipeline {
  agent any
}
 
// Docker agent: runs inside container
pipeline {
  agent {
    docker {
      image 'node:20-alpine'
      args '-v /tmp:/tmp'
    }
  }
}
 
// Kubernetes pod agent
pipeline {
  agent {
    kubernetes {
      yaml '''
        apiVersion: v1
        kind: Pod
        spec:
          containers:
          - name: node
            image: node:20
            command: [sleep, infinity]
      '''
      defaultContainer 'node'
    }
  }
}
 
// Stage-level agent override
stage('Build on Linux') {
  agent { label 'linux' }
  steps {
    sh 'uname -a'
  }
}

Parallel Stages

Run stages concurrently to reduce pipeline duration:

stage('Test Suite') {
  parallel {
    stage('Unit Tests') {
      steps {
        sh 'npm run test:unit'
      }
    }
    stage('Integration Tests') {
      steps {
        sh 'npm run test:integration'
      }
    }
    stage('Lint') {
      steps {
        sh 'npm run lint'
      }
    }
  }
}

Parameters and Credentials

pipeline {
  parameters {
    string(name: 'VERSION', defaultValue: 'latest', description: 'Image version')
    booleanParam(name: 'SKIP_TESTS', defaultValue: false)
    choice(name: 'ENV', choices: ['staging', 'production'], description: 'Target env')
  }
 
  stages {
    stage('Deploy') {
      steps {
        withCredentials([
          usernamePassword(
            credentialsId: 'docker-registry',
            usernameVariable: 'DOCKER_USER',
            passwordVariable: 'DOCKER_PASS'
          )
        ]) {
          sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS registry.example.com'
          sh "docker push registry.example.com/app:${params.VERSION}"
        }
      }
    }
  }
}

Shared Libraries

Reuse pipeline logic across multiple repositories:

// Jenkinsfile in application repo
@Library('company-pipeline-lib') _
 
standardPipeline(
  name: 'my-service',
  registry: 'registry.example.com',
  deployEnvs: ['staging', 'production']
)
// vars/standardPipeline.groovy in shared library
def call(Map config) {
  pipeline {
    agent any
    stages {
      stage('Build') {
        steps {
          sh "docker build -t ${config.registry}/${config.name} ."
        }
      }
    }
  }
}

Common Mistakes

  • Storing credentials directly in Jenkinsfile instead of the Jenkins credential store
  • Using scripted pipeline syntax (node { }) when declarative (pipeline { }) is clearer
  • Missing cleanWs() in post steps, causing disk exhaustion on build agents
  • Not using disableConcurrentBuilds() for deployment pipelines that should not run simultaneously
  • Omitting when conditions on deploy stages, causing accidental production deployments from feature branches

Best Practices

  • Store your Jenkinsfile at the repo root and version it with code
  • Pin Docker image versions in agent blocks — avoid latest tags
  • Use parallel stages for test and lint steps to cut pipeline wall time
  • Keep credentials in Jenkins Credential Store; reference by ID only
  • Use shared libraries for DRY pipeline logic across multiple projects
  • Set build timeouts to prevent hung pipelines from blocking agents indefinitely
  • Archive test results and artifacts for post-build analysis

Key Takeaways

  • Declarative pipeline syntax (pipeline { }) is the recommended modern approach over scripted pipelines
  • The agent directive controls where pipeline stages execute — supports Docker, Kubernetes, and labeled nodes
  • The post block handles always, success, failure, and unstable outcomes after all stages
  • parallel stages run concurrently and reduce total pipeline execution time
  • Jenkins Shared Libraries enable teams to extract and reuse pipeline logic across repositories
  • when conditions prevent stages like Deploy from running on non-main branches
  • Credentials should always be injected via withCredentials — never hardcoded in Jenkinsfile
  • The options block (timeout, disableConcurrentBuilds, buildDiscarder) controls pipeline behavior globally

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading