Jenkins Scripted Pipeline Guide: Groovy Architecture from Beginner to Enterprise 🛠️
A hands-on engineering guide to mastering Jenkins Scripted Pipelines. Learn the Groovy execution model, build basic continuous integration workflows, and construct production-ready multi-stage deployment engines with error handling and shared libraries.
Published by AHP • cicdclouds.com
1. The Core Fundamentals: Scripted vs. Declarative Pipelines
In the Jenkins ecosystem, Pipeline-as-Code allows development teams to define their complete build, test, and release cycle inside a version-controlled file called a Jenkinsfile. Jenkins provides two distinct syntactical styles:
- Declarative Pipeline (
pipeline { ... }): An opinionated, highly structured configuration format. It enforces predefined blocks (such asagent,stages,steps, andpost) designed for standard application delivery with built-in validation. - Scripted Pipeline (
node { ... }): A general-purpose Domain-Specific Language (DSL) executed directly on the Apache Groovy runtime. It places minimal restrictions on structure, granting developers complete programmatic control to utilize loops, conditional branching, try-catch blocks, and dynamic runtime computations.
Why Choose Scripted Pipelines?
While Declarative is recommended for straightforward projects, Scripted Pipelines shine in complex enterprise scenarios where:
- Build stages must be generated dynamically based on an external matrix or dynamic environment payload.
- Custom exception handling and granular rollback logic are required at each step.
- Teams require direct access to JVM objects, file system handles, and external API responses during pipeline orchestration.
2. Understanding Groovy Execution & The CPS Interpreter
Jenkins executes Scripted Pipelines using a specialized engine known as the CPS (Continuation-Passing Style) Interpreter. The CPS engine serializes the complete state of the pipeline to disk at each step boundary. If the Jenkins master crashes or restarts mid-build, the pipeline can resume precisely where it stopped.
Critical Groovy Syntax Conventions in Jenkins
- Closures: Code blocks passed as arguments (surrounded by curly braces
{ ... }) that define stages or execution scopes. - String Interpolation: Single-quoted strings (
'echo hello') are plain text literals, whereas double-quoted strings ("echo ${BRANCH_NAME}") evaluate dynamic Groovy expressions. - Serialization Safety: Because state is saved to disk continuously, non-serializable objects (such as raw database connections, iterators, or open file streams) must not be kept open across pipeline steps.
3. Simple Project: Clean Java Maven CI Pipeline
Let us begin with a clear, functional Scripted Pipeline. This project runs on an agent labeled linux-build-agent, checks out source code from Git, compiles a Java project with Maven, and runs automated JUnit tests with cleanup handling.
Simple Project Jenkinsfile
node('linux-build-agent') {
// Variable declarations
def mvnHome = tool 'Maven-3.9'
try {
stage('1. Checkout Code') {
echo "Checking out application source code from repository..."
checkout scm
}
stage('2. Build Application') {
echo "Compiling Java sources..."
sh "${mvnHome}/bin/mvn clean compile"
}
stage('3. Run Tests & Collect Reports') {
echo "Executing unit tests..."
sh "${mvnHome}/bin/mvn test"
// Archive test results into Jenkins UI
junit 'target/surefire-reports/*.xml'
}
stage('4. Package Artifact') {
echo "Packaging JAR artifact..."
sh "${mvnHome}/bin/mvn package -DskipTests=true"
// Archive build binary
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
} catch (Exception err) {
currentBuild.result = 'FAILURE'
echo "Build failed with error: ${err.getMessage()}"
throw err
} finally {
echo "Performing workspace cleanup..."
cleanWs()
}
}
Walkthrough of Key Steps:
node('label'): Allocates an executor on a matching build node and establishes a dedicated workspace.tool 'Maven-3.9': Dynamically looks up and injects the installation path of Maven configured in Global Tool Configuration.try-catch-finally: Ensures that even if the compile or test step fails,cleanWs()is invoked in thefinallyblock to prevent lingering build bloat on the agent.
4. Advanced Enterprise Project: DevSecOps & Kubernetes Deployment
In a modern enterprise ecosystem, our pipeline must do far more than compile code. It must:
- Run parallel code quality and static security analysis (SonarQube).
- Containerize the validated application via Docker and push it to a private registry with strict commit-hash immutability.
- Execute automated vulnerability scanning using Trivy.
- Deploy to an Amazon EKS / Kubernetes cluster with automated rolling status checks and immediate rollbacks on failure.
- Dispatch contextual Slack / Webhook notifications.
Production-Grade Enterprise Jenkinsfile
node('docker-agent') {
// Scoped pipeline variables
def appImage
def gitCommit
def imageTag
def dockerRegistry = "https://registry.hub.docker.com"
def registryCreds = "dockerhub-production-creds"
def k8sNamespace = "production"
// Wrap execution with a hard 60-minute timeout
timeout(time: 60, unit: 'MINUTES') {
try {
stage('1. SCM Extraction & Metadata') {
checkout scm
// Retrieve Git short hash for reproducible, immutable container tagging
gitCommit = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
imageTag = "cicdclouds/enterprise-app:${gitCommit}"
echo "Target deployment version identified: ${imageTag}"
}
stage('2. Parallel Quality & Security Gates') {
parallel(
'Unit & Integration Testing': {
echo "Executing Maven test suite..."
sh 'mvn clean test -B'
junit 'target/surefire-reports/*.xml'
},
'SonarQube Static Analysis': {
echo "Running static security and code smell scanning..."
withSonarQubeEnv('SonarQube-Enterprise-Server') {
sh 'mvn sonar:sonar -Dsonar.projectKey=cicdclouds-enterprise-app'
}
}
)
}
stage('3. Quality Gate Check') {
// Halt pipeline if SonarQube quality gate fails (Coverage < 80%, Vulnerabilities > 0)
timeout(time: 5, unit: 'MINUTES') {
def qg = waitForQualityGate()
if (qg.status != 'OK') {
error "Pipeline halted! Quality Gate check failed with status: ${qg.status}"
}
}
}
stage('4. Docker Build & Vulnerability Scan') {
echo "Compiling container image..."
docker.withRegistry(dockerRegistry, registryCreds) {
appImage = docker.build(imageTag, "--build-arg JAR_FILE=target/*.jar .")
echo "Running Trivy vulnerability scanning on container..."
sh """
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image --severity CRITICAL --exit-code 1 ${imageTag}
"""
echo "Publishing verified container image..."
appImage.push()
appImage.push('latest')
}
}
stage('5. Kubernetes Deployment & Verification') {
withCredentials([file(credentialsId: 'k8s-kubeconfig-prod', variable: 'KUBECONFIG')]) {
echo "Rolling out new release to Kubernetes cluster (${k8sNamespace})..."
sh """
kubectl set image deployment/enterprise-app-deploy \
enterprise-app=${imageTag} -n ${k8sNamespace}
"""
// Monitor rollout health with automatic timeout
def rolloutStatus = sh(
script: "kubectl rollout status deployment/enterprise-app-deploy -n ${k8sNamespace} --timeout=180s",
returnStatus: true
)
if (rolloutStatus != 0) {
echo "ALERT: Pods failed to become healthy. Triggering automated rollback!"
sh "kubectl rollout undo deployment/enterprise-app-deploy -n ${k8sNamespace}"
error "Deployment failed! Rollback executed successfully."
}
}
}
stage('6. Notification') {
echo "Deployment successfully verified across all pods!"
// Optional: slackSend channel: '#deployments', color: 'good', message: "Release ${imageTag} succeeded."
}
} catch (Exception err) {
currentBuild.result = 'FAILURE'
echo "Critical Pipeline Error: ${err.getMessage()}"
// Optional: slackSend channel: '#deployments', color: 'danger', message: "Build Failed: ${err.getMessage()}"
throw err
} finally {
echo "Sanitizing agent workspace and clearing Docker artifacts..."
sh "docker rmi ${imageTag} || true"
cleanWs()
}
}
}
6. Production Troubleshooting & Groovy Anti-Patterns
When developing Jenkins Scripted Pipelines at enterprise scale, engineers regularly encounter common architectural pitfalls:
1. The java.io.NotSerializableException
Root Cause: Because Jenkins CPS continuously serializes local state, creating non-serializable variables (such as direct instances of java.util.Scanner, open file descriptors, or non-serializable third-party objects) inside pipeline steps causes the build to fail with a serialization exception.
Remedy: Annotate helper functions that use complex local calculations with @NonCPS, or discard the non-serializable reference before invoking the next pipeline step.
2. Workspace Disk Exhaustion (Zombie Files)
Root Cause: If pipelines terminate abruptly or omit workspace cleanup, gigabytes of compiled binaries, node modules, and Docker cache layers accumulate on executor disks.
Remedy: Always wrap pipeline bodies in a try-finally block and place cleanWs() inside the finally section.
3. Long-Running Shell Commands Without Timeouts
Root Cause: If a deployment script encounters an unresponsive remote host or waits on an unacknowledged prompt, the build thread hangs indefinitely, tying up an executor slot.
Remedy: Wrap individual external network calls or the global execution with a strict timeout(time: N, unit: 'MINUTES') block.