Jenkinsfile Validator Skill
Comprehensive toolkit for validating, linting, testing, and automating Jenkinsfile pipelines (both Declarative and Scripted). Use this skill when working with Jenkins pipeline files, validating pipeline syntax, checking best practices, debugging pipeline issues, or working with custom plugins.
npx skills add github:pantheon-org/tekhne --skill ci-cd--jenkinsfile--validatorComprehensive toolkit for validating, linting, and testing Jenkinsfile pipelines (both Declarative and Scripted). This skill applies when working with Jenkins pipeline files, validating pipeline syntax, checking best practices, debugging pipeline issues, or working with custom plugins that require documentation lookup.
When to Use
Use this skill when you need to:
- Validate a Jenkinsfile (Declarative or Scripted) for syntax and best practices.
- Run the Declarative Linter against a Declarative pipeline.
- Check Shared Library calls the pipeline depends on.
- Detect hardcoded credentials or controller-heavy operations in a pipeline.
- Review a Jenkinsfile for best-practice scoring before merge.
When Not to Use
Do not use this skill for:
- Authoring a new Jenkinsfile from scratch: use
jenkinsfile-generatorinstead. - CI configuration for a non-Jenkins system (GitHub Actions, GitLab CI): use that system’s skill.
- Diagnosing a failed build’s application logs rather than the pipeline definition.
- Validating Jenkins controller configuration outside the Jenkinsfile.
Mindset
- Detect the type first. Declarative and Scripted have different rules; validate against the right one.
- Lint even when it looks fine. Run the Declarative Linter rather than eyeballing syntax.
- Follow the calls. A Jenkinsfile is incomplete without the Shared Library steps it invokes; validate those too.
- Look up unfamiliar steps. Judge a non-core plugin step against its documentation, not an assumption.
Quick Start
# Run from the skill directory
bash scripts/validate_jenkinsfile.sh assets/examples/bad-declarative-pipeline.Jenkinsfile
bash scripts/validate_jenkinsfile.sh --strict assets/examples/declarative-docker.Jenkinsfile
# Check a shared library step
bash scripts/validate_shared_library.sh assets/examples/shared-library/vars/buildApp.groovy
// Declarative skeleton the validator expects
pipeline {
agent any
stages {
stage('Build') { steps { sh 'make' } }
}
}
Validation Capabilities
Declarative: Required sections, directive placement, parallel execution, credential management, combined shell commands.
Scripted: Groovy syntax, node blocks, try-catch-finally, NonCPS annotation usage, variable scoping.
Both types: Hardcoded credential detection, controller-heavy operations (JsonSlurper, HttpRequest), variable declarations, plugin-specific step validation.
Shared Library: vars/*.groovy: call() method, NonCPS annotation correctness, CPS compatibility, camelCase naming, documentation comments. src/**/*.groovy: package declaration, class-filename match, Serializable implementation, wildcard import warnings, static method CPS compatibility.
See references/validation_rules.md for detailed rules, error reporting format, and examples.
Pipeline Type Detection
Auto-detected: Declarative (pipeline {), Scripted (node block or Groovy outside pipeline block). Clarification is requested only if ambiguous.
Validation Command Reference
Full Validation (Recommended)
# Run complete validation (syntax + security + best practices)
bash scripts/validate_jenkinsfile.sh Jenkinsfile
Auto-detects pipeline type, validates syntax, scans for hardcoded credentials, checks best practices, and produces a unified summary.
Command Options
# Full validation (default)
bash scripts/validate_jenkinsfile.sh Jenkinsfile
# Syntax validation only (fastest)
bash scripts/validate_jenkinsfile.sh --syntax-only Jenkinsfile
# Security audit only
bash scripts/validate_jenkinsfile.sh --security-only Jenkinsfile
# Best practices check only
bash scripts/validate_jenkinsfile.sh --best-practices Jenkinsfile
# Skip security checks
bash scripts/validate_jenkinsfile.sh --no-security Jenkinsfile
# Skip best practices
bash scripts/validate_jenkinsfile.sh --no-best-practices Jenkinsfile
# Strict mode (fail on warnings)
bash scripts/validate_jenkinsfile.sh --strict Jenkinsfile
Script Architecture
The validation system uses a modular script architecture:
scripts/
├── validate_jenkinsfile.sh # Main orchestrator (USE THIS)
│ ├── Auto-detects pipeline type
│ ├── Runs syntax validation
│ ├── Runs security scan
│ ├── Runs best practices check
│ └── Produces unified summary
│
├── validate_declarative.sh # Declarative syntax validator
├── validate_scripted.sh # Scripted syntax validator
├── common_validation.sh # Shared functions + security scan
├── best_practices.sh # 15-point best practices scorer
└── validate_shared_library.sh # Shared library validator
Shared Library Validation
Validate Jenkins Shared Library files using validate_shared_library.sh:
# Validate a single vars file
bash scripts/validate_shared_library.sh vars/myStep.groovy
# Validate entire shared library directory
bash scripts/validate_shared_library.sh /path/to/shared-library
Plugin Documentation Lookup
Important: Plugin documentation lookup is the assistant’s responsibility (not automated in scripts). After running validation, identify unknown plugins and look them up.
When to Look Up Plugin Documentation
Look up documentation when you encounter:
- Steps not in
references/common_plugins.md(e.g.,customDeploy,sendToDatadog,grafanaNotify) - Plugin-specific configuration (e.g.,
nexusArtifactUploader,sonarQubeScanner) - User questions about plugin parameters or best practices
Plugin Lookup Workflow (Assistant Responsibility)
- Identify Unknown Plugin Step - Review Jenkinsfile for unrecognized steps
- Check Local Reference First - Read: references/common_plugins.md
- Use Context7 MCP (if not in local reference)
- mcp__context7__resolve-library-id with query: “jenkinsci <plugin-name>-plugin”
- mcp__context7__get-library-docs for usage examples and parameters
- Web Search Fallback (if Context7 has no results)
- WebSearch: “Jenkins <plugin-name> plugin documentation”
- Official source: <https://plugins.jenkins.io/>
- Provide Usage Guidance
- Required vs optional parameters
- Best practices for the plugin
- Security considerations
See references/common_plugins.md for documentation on commonly used plugins.
Assistant Workflow
When a user provides a Jenkinsfile for validation:
-
Run validation using the main script:
bash scripts/validate_jenkinsfile.sh <path-to-jenkinsfile> -
Optionally read the Jenkinsfile using the Read tool if you need to:
- Understand the pipeline structure before validation
- Provide context-specific advice
- Identify specific plugins being used
-
Look up unknown plugins after validation:
- Review validation output for unrecognized step names
- Check
references/common_plugins.mdfirst - If not found, use Context7 MCP:
mcp__context7__resolve-library-idwith query “jenkinsci <plugin-name>” - If still not found, use WebSearch: “Jenkins <plugin-name> plugin documentation”
- Provide usage guidance based on documentation
-
Report results with line numbers, severity, and actionable suggestions
-
Provide inline fix suggestions when errors are found (include corrected code snippets directly in the response)
Anti-Patterns
NEVER validate only the Jenkinsfile without checking Shared Library calls
WHY: Pipeline files that reference shared library steps pass basic validation but fail at runtime when the library step has changed signature.
BAD: Validate Jenkinsfile in isolation when it calls @Library('my-lib') import com.example.Build.
GOOD: Note all @Library calls and cross-reference the library version being loaded.
NEVER skip Declarative Linter validation for Declarative pipelines
WHY: The Jenkins Declarative Linter catches structural errors that generic YAML/Groovy checks miss, such as missing steps blocks or invalid directive placement.
BAD: Rely only on Groovy syntax checking for Declarative pipelines.
GOOD: Submit the Jenkinsfile to http://<jenkins>/pipeline-model-converter/validate or use the CLI linter.
NEVER accept a “no errors” result from a linter as a green-light to deploy
WHY: Linters cannot execute the pipeline; validate that sh steps, credentials references, and environment variables exist in the target Jenkins environment.
BAD: Merge pipeline changes based only on linter results.
GOOD: Run the pipeline against a staging branch with a dry-run or canary job before merging to main.
NEVER leave retry(n) in production pipelines without understanding why it was added
WHY: Retry masks flaky steps and increases build time; investigate the root cause instead.
BAD: Add retry(3) to a flaky test stage and close the ticket.
GOOD: Investigate why the step is flaky, fix the root cause, and remove the retry.
NEVER validate a Declarative pipeline against Scripted rules, or the reverse
WHY: The two syntaxes have different required structure. Validating against the wrong rule set produces false errors and misses real ones.
BAD: Treat a node { ... } file as missing the pipeline { } block and report it as broken.
GOOD: Detect the pipeline type first (pipeline { is Declarative; a node block is Scripted) and apply the matching validator.
NEVER guess how an unfamiliar plugin step behaves
WHY: Steps not covered by references/common_plugins.md have plugin-specific parameters. Guessing leads to wrong fix suggestions.
BAD: Declare sonarQubeScanner or customDeploy usage correct or incorrect from memory.
GOOD: Check references/common_plugins.md first, then consult the plugin documentation at <https://plugins.jenkins.io/> and report required and optional parameters.
NEVER ignore hardcoded credential findings
WHY: The security scan flags credentials embedded in the Jenkinsfile. They end up in source control and build logs.
BAD: Treat a hardcoded credential warning as noise and proceed.
GOOD: Replace the literal with a Jenkins credential binding such as withCredentials([...]) and re-run bash scripts/validate_jenkinsfile.sh Jenkinsfile.
NEVER report findings without line numbers and a fix suggestion
WHY: The workflow requires results reported with line numbers, severity and actionable suggestions, plus inline corrected snippets for errors.
BAD: Reply “the Jenkinsfile has problems with its post section”.
GOOD: Report the line, the severity, and a corrected code snippet.
NEVER run controller-heavy operations in pipeline code without flagging them
WHY: The validator checks for controller-heavy operations such as JsonSlurper and HttpRequest, which run on the Jenkins controller and degrade it.
BAD: Let new JsonSlurper().parseText(...) outside a @NonCPS method pass unmentioned.
GOOD: Flag it and suggest moving the work to an agent step or a correctly annotated @NonCPS method.
References
Internal:
- Declarative Syntax: Complete Declarative Pipeline syntax reference
- Scripted Syntax: Complete Scripted Pipeline syntax reference
- Best Practices: Jenkins pipeline best practices and patterns
- Common Plugins: Documentation for frequently used Jenkins plugins
- Validation Rules: Detailed validation rules, error reporting format, and examples
- Troubleshooting: Common issues, debug mode, and limitations
External:
- Official Jenkins Pipeline Syntax: <https://www.jenkins.io/doc/book/pipeline/syntax/>
- Pipeline Development Tools: <https://www.jenkins.io/doc/book/pipeline/development/>
- Pipeline Best Practices: <https://www.jenkins.io/doc/book/pipeline/pipeline-best-practices/>
- Jenkins Plugins: <https://plugins.jenkins.io/>
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Runtime validation via eval scenarios proving the skill actually changes agent behaviour. Requires evals/ directory, instructions.json, ≥3 scenarios, and ≥80% instruction coverage in summary.json.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
No eval scenarios available.
Jenkins Pipeline Best Practices
Comprehensive guide based on official Jenkins documentation and community best practices.
Performance Best Practices
1. Combine Shell Commands
Bad:
sh 'echo "Starting build"'
sh 'mkdir build'
sh 'cd build'
sh 'cmake ..'
sh 'make'
sh 'echo "Build complete"'
Good:
sh '''
echo "Starting build"
mkdir build
cd build
cmake ..
make
echo "Build complete"
'''
Why: Each sh step has start-up and tear-down overhead. Combining commands reduces this overhead and improves performance.
2. Use Agent-Based Operations
Bad (runs on controller):
@NonCPS
def parseJson(String jsonString) {
def jsonSlurper = new groovy.json.JsonSlurper()
return jsonSlurper.parseText(jsonString)
}
def data = readFile('data.json')
def parsed = parseJson(data)
Good (runs on agent):
def result = sh(script: 'jq ".field" data.json', returnStdout: true).trim()
Why: Controller resources are shared across all builds. Heavy operations should run on agents to prevent controller bottlenecks.
3. Minimize Data Transfer to Controller
Bad:
def logFile = readFile('huge-log.txt') // Loads entire file into controller memory
def lines = logFile.split('\n')
Good:
def errorCount = sh(script: 'grep ERROR huge-log.txt | wc -l', returnStdout: true).trim()
Why: Reduces memory usage on controller and network transfer time.
Security Best Practices
1. Never Hardcode Credentials
Bad:
sh 'docker login -u admin -p password123'
sh 'curl -H "Authorization: Bearer abc123xyz" https://api.example.com'
Good:
withCredentials([usernamePassword(
credentialsId: 'docker-hub',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS'
}
withCredentials([string(credentialsId: 'api-token', variable: 'API_TOKEN')]) {
sh 'curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com'
}
Why: Credentials stored in Jenkins Credentials Manager are encrypted and access-controlled.
2. Use Credentials Binding
Good:
environment {
AWS_CREDENTIALS = credentials('aws-credentials-id')
// Creates AWS_CREDENTIALS_USR and AWS_CREDENTIALS_PSW
}
3. Validate User Input
Bad:
parameters {
string(name: 'BRANCH', defaultValue: '', description: 'Branch to build')
}
sh "git checkout ${params.BRANCH}" // Injection risk!
Good:
parameters {
choice(name: 'BRANCH', choices: ['main', 'develop', 'release'], description: 'Branch to build')
}
// Or validate input
def branch = params.BRANCH
if (!branch.matches(/^[a-zA-Z0-9_\-\/]+$/)) {
error "Invalid branch name: ${branch}"
}
Reliability Best Practices
1. Use Timeouts
Good:
// Declarative
options {
timeout(time: 1, unit: 'HOURS')
}
// Scripted
timeout(time: 30, unit: 'MINUTES') {
node {
// steps
}
}
Why: Prevents builds from hanging indefinitely and consuming resources.
2. Implement Error Handling
Declarative:
post {
always {
cleanWs()
}
success {
slackSend color: 'good', message: "Build succeeded"
}
failure {
mail to: 'team@example.com',
subject: "Build Failed: ${currentBuild.fullDisplayName}",
body: "Check ${env.BUILD_URL}"
}
}
Scripted:
node {
try {
stage('Build') {
sh 'make build'
}
stage('Test') {
sh 'make test'
}
} catch (Exception e) {
currentBuild.result = 'FAILURE'
mail to: 'team@example.com',
subject: "Build Failed",
body: "Error: ${e.message}"
throw e
} finally {
cleanWs()
}
}
3. Use Proper Workspace Cleanup
Good:
post {
always {
cleanWs()
}
}
// Or for specific cleanup
post {
cleanup {
deleteDir()
}
}
Why: Ensures consistent build environment and prevents disk space issues.
4. Implement Retries for Flaky Operations
Good:
retry(3) {
sh 'curl -f https://flaky-api.example.com/data'
}
// Or with exponential backoff
script {
def attempts = 0
retry(3) {
attempts++
if (attempts > 1) {
sleep time: attempts * 10, unit: 'SECONDS'
}
sh 'flaky-command'
}
}
Maintainability Best Practices
1. Use Shared Libraries
Bad: Copy-pasting common code across Jenkinsfiles
Good:
@Library('my-shared-library@master') _
pipeline {
agent any
stages {
stage('Build') {
steps {
buildMavenProject() // From shared library
}
}
stage('Deploy') {
steps {
deployToKubernetes(env: 'production') // From shared library
}
}
}
}
2. Use Descriptive Stage Names
Bad:
stage('Step 1') { }
stage('Step 2') { }
Good:
stage('Build Application') { }
stage('Run Unit Tests') { }
stage('Build Docker Image') { }
stage('Deploy to Staging') { }
3. Add Comments for Complex Logic
Good:
script {
// Calculate next version based on git tags
def lastTag = sh(script: 'git describe --tags --abbrev=0', returnStdout: true).trim()
def (major, minor, patch) = lastTag.tokenize('.')
// Increment patch version for feature branches
if (env.BRANCH_NAME.startsWith('feature/')) {
patch = patch.toInteger() + 1
}
def nextVersion = "${major}.${minor}.${patch}"
echo "Next version: ${nextVersion}"
}
4. Break Long Pipelines into Stages
Good:
pipeline {
stages {
stage('Preparation') {
stages {
stage('Checkout') { }
stage('Setup Environment') { }
}
}
stage('Build') {
stages {
stage('Compile') { }
stage('Package') { }
}
}
stage('Quality Checks') {
parallel {
stage('Unit Tests') { }
stage('Integration Tests') { }
stage('Code Analysis') { }
}
}
}
}
Optimization Best Practices
1. Use Parallel Execution
Good:
stage('Tests') {
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test'
}
}
stage('Integration Tests') {
steps {
sh 'mvn verify'
}
}
stage('E2E Tests') {
steps {
sh 'npm run e2e'
}
}
}
}
2. Use failFast with Parallel
Good:
stage('Deploy') {
failFast true
parallel {
stage('Region 1') { }
stage('Region 2') { }
stage('Region 3') { }
}
}
Why: Stops remaining parallel tasks immediately if one fails, saving time and resources.
3. Use Stash/Unstash for Artifacts
Good:
node('build-agent') {
stage('Build') {
sh 'mvn package'
stash name: 'app-jar', includes: 'target/*.jar'
}
}
node('test-agent') {
stage('Test') {
unstash 'app-jar'
sh 'java -jar target/*.jar --test'
}
}
4. Skip Default Checkout When Not Needed
Good:
options {
skipDefaultCheckout() // Don't checkout automatically
}
stages {
stage('Build') {
steps {
checkout scm // Checkout only when needed
}
}
}
Docker Best Practices
1. Use Docker Agents for Consistent Environment
Good:
agent {
docker {
image 'maven:3.8.1-adoptopenjdk-11'
args '-v $HOME/.m2:/root/.m2'
}
}
2. Reuse Docker Images
Bad:
sh 'docker run maven:3.8.1 mvn clean'
sh 'docker run maven:3.8.1 mvn compile'
sh 'docker run maven:3.8.1 mvn package'
Good:
docker.image('maven:3.8.1').inside {
sh 'mvn clean compile package'
}
3. Build Once, Deploy Many Times
Good:
stage('Build') {
steps {
script {
dockerImage = docker.build("myapp:${env.BUILD_NUMBER}")
}
}
}
stage('Test') {
steps {
script {
dockerImage.inside {
sh 'run-tests.sh'
}
}
}
}
stage('Deploy to Staging') {
steps {
script {
dockerImage.push('staging')
}
}
}
stage('Deploy to Production') {
steps {
script {
dockerImage.push('production')
dockerImage.push('latest')
}
}
}
Kubernetes Best Practices
1. Use Resource Limits
Good:
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.8.1
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
'''
}
}
2. Use Service Accounts
Good:
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
serviceAccountName: jenkins-agent
containers:
- name: kubectl
image: bitnami/kubectl:latest
'''
}
}
Testing Best Practices
1. Always Publish Test Results
Good:
post {
always {
junit '**/target/test-results/*.xml'
publishHTML([
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
2. Archive Artifacts
Good:
post {
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
3. Separate Build and Test Stages
Good:
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/test-results/*.xml'
}
}
}
}
Build Trigger Best Practices
1. Use Webhooks Instead of Polling
Bad:
triggers {
pollSCM('H/5 * * * *') // Polls every 5 minutes
}
Good: Configure webhooks in your repository to trigger builds on push/PR
Why: Webhooks are more efficient and provide faster feedback than polling.
2. Use Appropriate Cron Syntax
Good:
triggers {
cron('H 2 * * *') // Daily at ~2 AM (H for hash-based distribution)
cron('H H(0-7) * * *') // Once between midnight and 7 AM
}
Notification Best Practices
1. Send Notifications for Important Events
Good:
post {
failure {
slackSend (
color: 'danger',
message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
fixed {
slackSend (
color: 'good',
message: "Build FIXED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
}
2. Include Relevant Information
Good:
post {
failure {
mail to: 'team@example.com',
subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
Build: ${env.BUILD_URL}
Branch: ${env.BRANCH_NAME}
Commit: ${env.GIT_COMMIT}
Author: ${env.CHANGE_AUTHOR}
Please check the build logs for details.
"""
}
}
Multi-Branch Pipeline Best Practices
1. Use Branch-Specific Logic
Good:
stage('Deploy') {
when {
branch 'main'
}
steps {
sh 'deploy-production.sh'
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh 'deploy-staging.sh'
}
}
2. Use Pull Request Triggers
Good:
stage('PR Validation') {
when {
changeRequest()
}
steps {
sh 'run-pr-checks.sh'
}
}
Credential Management Best Practices
1. Use Least Privilege
- Create separate credentials for different purposes
- Use read-only credentials where possible
- Rotate credentials regularly
2. Use Credential Domains
Organize credentials by domain (global, project-specific, etc.)
3. Mask Sensitive Output
Good:
withCredentials([string(credentialsId: 'api-key', variable: 'API_KEY')]) {
wrap([$class: 'MaskPasswordsBuildWrapper']) {
sh 'echo "Using API key: $API_KEY"' // Will be masked in logs
}
}
Pipeline Configuration Best Practices
1. Use Build Discarder
Good:
options {
buildDiscarder(logRotator(
numToKeepStr: '10', // Keep last 10 builds
daysToKeepStr: '30', // Keep builds from last 30 days
artifactNumToKeepStr: '5', // Keep artifacts from last 5 builds
artifactDaysToKeepStr: '14' // Keep artifacts from last 14 days
))
}
2. Disable Concurrent Builds When Needed
Good:
options {
disableConcurrentBuilds()
}
3. Use Timestamps
Good:
options {
timestamps()
}
Summary Checklist
- Combine multiple shell commands into single steps
- Use agent-based operations, not controller-based
- Never hardcode credentials
- Implement timeouts for all builds
- Add proper error handling (try-catch, post blocks)
- Clean workspace after builds
- Use parallel execution for independent tasks
- Publish test results and artifacts
- Send notifications for important events
- Use webhooks instead of polling
- Implement retries for flaky operations
- Use descriptive stage names
- Add comments for complex logic
- Use shared libraries for common code
- Configure build discarder
- Use Docker for consistent build environment
- Set resource limits for Kubernetes pods
- Validate user input
- Use least-privilege credentials
- Separate build and test stages
References
Common Jenkins Plugins Reference
Documentation for frequently used Jenkins plugins in pipelines.
Table of Contents
- Git Plugin
- Docker Plugin
- Kubernetes Plugin
- Credentials Plugin
- Pipeline Utility Steps
- JUnit Plugin
- HTML Publisher Plugin
- Slack Notification Plugin
- Email Extension Plugin
- Build Timeout Plugin
- Timestamper Plugin
- AnsiColor Plugin
- Workspace Cleanup Plugin
Git Plugin
Provides Git repository access for Jenkins jobs.
Checkout SCM
Declarative:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
}
}
Scripted:
node {
checkout scm
}
Explicit Git Checkout
checkout([
$class: 'GitSCM',
branches: [[name: '*/main']],
userRemoteConfigs: [[
url: 'https://github.com/user/repo.git',
credentialsId: 'github-credentials'
]]
])
// With multiple remotes
checkout([
$class: 'GitSCM',
branches: [[name: '*/develop']],
userRemoteConfigs: [
[url: 'https://github.com/user/repo.git', name: 'origin'],
[url: 'https://github.com/upstream/repo.git', name: 'upstream']
]
])
Git Operations
// Get commit hash
def commit = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
// Get short commit hash
def shortCommit = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
// Get current branch
def branch = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
// Get commit author
def author = sh(script: 'git log -1 --pretty=%an', returnStdout: true).trim()
// Get commit message
def message = sh(script: 'git log -1 --pretty=%B', returnStdout: true).trim()
// Tag commit
sh "git tag -a v${env.BUILD_NUMBER} -m 'Release ${env.BUILD_NUMBER}'"
sh 'git push origin --tags'
Environment Variables
GIT_COMMIT- Current commit hashGIT_BRANCH- Branch nameGIT_PREVIOUS_COMMIT- Previous commitGIT_PREVIOUS_SUCCESSFUL_COMMIT- Last successful build commitGIT_URL- Repository URLGIT_AUTHOR_NAME- Commit author nameGIT_AUTHOR_EMAIL- Commit author email
Docker Plugin
Jenkins plugin for running builds in Docker containers.
Docker Agent
Declarative:
pipeline {
agent {
docker {
image 'maven:3.8.1-adoptopenjdk-11'
args '-v /tmp:/tmp'
label 'docker-agent'
}
}
stages {
stage('Build') {
steps {
sh 'mvn --version'
}
}
}
}
Docker in Scripted Pipeline
node {
// Run inside container
docker.image('maven:3.8.1').inside {
sh 'mvn clean package'
}
// With additional arguments
docker.image('node:14').inside('-v /tmp:/tmp -e NODE_ENV=production') {
sh 'npm install'
sh 'npm test'
}
// Build Docker image
def image = docker.build("myapp:${env.BUILD_NUMBER}")
// Build with custom Dockerfile
def image2 = docker.build("myapp:latest", "-f Dockerfile.prod .")
// Push to registry
docker.withRegistry('https://registry.example.com', 'registry-credentials') {
image.push()
image.push('latest')
}
// Run container
def container = docker.image('nginx:latest').run('-p 8080:80')
try {
sh 'curl http://localhost:8080'
} finally {
container.stop()
}
}
Docker Compose
sh 'docker-compose up -d'
try {
sh 'run-integration-tests.sh'
} finally {
sh 'docker-compose down'
}
Kubernetes Plugin
Run Jenkins agents as Kubernetes pods.
Pod Template
Declarative:
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
metadata:
labels:
jenkins: agent
spec:
containers:
- name: maven
image: maven:3.8.1-adoptopenjdk-11
command:
- cat
tty: true
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
- name: docker
image: docker:latest
command:
- cat
tty: true
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
'''
}
}
stages {
stage('Build') {
steps {
container('maven') {
sh 'mvn clean package'
}
}
}
stage('Docker Build') {
steps {
container('docker') {
sh 'docker build -t myapp:latest .'
}
}
}
}
}
Scripted with Pod Template
podTemplate(
label: 'my-pod',
containers: [
containerTemplate(name: 'maven', image: 'maven:3.8.1', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'kubectl', image: 'bitnami/kubectl:latest', ttyEnabled: true, command: 'cat')
],
volumes: [
secretVolume(secretName: 'kubeconfig', mountPath: '/home/jenkins/.kube')
]
) {
node('my-pod') {
stage('Build') {
container('maven') {
sh 'mvn clean package'
}
}
stage('Deploy') {
container('kubectl') {
sh 'kubectl apply -f deployment.yaml'
}
}
}
}
Credentials Plugin
Securely store and use credentials in pipelines.
Credential Types
Username and Password
withCredentials([usernamePassword(
credentialsId: 'my-credentials',
usernameVariable: 'USERNAME',
passwordVariable: 'PASSWORD'
)]) {
sh 'echo "User: $USERNAME"'
// Use $PASSWORD
}
Secret Text
withCredentials([string(
credentialsId: 'api-token',
variable: 'API_TOKEN'
)]) {
sh 'curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com'
}
SSH User Private Key
withCredentials([sshUserPrivateKey(
credentialsId: 'ssh-key',
keyFileVariable: 'SSH_KEY',
usernameVariable: 'SSH_USER'
)]) {
sh 'ssh -i $SSH_KEY $SSH_USER@server.example.com "deploy.sh"'
}
File
withCredentials([file(
credentialsId: 'kubeconfig',
variable: 'KUBECONFIG'
)]) {
sh 'kubectl --kubeconfig=$KUBECONFIG get pods'
}
Certificate
withCredentials([certificate(
credentialsId: 'cert-id',
keystoreVariable: 'KEYSTORE',
passwordVariable: 'KEYSTORE_PASSWORD'
)]) {
sh 'sign-app.sh $KEYSTORE $KEYSTORE_PASSWORD'
}
Environment Credentials Binding
Declarative:
environment {
DOCKER_CREDENTIALS = credentials('docker-hub-credentials')
// Creates DOCKER_CREDENTIALS_USR and DOCKER_CREDENTIALS_PSW
API_KEY = credentials('api-key') // Secret text
}
Pipeline Utility Steps
Common utility steps for pipelines.
Read and Write Files
// Read file
def content = readFile(file: 'version.txt')
// Write file
writeFile(file: 'output.txt', text: 'Hello World')
// Read JSON
def json = readJSON(file: 'config.json')
// Or from text
def data = readJSON(text: '{"key": "value"}')
// Write JSON
writeJSON(file: 'output.json', json: [name: 'Jenkins', version: '2.0'])
// Read YAML
def yaml = readYAML(file: 'config.yaml')
// Write YAML
writeYAML(file: 'output.yaml', data: [name: 'Jenkins', version: '2.0'])
// Read CSV
def csv = readCSV(file: 'data.csv')
// Read properties
def props = readProperties(file: 'config.properties')
File Operations
// Check if file exists
if (fileExists('path/to/file')) {
echo 'File exists'
}
// Find files
def files = findFiles(glob: '**/*.jar')
files.each { file ->
echo "Found: ${file.path}"
}
// Touch file
touch(file: 'marker.txt')
// ZIP files
zip(zipFile: 'archive.zip', dir: 'target')
// Unzip
unzip(zipFile: 'archive.zip', dir: 'output')
JUnit Plugin
Publish JUnit test results.
Basic Usage
post {
always {
junit '**/target/test-results/*.xml'
}
}
// With options
junit(
testResults: '**/target/surefire-reports/*.xml',
allowEmptyResults: true,
keepLongStdio: true,
healthScaleFactor: 1.0
)
HTML Publisher Plugin
Publish HTML reports.
publishHTML([
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report',
keepAll: true,
alwaysLinkToLastBuild: true,
allowMissing: false
])
// Multiple reports
publishHTML([
reportDir: 'test-results',
reportFiles: 'index.html',
reportName: 'Test Results'
])
publishHTML([
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Code Coverage'
])
Slack Notification Plugin
Send notifications to Slack.
// Simple notification
slackSend(
color: 'good',
message: 'Build succeeded!'
)
// With details
slackSend(
color: currentBuild.result == 'SUCCESS' ? 'good' : 'danger',
message: """
Build: ${env.JOB_NAME} #${env.BUILD_NUMBER}
Status: ${currentBuild.result}
Duration: ${currentBuild.durationString}
URL: ${env.BUILD_URL}
""",
channel: '#builds',
teamDomain: 'myteam',
tokenCredentialId: 'slack-token'
)
// Conditional notifications
post {
success {
slackSend color: 'good', message: "Build ${env.BUILD_NUMBER} succeeded"
}
failure {
slackSend color: 'danger', message: "Build ${env.BUILD_NUMBER} failed"
}
fixed {
slackSend color: 'good', message: "Build ${env.BUILD_NUMBER} fixed!"
}
}
Email Extension Plugin
Send detailed email notifications.
emailext(
subject: "Build ${currentBuild.result}: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
<h2>Build ${currentBuild.result}</h2>
<p><strong>Job:</strong> ${env.JOB_NAME}</p>
<p><strong>Build Number:</strong> ${env.BUILD_NUMBER}</p>
<p><strong>Build URL:</strong> <a href="${env.BUILD_URL}">${env.BUILD_URL}</a></p>
<p><strong>Duration:</strong> ${currentBuild.durationString}</p>
""",
to: 'team@example.com',
from: 'jenkins@example.com',
replyTo: 'noreply@example.com',
mimeType: 'text/html',
attachLog: true,
compressLog: true,
attachmentsPattern: '**/target/*.jar'
)
// Conditional emails
post {
failure {
emailext(
subject: "Build Failed: ${env.JOB_NAME}",
body: "Check ${env.BUILD_URL}",
to: 'team@example.com',
recipientProviders: [
developers(), // Send to developers who made changes
culprits(), // Send to developers who broke the build
requestor() // Send to user who triggered the build
]
)
}
}
Build Timeout Plugin
Set timeouts for builds.
Declarative:
options {
timeout(time: 1, unit: 'HOURS')
}
Scripted:
timeout(time: 30, unit: 'MINUTES') {
node {
// steps
}
}
// Activity timeout (no console output)
timeout(time: 10, unit: 'MINUTES', activity: true) {
node {
// steps
}
}
Timestamper Plugin
Add timestamps to console output.
Declarative:
options {
timestamps()
}
Scripted:
timestamps {
node {
echo 'This will have timestamps'
}
}
AnsiColor Plugin
Add color to console output.
Declarative:
options {
ansiColor('xterm')
}
Scripted:
ansiColor('xterm') {
node {
sh 'ls --color=always'
}
}
Workspace Cleanup Plugin
Clean workspace before/after builds.
// Clean before build
cleanWs()
// Clean after build
post {
always {
cleanWs()
}
}
// Clean with options
cleanWs(
deleteDirs: true,
disableDeferredWipeout: true,
notFailBuild: true,
patterns: [
[pattern: 'target', type: 'INCLUDE'],
[pattern: '*.log', type: 'INCLUDE']
]
)
// Delete directory
deleteDir()
Additional Common Plugins
Archive Artifacts
archiveArtifacts(
artifacts: '**/*.jar',
allowEmptyArchive: false,
fingerprint: true,
onlyIfSuccessful: true
)
Stash/Unstash
// Stash files
stash(
name: 'build-artifacts',
includes: 'target/*.jar',
excludes: 'target/*-sources.jar'
)
// Unstash files
unstash 'build-artifacts'
Build Job
// Trigger another job
build(
job: 'downstream-job',
parameters: [
string(name: 'ENVIRONMENT', value: 'production'),
booleanParam(name: 'RUN_TESTS', value: true)
],
wait: true,
propagate: true
)
Input
def userInput = input(
message: 'Deploy to production?',
ok: 'Deploy',
parameters: [
choice(name: 'ENVIRONMENT', choices: ['staging', 'production'], description: 'Target environment'),
string(name: 'VERSION', defaultValue: '1.0', description: 'Version to deploy')
],
submitter: 'admin,ops',
submitterParameter: 'approver'
)
echo "Deploying ${userInput.VERSION} to ${userInput.ENVIRONMENT}"
echo "Approved by: ${userInput.approver}"
Retry
retry(3) {
sh 'flaky-command'
}
Sleep
sleep(time: 30, unit: 'SECONDS')
Wait Until
waitUntil {
def status = sh(script: 'check-status.sh', returnStatus: true)
return status == 0
}
Plugin Documentation Lookup
For unlisted plugins, use:
- Context7: Search for
/jenkinsci/\<plugin-name\>-plugin - Web Search: “Jenkins <plugin-name> plugin documentation”
- Official Plugins: https://plugins.jenkins.io/
References
Declarative Pipeline Syntax Reference
Complete reference for Jenkins Declarative Pipeline syntax based on official documentation.
Basic Structure
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
}
}
Required Sections
1. pipeline
The outermost block that contains all pipeline code.
pipeline {
// All declarative pipeline code goes here
}
2. agent
Specifies where the pipeline or stage will execute. Required at top level or per stage.
// Execute on any available agent
agent any
// Execute on agent with specific label
agent {
label 'linux'
}
// Execute in Docker container
agent {
docker {
image 'maven:3.8.1-adoptopenjdk-11'
args '-v /tmp:/tmp'
}
}
// Execute in Kubernetes pod
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.8.1-adoptopenjdk-11
'''
}
}
// No agent (stages must define their own)
agent none
3. stages
Contains a sequence of one or more stage directives. Required.
stages {
stage('Build') {
steps {
// build steps
}
}
stage('Test') {
steps {
// test steps
}
}
}
4. steps
Defines actions to execute within a stage. Required in each stage (unless stage has stages).
steps {
echo 'Hello World'
sh 'make'
bat 'build.bat'
script {
// Groovy script
def myVar = 'value'
}
}
Optional Top-Level Directives
environment
Defines environment variables available to all steps.
environment {
CC = 'clang'
DISABLE_AUTH = 'true'
DB_ENGINE = 'sqlite'
// From credentials
AWS_ACCESS_KEY_ID = credentials('aws-secret-key-id')
// From credentials with username/password
DOCKER_CREDS = credentials('docker-hub-credentials')
// Creates: DOCKER_CREDS_USR and DOCKER_CREDS_PSW
}
options
Configures pipeline-specific settings.
options {
// Keep only last 10 builds
buildDiscarder(logRotator(numToKeepStr: '10'))
// Disable concurrent builds
disableConcurrentBuilds()
// Prevent builds from running forever
timeout(time: 1, unit: 'HOURS')
// Add timestamps to console output
timestamps()
// Retry failed pipeline up to 3 times
retry(3)
// Skip default checkout
skipDefaultCheckout()
// Prepend all console output with time
ansiColor('xterm')
}
parameters
Defines build parameters users can provide.
parameters {
string(
name: 'DEPLOY_ENV',
defaultValue: 'staging',
description: 'Environment to deploy to'
)
choice(
name: 'VERSION',
choices: ['1.0', '1.1', '2.0'],
description: 'Version to deploy'
)
booleanParam(
name: 'RUN_TESTS',
defaultValue: true,
description: 'Run tests before deploy'
)
text(
name: 'RELEASE_NOTES',
defaultValue: '',
description: 'Release notes'
)
password(
name: 'SECRET',
defaultValue: '',
description: 'Secret value'
)
}
// Access in pipeline:
// ${params.DEPLOY_ENV}
triggers
Defines automatic build triggers.
triggers {
// Poll SCM every 15 minutes
pollSCM('H/15 * * * *')
// Cron schedule
cron('H 4 * * 1-5') // Weekdays at 4 AM
// Trigger from upstream job
upstream(
upstreamProjects: 'job1,job2',
threshold: hudson.model.Result.SUCCESS
)
}
tools
Auto-installs and configures tools.
tools {
maven 'Maven 3.8.1'
jdk 'JDK 11'
gradle 'Gradle 7.0'
}
libraries
Loads shared libraries.
@Library('my-shared-library@master') _
// Or
libraries {
lib('my-shared-library@master')
}
Stage-Level Directives
agent (stage-level)
Override agent for specific stage.
stage('Build') {
agent {
docker 'maven:3.8.1-adoptopenjdk-11'
}
steps {
sh 'mvn clean package'
}
}
environment (stage-level)
Stage-specific environment variables.
stage('Deploy') {
environment {
DEPLOY_ENV = 'production'
}
steps {
sh 'deploy.sh $DEPLOY_ENV'
}
}
when
Conditional execution of stage.
stage('Deploy to Production') {
when {
branch 'main'
environment name: 'DEPLOY_ENV', value: 'production'
expression { return params.RUN_DEPLOY }
}
steps {
echo 'Deploying...'
}
}
// When conditions:
when {
branch 'main' // Branch name
branch pattern: "release-\\d+", comparator: "REGEXP"
environment name: 'DEPLOY', value: 'true' // Environment variable
expression { return currentBuild.result == null } // Groovy expression
tag "release-*" // Git tag
tag pattern: "release-\\d+", comparator: "REGEXP"
not { branch 'main' } // Negation
allOf { // AND
branch 'main'
environment name: 'DEPLOY', value: 'true'
}
anyOf { // OR
branch 'main'
branch 'develop'
}
triggeredBy 'UserIdCause' // Trigger type
triggeredBy cause: 'UserIdCause', detail: 'admin'
buildingTag() // Building a tag
changelog '.*\\[DEPLOY\\].*' // Changelog pattern
changeset "**/*.js" // Changed files
equals expected: 2, actual: currentBuild.number // Comparison
}
input
Pause for user input.
stage('Deploy') {
input {
message "Deploy to production?"
ok "Deploy"
submitter "admin,ops"
parameters {
string(name: 'VERSION', description: 'Version to deploy')
}
}
steps {
echo "Deploying ${VERSION}"
}
}
options (stage-level)
Stage-specific options.
stage('Test') {
options {
timeout(time: 30, unit: 'MINUTES')
retry(2)
timestamps()
}
steps {
sh 'run-tests.sh'
}
}
post
Runs after pipeline/stage completion.
post {
always {
// Always run, regardless of status
echo 'Pipeline completed'
cleanWs()
}
success {
// Run only if successful
slackSend color: 'good', message: 'Build succeeded!'
}
failure {
// Run only if failed
mail to: 'team@example.com',
subject: "Build Failed: ${currentBuild.fullDisplayName}",
body: "Something is wrong"
}
unstable {
// Run if unstable (tests failed but build succeeded)
echo 'Build is unstable'
}
changed {
// Run if status changed from previous build
echo 'Build status changed'
}
fixed {
// Run if previous build failed but current succeeded
echo 'Build is fixed'
}
regression {
// Run if previous build succeeded but current failed
echo 'Build regressed'
}
aborted {
// Run if aborted
echo 'Build was aborted'
}
cleanup {
// Always run, after all other post conditions
echo 'Cleaning up...'
deleteDir()
}
}
Parallel Stages
Execute stages in parallel.
stage('Parallel Tests') {
parallel {
stage('Test on Linux') {
agent { label 'linux' }
steps {
sh 'make test'
}
}
stage('Test on Windows') {
agent { label 'windows' }
steps {
bat 'make test'
}
}
stage('Test on Mac') {
agent { label 'mac' }
steps {
sh 'make test'
}
}
}
}
// With failFast
stage('Parallel Deploy') {
failFast true // Stop all parallel stages if one fails
parallel {
stage('Deploy to Region 1') {
steps { sh 'deploy-region1.sh' }
}
stage('Deploy to Region 2') {
steps { sh 'deploy-region2.sh' }
}
}
}
Sequential Stages
Nested stages that run sequentially.
stage('Build and Test') {
stages {
stage('Build') {
steps {
sh 'make build'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
}
}
Matrix
Run stages across combinations of axes.
stage('Test') {
matrix {
axes {
axis {
name 'PLATFORM'
values 'linux', 'mac', 'windows'
}
axis {
name 'BROWSER'
values 'chrome', 'firefox', 'safari'
}
}
excludes {
exclude {
axis {
name 'PLATFORM'
values 'linux'
}
axis {
name 'BROWSER'
values 'safari'
}
}
}
stages {
stage('Test') {
steps {
echo "Testing on ${PLATFORM} with ${BROWSER}"
}
}
}
}
}
Common Steps
steps {
// Shell commands
sh 'echo "Hello"'
sh '''
echo "Multi-line"
echo "shell script"
'''
sh(script: 'ls -la', returnStdout: true)
sh(script: 'exit 1', returnStatus: true)
// Windows batch
bat 'echo Hello'
// PowerShell
powershell 'Write-Host "Hello"'
// Echo
echo 'Message'
// Error
error 'Build failed'
// Retry
retry(3) {
sh 'flaky-command'
}
// Timeout
timeout(time: 5, unit: 'MINUTES') {
sh 'long-running-command'
}
// Script (run Groovy code)
script {
def myVar = 'value'
if (myVar == 'value') {
echo 'Condition met'
}
}
// Credentials
withCredentials([string(credentialsId: 'my-secret', variable: 'SECRET')]) {
sh 'echo $SECRET'
}
// Git checkout
checkout scm
checkout([
$class: 'GitSCM',
branches: [[name: '*/main']],
userRemoteConfigs: [[url: 'https://github.com/user/repo.git']]
])
// Archive artifacts
archiveArtifacts artifacts: '**/*.jar', fingerprint: true
// Publish test results
junit '**/target/test-results/*.xml'
// Stash/unstash
stash name: 'build-artifacts', includes: 'target/*.jar'
unstash 'build-artifacts'
// Delete workspace
deleteDir()
// Clean workspace
cleanWs()
}
Built-in Variables
// Build info
currentBuild.number // Build number
currentBuild.result // SUCCESS, FAILURE, UNSTABLE, ABORTED
currentBuild.currentResult // Current result
currentBuild.displayName // Display name
currentBuild.description // Build description
currentBuild.duration // Build duration in ms
// Environment variables
env.BUILD_ID
env.BUILD_NUMBER
env.BUILD_TAG
env.BUILD_URL
env.JOB_NAME
env.JOB_BASE_NAME
env.NODE_NAME
env.WORKSPACE
env.JENKINS_HOME
env.BRANCH_NAME // For multibranch pipelines
env.CHANGE_ID // For pull requests
env.GIT_COMMIT
env.GIT_BRANCH
// Parameters
params.PARAMETER_NAME
// SCM
scm.userRemoteConfigs
scm.branches
Complete Example
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 1, unit: 'HOURS')
timestamps()
}
parameters {
choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'production'], description: 'Deployment environment')
booleanParam(name: 'RUN_TESTS', defaultValue: true, description: 'Run tests')
}
environment {
APP_NAME = 'my-app'
VERSION = "${env.BUILD_NUMBER}"
DOCKER_IMAGE = "${APP_NAME}:${VERSION}"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
agent {
docker {
image 'maven:3.8.1-adoptopenjdk-11'
}
}
steps {
sh 'mvn clean package'
stash name: 'build-artifacts', includes: 'target/*.jar'
}
}
stage('Test') {
when {
expression { return params.RUN_TESTS }
}
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test'
}
}
stage('Integration Tests') {
steps {
sh 'mvn verify'
}
}
}
post {
always {
junit '**/target/test-results/*.xml'
}
}
}
stage('Docker Build') {
steps {
unstash 'build-artifacts'
sh "docker build -t ${DOCKER_IMAGE} ."
}
}
stage('Deploy') {
when {
branch 'main'
}
input {
message "Deploy to ${params.ENVIRONMENT}?"
ok "Deploy"
submitter "ops,admin"
}
steps {
withCredentials([usernamePassword(credentialsId: 'docker-hub', usernameVariable: 'USER', passwordVariable: 'PASS')]) {
sh '''
docker login -u $USER -p $PASS
docker push ${DOCKER_IMAGE}
'''
}
sh "kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_IMAGE}"
}
}
}
post {
success {
slackSend color: 'good', message: "Build ${env.BUILD_NUMBER} succeeded"
}
failure {
mail to: 'team@example.com',
subject: "Build ${env.BUILD_NUMBER} failed",
body: "Check ${env.BUILD_URL}"
}
cleanup {
cleanWs()
}
}
}
References
Scripted Pipeline Syntax Reference
Complete reference for Jenkins Scripted Pipeline syntax using Groovy.
Overview
Scripted Pipeline is written using Groovy, providing maximum flexibility and power. Unlike Declarative Pipeline, Scripted Pipeline uses imperative programming and has few structural restrictions.
Basic Structure
node {
stage('Build') {
echo 'Building...'
}
stage('Test') {
echo 'Testing...'
}
stage('Deploy') {
echo 'Deploying...'
}
}
node Block
The node block allocates an executor (agent) for the pipeline.
// Run on any available agent
node {
// steps here
}
// Run on specific labeled agent
node('linux') {
// steps here
}
// Run on Docker agent
node('docker') {
// steps here
}
// Run on specific node
node('master') {
// steps here
}
// Run on agent matching expression
node('linux && java11') {
// steps here
}
stage Block
Stages organize pipeline into logical sections (mainly for visualization).
node {
stage('Checkout') {
checkout scm
}
stage('Build') {
sh 'make build'
}
stage('Test') {
sh 'make test'
}
}
Variables and Data Types
Variable Declaration
// Using def (recommended for local scope)
def myString = 'Hello'
def myNumber = 42
def myBoolean = true
def myList = [1, 2, 3]
def myMap = [key1: 'value1', key2: 'value2']
// Without def (global scope - use cautiously)
globalVar = 'accessible everywhere'
// Typed variables
String name = 'Jenkins'
Integer count = 10
Boolean flag = false
List<String> items = ['a', 'b', 'c']
Map<String, String> config = [env: 'prod', version: '1.0']
String Interpolation
def name = 'World'
// Double quotes for interpolation
def greeting = "Hello, ${name}!"
// Single quotes for literal strings
def literal = 'Hello, ${name}!' // Won't interpolate
// Multi-line strings
def multiLine = """
This is a
multi-line string
with ${name}
"""
// Multi-line without interpolation
def multiLineLiteral = '''
This is a
literal multi-line string
with ${name}
'''
Control Structures
if-else
node {
def environment = 'production'
if (environment == 'production') {
echo 'Deploying to production'
} else if (environment == 'staging') {
echo 'Deploying to staging'
} else {
echo 'Deploying to development'
}
// Ternary operator
def message = (environment == 'production') ? 'PROD' : 'NON-PROD'
}
for Loops
node {
// Iterate over list
def items = ['build', 'test', 'deploy']
for (item in items) {
echo "Step: ${item}"
}
// Iterate with index
for (int i = 0; i < items.size(); i++) {
echo "${i}: ${items[i]}"
}
// Range iteration
for (i in 0..5) {
echo "Number: ${i}"
}
// Each method
items.each { item ->
echo "Processing ${item}"
}
// Each with index
items.eachWithIndex { item, index ->
echo "${index}: ${item}"
}
}
while Loops
node {
def counter = 0
while (counter < 5) {
echo "Counter: ${counter}"
counter++
}
}
switch Statement
node {
def environment = 'staging'
switch(environment) {
case 'development':
echo 'Dev environment'
break
case 'staging':
echo 'Staging environment'
break
case 'production':
echo 'Production environment'
break
default:
error 'Unknown environment'
}
}
Error Handling
try-catch-finally
node {
try {
sh 'make build'
sh 'make test'
} catch (Exception e) {
echo "Build failed: ${e.message}"
currentBuild.result = 'FAILURE'
throw e // Re-throw if needed
} finally {
echo 'Cleaning up...'
sh 'make clean'
}
}
try-catch with Different Exception Types
node {
try {
sh 'risky-command'
} catch (hudson.AbortException e) {
echo "Process was aborted: ${e.message}"
} catch (Exception e) {
echo "General error: ${e.message}"
currentBuild.result = 'FAILURE'
}
}
Catching Specific Errors
node {
try {
def result = sh(script: 'test-command', returnStatus: true)
if (result != 0) {
error "Command failed with exit code ${result}"
}
} catch (Exception e) {
echo "Handling error: ${e}"
// Continue or fail
}
}
Methods and Functions
Defining Methods
// Method definition
def buildApplication() {
echo 'Building application...'
sh 'mvn clean package'
}
// Method with parameters
def deploy(String environment, String version) {
echo "Deploying version ${version} to ${environment}"
sh "kubectl set image deployment/app app=${version}"
}
// Method with return value
def getVersion() {
return sh(script: 'git describe --tags', returnStdout: true).trim()
}
// Usage
node {
buildApplication()
def version = getVersion()
deploy('production', version)
}
NonCPS Methods
Methods annotated with @NonCPS should not use Continuation Passing Style (for complex Groovy operations).
@NonCPS
def parseJson(String json) {
def jsonSlurper = new groovy.json.JsonSlurper()
return jsonSlurper.parseText(json)
}
@NonCPS
def processData(data) {
// Complex Groovy logic that doesn't involve pipeline steps
return data.collect { it.toUpperCase() }
}
node {
def json = '{"name": "Jenkins", "version": "2.0"}'
def parsed = parseJson(json)
echo "Name: ${parsed.name}"
// WARNING: Cannot use pipeline steps (sh, echo, etc.) in @NonCPS methods
}
Parallel Execution
Basic Parallel
node {
stage('Parallel Tests') {
parallel(
'Unit Tests': {
node('linux') {
sh 'make unit-test'
}
},
'Integration Tests': {
node('linux') {
sh 'make integration-test'
}
},
'Smoke Tests': {
node('linux') {
sh 'make smoke-test'
}
}
)
}
}
Parallel with failFast
node {
stage('Deploy to Regions') {
parallel(
failFast: true, // Stop all if one fails
'Region US-EAST': {
sh 'deploy-us-east.sh'
},
'Region US-WEST': {
sh 'deploy-us-west.sh'
},
'Region EU': {
sh 'deploy-eu.sh'
}
)
}
}
Dynamic Parallel Execution
node {
def branches = [:]
def environments = ['dev', 'qa', 'staging']
for (int i = 0; i < environments.size(); i++) {
def env = environments[i] // Important: capture variable
branches["Deploy to ${env}"] = {
node {
echo "Deploying to ${env}"
sh "deploy.sh ${env}"
}
}
}
parallel branches
}
Working with Credentials
Username and Password
node {
withCredentials([usernamePassword(
credentialsId: 'my-credentials',
usernameVariable: 'USERNAME',
passwordVariable: 'PASSWORD'
)]) {
sh '''
echo "Username: $USERNAME"
# Use $PASSWORD in commands
'''
}
}
Secret Text
node {
withCredentials([string(
credentialsId: 'api-token',
variable: 'API_TOKEN'
)]) {
sh 'curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com'
}
}
SSH Key
node {
withCredentials([sshUserPrivateKey(
credentialsId: 'ssh-key',
keyFileVariable: 'SSH_KEY',
usernameVariable: 'SSH_USER'
)]) {
sh '''
ssh -i $SSH_KEY $SSH_USER@server.example.com 'deploy.sh'
'''
}
}
Multiple Credentials
node {
withCredentials([
usernamePassword(credentialsId: 'docker-hub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS'),
string(credentialsId: 'api-key', variable: 'API_KEY')
]) {
sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS'
sh 'curl -H "X-API-Key: $API_KEY" https://api.example.com'
}
}
Environment Variables
Setting Environment Variables
node {
// Using withEnv
withEnv(['ENV=production', 'VERSION=1.0']) {
sh 'echo "Environment: $ENV, Version: $VERSION"'
}
// Direct assignment
env.MY_VAR = 'value'
sh 'echo $MY_VAR'
// From shell command
env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
}
Accessing Environment Variables
node {
echo "Build number: ${env.BUILD_NUMBER}"
echo "Job name: ${env.JOB_NAME}"
echo "Workspace: ${env.WORKSPACE}"
def branch = env.BRANCH_NAME ?: 'main'
echo "Branch: ${branch}"
}
Common Wrappers
Timestamps
timestamps {
node {
echo 'This output will have timestamps'
sh 'sleep 5'
echo 'Done'
}
}
Timeout
timeout(time: 30, unit: 'MINUTES') {
node {
sh 'long-running-command'
}
}
// With activity timeout
timeout(time: 5, unit: 'MINUTES', activity: true) {
node {
// Timeout if no console output for 5 minutes
sh 'command-with-output'
}
}
Retry
retry(3) {
node {
sh 'flaky-test-command'
}
}
// With custom condition
retry(3) {
try {
sh 'test-command'
} catch (Exception e) {
if (e.message.contains('timeout')) {
throw e // Retry
} else {
return // Don't retry
}
}
}
Lock
lock(resource: 'deployment-lock', inversePrecedence: true) {
node {
echo 'Only one build can deploy at a time'
sh 'deploy.sh'
}
}
AnsiColor
ansiColor('xterm') {
node {
sh 'ls --color=always'
}
}
Working with Docker
Using Docker Images
node {
docker.image('maven:3.8.1-adoptopenjdk-11').inside {
sh 'mvn --version'
sh 'mvn clean package'
}
// With additional arguments
docker.image('maven:3.8.1').inside('-v /tmp:/tmp -e MAVEN_OPTS="-Xmx1024m"') {
sh 'mvn clean install'
}
}
Building Docker Images
node {
def image = docker.build("my-app:${env.BUILD_NUMBER}")
// With custom Dockerfile
def image2 = docker.build("my-app:latest", "-f Dockerfile.prod .")
// Push to registry
docker.withRegistry('https://registry.example.com', 'registry-credentials') {
image.push()
image.push('latest')
}
}
Running Docker Containers
node {
def container = docker.image('nginx:latest').run('-p 8080:80')
try {
// Run tests against container
sh 'curl http://localhost:8080'
} finally {
container.stop()
}
}
Working with Git
Basic Checkout
node {
checkout scm
// Or explicit checkout
checkout([
$class: 'GitSCM',
branches: [[name: '*/main']],
userRemoteConfigs: [[
url: 'https://github.com/user/repo.git',
credentialsId: 'github-credentials'
]]
])
}
Git Operations
node {
// Get commit info
def commit = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
def shortCommit = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
def branch = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
echo "Commit: ${commit}"
echo "Short: ${shortCommit}"
echo "Branch: ${branch}"
// Tag
sh "git tag -a v${env.BUILD_NUMBER} -m 'Build ${env.BUILD_NUMBER}'"
sh 'git push origin --tags'
}
Stash and Unstash
node('build-agent') {
stage('Build') {
sh 'make build'
stash name: 'build-artifacts', includes: 'target/*.jar'
}
}
node('deploy-agent') {
stage('Deploy') {
unstash 'build-artifacts'
sh 'deploy.sh target/*.jar'
}
}
Input and Approval
node {
stage('Build') {
sh 'make build'
}
stage('Approval') {
def userInput = input(
message: 'Deploy to production?',
parameters: [
choice(name: 'ENVIRONMENT', choices: ['staging', 'production'], description: 'Target environment'),
string(name: 'VERSION', defaultValue: '1.0', description: 'Version to deploy')
],
submitter: 'admin,ops'
)
echo "Deploying ${userInput.VERSION} to ${userInput.ENVIRONMENT}"
}
stage('Deploy') {
sh "deploy.sh ${userInput.ENVIRONMENT} ${userInput.VERSION}"
}
}
Build Parameters
properties([
parameters([
string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: 'Deployment environment'),
choice(name: 'VERSION', choices: ['1.0', '1.1', '2.0'], description: 'Version'),
booleanParam(name: 'RUN_TESTS', defaultValue: true, description: 'Run tests')
])
])
node {
echo "Environment: ${params.DEPLOY_ENV}"
echo "Version: ${params.VERSION}"
if (params.RUN_TESTS) {
sh 'make test'
}
}
Accessing Build Information
node {
// Current build
echo "Build number: ${currentBuild.number}"
echo "Build result: ${currentBuild.result}" // SUCCESS, FAILURE, UNSTABLE, ABORTED
echo "Display name: ${currentBuild.displayName}"
echo "Duration: ${currentBuild.duration}"
// Set build properties
currentBuild.displayName = "#${env.BUILD_NUMBER} - ${env.BRANCH_NAME}"
currentBuild.description = "Deployed version ${version}"
currentBuild.result = 'SUCCESS'
// Previous build
if (currentBuild.previousBuild) {
echo "Previous result: ${currentBuild.previousBuild.result}"
}
}
Complete Example
@Library('shared-library@master') _
// Build properties
properties([
buildDiscarder(logRotator(numToKeepStr: '10')),
disableConcurrentBuilds(),
parameters([
choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'production'], description: 'Target environment'),
booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: 'Skip tests')
])
])
// Variables
def version
def dockerImage
// Helper methods
def buildApp() {
sh 'mvn clean package'
}
@NonCPS
def parseVersion(String pomXml) {
def matcher = (pomXml =~ /<version>(.+)<\/version>/)
return matcher[0][1]
}
// Main pipeline
timestamps {
ansiColor('xterm') {
node('linux') {
try {
stage('Checkout') {
checkout scm
version = sh(script: 'git describe --tags --always', returnStdout: true).trim()
currentBuild.displayName = "#${env.BUILD_NUMBER} - ${version}"
}
stage('Build') {
docker.image('maven:3.8.1-adoptopenjdk-11').inside {
buildApp()
stash name: 'app-jar', includes: 'target/*.jar'
}
}
if (!params.SKIP_TESTS) {
stage('Test') {
parallel(
'Unit Tests': {
sh 'mvn test'
},
'Integration Tests': {
sh 'mvn verify'
}
)
junit '**/target/test-results/*.xml'
}
}
stage('Docker Build') {
unstash 'app-jar'
dockerImage = docker.build("myapp:${version}")
}
if (params.ENVIRONMENT == 'production') {
stage('Approval') {
timeout(time: 1, unit: 'HOURS') {
input message: 'Deploy to production?', submitter: 'ops,admin'
}
}
}
stage('Deploy') {
withCredentials([
usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'USER', passwordVariable: 'PASS'),
string(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')
]) {
sh 'docker login -u $USER -p $PASS registry.example.com'
dockerImage.push()
sh """
kubectl set image deployment/myapp myapp=myapp:${version}
kubectl rollout status deployment/myapp
"""
}
}
currentBuild.result = 'SUCCESS'
} catch (Exception e) {
currentBuild.result = 'FAILURE'
echo "Pipeline failed: ${e.message}"
throw e
} finally {
stage('Cleanup') {
cleanWs()
// Send notification
def color = currentBuild.result == 'SUCCESS' ? 'good' : 'danger'
slackSend color: color, message: "Build ${currentBuild.displayName}: ${currentBuild.result}"
}
}
}
}
}
References
Troubleshooting Guide
Common Issues
Issue: “Best practices check shows false negatives”
- Cause: Comment stripping may interfere with pattern detection
- Solution: Update to latest version
Issue: “Syntax validation passes but pipeline fails on Jenkins”
- Explanation: Local validation catches structural issues but cannot verify:
- Plugin availability
- Agent/node availability
- Credential existence
- Network connectivity
- Solution: Validate on Jenkins using Replay feature or Pipeline Unit Testing Framework
Issue: “Security scan shows passed but best practices finds credentials”
- Solution: Security scan now properly detects all credential patterns
Issue: “Scripts not executable”
- Solution: Run
chmod +x scripts/*.sh
Debug Mode
Enable verbose output for troubleshooting:
# Run with bash debug mode
bash -x scripts/validate_jenkinsfile.sh Jenkinsfile
# Check individual validator output
bash scripts/validate_declarative.sh Jenkinsfile
bash scripts/best_practices.sh Jenkinsfile
bash scripts/common_validation.sh check_credentials Jenkinsfile
Limitations
- No Jenkins Server Required: All validation is local (no live testing)
- Plugin Steps: Cannot fully validate custom plugin steps without documentation
- Runtime Behavior: Cannot detect runtime issues (permissions, network, etc.)
- Complex Groovy: Advanced Groovy constructs may not be fully validated
- Shared Libraries: Remote shared libraries are not fetched or validated
Additional Capabilities
- Dry-run Testing: Validate without Jenkins server (all validation is local)
- Plugin Version Checking: Warn about deprecated plugin versions
- Performance Analysis: Identify potential performance bottlenecks
- Compliance Checking: Validate against organizational standards
- Multi-file Support: Validate multiple Jenkinsfiles in a directory
Validation Rules Reference
Syntax Issues
- Missing required sections (agent, stages, steps)
- Invalid section names or misplaced directives
- Groovy syntax errors
- Missing braces, quotes, or brackets
- Semicolons at end of lines (unnecessary in Jenkins pipelines)
Best Practices
- Combine Shell Commands: Use single
shstep with multiple commands instead of multipleshsteps - Credential Management: Use
credentials()orwithCredentials, never hardcode secrets - Agent Operations: Perform heavy operations on agents, not controller
- Parallel Execution: Use
parallelfor independent stages - Error Handling: Wrap critical sections in try-catch blocks
- Timeouts: Define timeouts in options to prevent hung builds
- Clean Workspace: Clean workspace before/after builds
Variable Usage
- Proper variable declaration and scoping
- Correct interpolation syntax (
${VAR}vs$VAR) - Undefined variable detection
- Environment variable usage
Security
- No hardcoded passwords, API keys, or tokens
- Proper use of Jenkins Credentials Manager
- Secrets management best practices
- Role-based access control recommendations
Error Reporting Format
Validation results include:
- Line numbers for each issue
- Severity levels: Error, Warning, Info
- Descriptions: Clear explanation of the issue
- Suggestions: How to fix the problem
- References: Links to documentation
Example Output
ERROR [Line 5]: Missing required section 'agent'
→ Add 'agent any' or specific agent configuration at top level
WARNING [Line 12]: Multiple consecutive 'sh' steps detected
→ Combine into single sh step with triple-quoted string
→ See: best_practices.md#combine-shell-commands
INFO [Line 23]: Consider using parallel execution for independent stages
→ See: references/declarative_syntax.md#parallel-stages
Best Practice Examples
Good: Combined Shell Commands
sh '''
echo "Building..."
mkdir build
./gradlew build
echo "Build complete"
'''
Bad: Multiple Shell Steps
sh 'echo "Building..."'
sh 'mkdir build'
sh './gradlew build'
sh 'echo "Build complete"'
Good: Credential Management
withCredentials([string(credentialsId: 'api-key', variable: 'API_KEY')]) {
sh 'curl -H "Authorization: Bearer $API_KEY" ...'
}
Bad: Hardcoded Credentials
sh 'curl -H "Authorization: Bearer abc123xyz" ...'