Jenkins Pipelines
Jenkins is the most widely deployed open-source CI/CD server. Declarative Pipelines define the entire build, test, and deploy process as code in a Jenkinsfile, enabling version control, code review, and reproducibility.
Jenkins Architecture
Jenkins follows a master-agent (controller-agent) architecture:
- Jenkins Controller — The central server that manages configuration, schedules builds, and serves the web UI. It should not run builds directly in production. - Agents (Nodes) — Worker machines that execute pipeline steps. Can be permanent (always-on VMs) or ephemeral (Docker containers, Kubernetes pods spun up per build). - Executors — Slots on an agent for running concurrent builds. Each agent can have multiple executors.
This architecture allows Jenkins to scale horizontally — add more agents to handle more concurrent builds.
Declarative vs Scripted Pipeline
Jenkins supports two pipeline syntaxes:
- Declarative Pipeline — Structured, opinionated syntax with a predefined set of sections. Easier to read and write. Recommended for most use cases.
- Scripted Pipeline — Full Groovy DSL. More flexible and powerful but harder to read. Use when declarative syntax is insufficient.
- Both are stored in a Jenkinsfile at the root of the repository.
- Declarative pipelines have built-in validation and better IDE support.
Declarative Pipeline Syntax
A complete Declarative Pipeline for a Java application:
// Jenkinsfile (Declarative Pipeline)
pipeline {
agent {
docker {
image 'maven:3.9-eclipse-temurin-17' args '-v $HOME/.m2:/root/.m2'
}
}
environment {
APP_NAME = 'my-java-app'
DOCKER_REGISTRY = 'registry.company.com'
SONAR_TOKEN = credentials('sonar-token')
DOCKER_CREDS = credentials('docker-registry-creds')
}
options {
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'git log --oneline -5'
}
}
stage('Build') {
steps {
sh 'mvn clean compile -q'
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test -pl unit-tests'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('Integration Tests') {
steps {
sh 'mvn verify -pl integration-tests'
}
}
}
}
stage('Code Quality') {
steps {
sh """
mvn sonar:sonar \
-Dsonar.projectKey=${APP_NAME} \
-Dsonar.token=${SONAR_TOKEN}
"""
}
}
stage('Package') {
steps {
sh 'mvn package -DskipTests'
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
stage('Build Docker Image') {
steps {
script {
def image = docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}")
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-creds') {
image.push()
image.push('latest')
}
}
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh "kubectl set image deployment/${APP_NAME} " +
"${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} "+ "-n staging"
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
input {
message "Deploy to production?" ok="Deploy" submitter="devops-team"
}
steps {
sh "kubectl set image deployment/${APP_NAME} " +
"${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} "+ "-n production"
}
}
}
post {
success {
slackSend channel: '#deployments', color: 'good',
message: "SUCCESS: ${APP_NAME} build ${BUILD_NUMBER} deployed"
}
failure {
slackSend channel: '#deployments', color: 'danger',
message: "FAILED: ${APP_NAME} build ${BUILD_NUMBER} - ${BUILD_URL}"
emailext to: 'team@company.com',
subject: "Build Failed: ${APP_NAME}",
body: "Check ${BUILD_URL} for details"
}
always {
cleanWs() // Clean workspace after build
}
}
}Shared Libraries
Shared Libraries allow you to define reusable pipeline code in a separate repository and import it into multiple Jenkinsfiles. This prevents code duplication across projects.
Structure of a shared library: - vars/ — Global variables and functions callable from pipelines - src/ — Groovy classes for complex logic - resources/ — Non-Groovy files (scripts, templates)
Configure shared libraries in Jenkins: Manage Jenkins > Configure System > Global Pipeline Libraries.
// vars/deployApp.groovy (in shared library repo)
def call(Map config) {
pipeline {
agent any
stages {
stage('Deploy') {
steps {
sh "kubectl set image deployment/${config.appName} " +
"${config.appName}=${config.image}:${config.tag} "+ "-n ${config.namespace}"
}
}
}
}
}
// Jenkinsfile in application repo (uses shared library)
@Library('my-shared-library@main') _
deployApp(
appName: 'web-app',
image: 'registry.company.com/web-app',
tag: env.BUILD_NUMBER,
namespace: 'production'
)Key Takeaways
- Jenkins uses a controller-agent architecture — the controller schedules builds, agents execute them.
- Declarative Pipelines in a Jenkinsfile define the entire CI/CD process as version-controlled code.
- Use parallel stages to run independent tasks (unit tests, integration tests) simultaneously.
- The input step pauses a pipeline for manual approval before deploying to production.
- Shared Libraries eliminate code duplication by centralizing reusable pipeline logic.
Contact Us
Have a question or feedback? Fill out the form below or reach us directly at support@nvaitraining.com