Lesson 003 · Jenkins Learning Path

Pipeline Types, Declarative and Scripted Jenkinsfiles

· Published · 5 min read

Labelled Jenkins CI/CD path separating untrusted pull request validation from trusted immutable artifact approval deployment monitoring and rollback

Jenkins uses the word Pipeline for both a delivery process and the code that describes it. Before writing a Jenkinsfile, choose the correct job model, syntax and source-control strategy. This lesson compares Freestyle, Pipeline, Multibranch Pipeline and Organization Folder jobs, then builds Declarative, Scripted, parallel and matrix examples.

Separate job type from Pipeline syntax

A Jenkins item type controls how Jenkins discovers and configures work. A Pipeline syntax controls how the delivery flow is expressed. For example, a Multibranch Pipeline item can run a Declarative Jenkinsfile or a Scripted Jenkinsfile.

Jenkins itemUse it forSource behaviorRecommendation
Freestyle projectSmall integration or a simple command that cannot yet be movedSteps primarily configured in the UIAvoid for growing delivery workflows because review and reuse are weak.
PipelineOne controlled Pipeline definitionInline script or Pipeline script from SCMUse SCM. Inline scripts are suitable only for small administration tasks.
Multibranch PipelineBranches, pull requests and tags with a JenkinsfileDiscovers matching repository heads automaticallyDefault choice for an application repository.
Organization FolderMany repositories owned by one GitHub organizationDiscovers repositories and their branches/PRsUse when governance is consistent across a repository fleet.
FolderOwnership, permissions, credentials and naming boundariesContains other itemsUse folder-scoped authorization and credentials for teams/environments.

Declarative and Scripted Pipeline

CharacteristicDeclarativeScripted
Top levelpipeline {}node {} and Groovy control flow
StructureOpinionated sections and directivesFlexible step-oriented program
ValidationStronger syntax validation and visual stage modelMore responsibility on the author
Typical useMost CI/CD PipelinesDynamic behavior that Declarative cannot express cleanly
Mixingscript {} permits a small Scripted regionCan call ordinary Pipeline steps

Prefer Declarative Pipeline for readability and policy. Use script {} for a bounded dynamic calculation, not to hide the entire Pipeline inside one Scripted block.

Read a Declarative Jenkinsfile in order

pipeline {
  agent none
  options {
    timestamps()
    timeout(time: 45, unit: 'MINUTES')
    disableConcurrentBuilds()
    buildDiscarder(logRotator(numToKeepStr: '30'))
  }
  parameters {
    choice(name: 'TARGET_ENV', choices: ['dev', 'staging'], description: 'Deployment target')
    booleanParam(name: 'RUN_SLOW_TESTS', defaultValue: false, description: 'Run extended tests')
  }
  environment {
    APP_NAME = 'example-api'
  }
  stages {
    stage('Build') {
      agent { label 'linux && nodejs' }
      steps {
        checkout scm
        sh 'set -euo pipefail; ./ci/build.sh'
      }
    }
    stage('Slow tests') {
      when { expression { params.RUN_SLOW_TESTS } }
      agent { label 'linux' }
      steps { sh 'set -euo pipefail; ./ci/slow-tests.sh' }
    }
  }
  post {
    always { junit testResults: 'reports/*.xml', allowEmptyResults: true }
    cleanup { deleteDir() }
  }
}
  • agent none prevents one agent from being reserved for the whole run; each stage chooses the capability it needs.
  • options defines runtime policy. A stage-level timeout includes time waiting for that stage's agent.
  • parameters becomes the read-only params map. Validate parameters again before using them in shell or deployment commands.
  • environment defines non-secret values. Bind secrets only around the steps that need them.
  • when decides whether a stage runs. Use beforeAgent true when a condition should be evaluated before provisioning an expensive stage agent.
  • post publishes evidence and performs result-specific or final cleanup.

Scripted Pipeline example

node('linux') {
  timestamps {
    try {
      stage('Checkout') { checkout scm }
      stage('Build') { sh 'set -euo pipefail; ./ci/build.sh' }
      stage('Test') { sh 'set -euo pipefail; ./ci/test.sh' }
    } catch (err) {
      currentBuild.result = 'FAILURE'
      throw err
    } finally {
      junit testResults: 'reports/*.xml', allowEmptyResults: true
      deleteDir()
    }
  }
}

Scripted syntax is not ordinary Groovy. Pipeline execution is transformed so it can pause and resume; calling unsupported methods, storing non-serializable objects across steps, or mixing Pipeline steps inside methods annotated @NonCPS causes difficult failures.

Sequential, parallel and matrix execution

stage('Quality') {
  parallel {
    stage('Unit') { steps { sh './ci/unit.sh' } }
    stage('Lint') { steps { sh './ci/lint.sh' } }
    stage('Security') { steps { sh './ci/security.sh' } }
  }
}
stage('Compatibility') {
  matrix {
    axes {
      axis { name 'NODE_VERSION'; values '22', '24' }
      axis { name 'PLATFORM'; values 'linux', 'windows' }
    }
    excludes {
      exclude {
        axis { name 'NODE_VERSION'; values '22' }
        axis { name 'PLATFORM'; values 'windows' }
      }
    }
    agent { label "${PLATFORM}" }
    stages {
      stage('Test') {
        steps { echo "Test Node ${NODE_VERSION} on ${PLATFORM}" }
      }
    }
  }
}

Parallel branches need independent directories, ports, test accounts and output names. A matrix is appropriate when the same stages run across combinations. Do not use parallel deployment to environments sharing one mutable database unless the release design supports it.

Branch, pull request, tag and change routing

stage('Deploy main') {
  when { allOf { branch 'main'; not { changeRequest() } } }
  steps { sh './ci/deploy-staging.sh' }
}
stage('Publish tag') {
  when { buildingTag() }
  steps { sh './ci/publish-tag.sh' }
}
stage('Service A') {
  when { changeset 'services/service-a/**' }
  steps { build job: 'service-a-ci', wait: true, propagate: true }
}

Local infrastructure Jenkinsfiles use this monorepo orchestration pattern: a root Pipeline detects changed paths and triggers component jobs. It reduces unnecessary work, but wait: false makes the parent succeed without knowing the child result. Use wait: true, propagate: true when the orchestrator must represent delivery success.

Use Shared Libraries for governed reuse

Move stable cross-repository behavior such as standard build, scan and deployment wrappers into a versioned Shared Library. Keep application-specific stages visible in the Jenkinsfile.

@Library('delivery-library@v3') _

pipeline {
  agent none
  stages {
    stage('Build') {
      steps { standardNodeBuild(nodeVersion: '24') }
    }
  }
}

Pin the library version for release Pipelines. A globally trusted library can execute outside the Groovy sandbox and is controller-trusted code; restrict its repository and reviewers accordingly.

Pipeline development and validation tools

  1. Use the Snippet Generator at /pipeline-syntax for installed plugin steps.
  2. Use the Declarative Directive Generator for agent, when, matrix and other directives.
  3. Validate in a non-production controller or folder with non-production credentials.
  4. Test success, failure, timeout, retry, cancelled approval, agent loss and restart behavior.
  5. Keep the Jenkinsfile and every invoked script under review in source control.

Unsafe design patterns

Unsafe: a single enormous script {} block defeats Declarative validation and makes restart behavior difficult to reason about. Unsafe: allowing untrusted pull requests to edit a Jenkinsfile that receives protected credentials enables credential theft. Unsafe: interpolating an unchecked parameter into sh can turn user input into a command.

Practice lab

  1. Create a Multibranch Pipeline from a training repository.
  2. Add Build, Test and Package stages using stage-level agents.
  3. Add a boolean parameter for slow tests and a main-branch-only publish stage.
  4. Run two quality checks in parallel and publish their results.
  5. Open a branch, change its Jenkinsfile, and confirm production credentials are unavailable.
  6. Use Replay only for diagnosis, then commit the final correction to source control.

Official references

Advertisement