Skip to main content
Jenkins intermediate Lesson 3 of 4

Jenkins: Multibranch Pipelines & Parallel Stages

Level up Jenkins by running builds for every branch automatically, parallelising slow stages, and controlling flow with post conditions and input gates.

After you understand basic Jenkins pipelines, the next step is scaling them: automating per-branch builds, running slow test suites in parallel, and adding promotion gates for production deploys.

Learning outcomes

By the end you can:

  • configure a Multibranch Pipeline project
  • run stages in parallel
  • use post conditions for notifications and cleanup
  • add manual input gates before production releases

1) Multibranch Pipeline

A Multibranch Pipeline automatically discovers branches and PRs in your repo. Each branch’s Jenkinsfile defines its own pipeline.

How to set it up

  1. In Jenkins, click New Item → Multibranch Pipeline
  2. Under Branch Sources, add your Git repo URL and credentials
  3. Set scan interval (e.g., every 5 minutes) or use a webhook
  4. Jenkins creates and runs a job for each branch with a Jenkinsfile

Branch-aware Jenkinsfile

pipeline {
  agent any

  stages {
    stage('Build') {
      steps {
        sh 'npm ci'
        sh 'npm run build'
      }
    }

    stage('Test') {
      steps {
        sh 'npm test'
      }
    }

    stage('Deploy to Staging') {
      // Only run on the main branch
      when {
        branch 'main'
      }
      steps {
        sh './scripts/deploy.sh staging'
      }
    }

    stage('Deploy to Production') {
      when {
        branch 'main'
      }
      input {
        message "Deploy to production?"
        ok "Deploy Now"
        submitter "team-lead,devops-team"
      }
      steps {
        sh './scripts/deploy.sh production'
      }
    }
  }
}

2) Parallel stages

Run independent stages at the same time to reduce pipeline duration.

pipeline {
  agent any

  stages {
    stage('Build') {
      steps {
        sh 'npm ci'
        sh 'npm run build'
      }
    }

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

        stage('Integration Tests') {
          agent { label 'integration-runner' }
          steps {
            sh 'npm run test:integration'
          }
        }

        stage('Lint') {
          steps {
            sh 'npm run lint'
          }
        }
      }
    }

    stage('Security Scan') {
      steps {
        sh 'npm audit --audit-level=high'
      }
    }
  }
}

failFast: true inside parallel {} aborts siblings as soon as one fails—useful when you want fast feedback:

stage('Tests') {
  parallel {
    failFast true
    stage('Unit') { steps { sh 'npm run test:unit' } }
    stage('E2E')  { steps { sh 'npm run test:e2e' } }
  }
}

3) Post conditions

post blocks run after stages complete, regardless of result.

pipeline {
  agent any

  stages {
    stage('Build') {
      steps {
        sh 'npm ci && npm run build'
      }
    }

    stage('Test') {
      steps {
        sh 'npm test'
      }
    }
  }

  post {
    always {
      // Always clean up workspace
      cleanWs()
    }

    success {
      // Notify on success
      echo "Build ${env.BUILD_NUMBER} succeeded on branch ${env.BRANCH_NAME}"
    }

    failure {
      // Alert the team on failure
      emailext(
        to: 'team@example.com',
        subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
        body: "Check ${env.BUILD_URL} for details."
      )
    }

    unstable {
      // Tests ran but some failed (non-fatal)
      echo "Build is unstable—check test results"
    }
  }
}

4) Environment variables and build parameters

pipeline {
  agent any

  parameters {
    string(name: 'APP_VERSION', defaultValue: '1.0.0', description: 'Version to deploy')
    choice(name: 'TARGET_ENV', choices: ['staging', 'production'], description: 'Target environment')
    booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: 'Skip test stage')
  }

  environment {
    // Static values
    REGISTRY = 'registry.example.com'
    IMAGE_TAG = "${params.APP_VERSION}-${env.BUILD_NUMBER}"
  }

  stages {
    stage('Test') {
      when {
        expression { !params.SKIP_TESTS }
      }
      steps {
        sh 'npm test'
      }
    }

    stage('Build Docker Image') {
      steps {
        sh "docker build -t ${REGISTRY}/myapp:${IMAGE_TAG} ."
        sh "docker push ${REGISTRY}/myapp:${IMAGE_TAG}"
      }
    }

    stage('Deploy') {
      steps {
        sh "./scripts/deploy.sh ${params.TARGET_ENV} ${IMAGE_TAG}"
      }
    }
  }
}

5) Stashing files between stages on different agents

If your stages run on different agents, use stash/unstash to pass files:

stage('Build') {
  agent { label 'build-server' }
  steps {
    sh 'npm ci && npm run build'
    stash name: 'dist', includes: 'dist/**'
  }
}

stage('Test') {
  agent { label 'test-server' }
  steps {
    unstash 'dist'
    sh 'npm test'
  }
}

Next steps

  • Jenkins Shared Libraries: reusable pipeline code across teams
  • Jenkins + Docker: containerised build agents
  • Integrate with Terraform and Ansible for infrastructure deployments

Frequently Asked Questions

What is a Multibranch Pipeline in Jenkins?
A Multibranch Pipeline project scans your repository and automatically creates a Jenkins pipeline job for each branch (or PR) that contains a Jenkinsfile. You don't need to manually create a job per branch.
How do parallel stages affect build time?
Parallel stages run simultaneously on available executors/agents. If you have three independent test suites each taking 5 minutes, running them in parallel reduces total test time from 15 minutes to ~5 minutes.