Jenkinsfile Generator Skill
Generates Jenkinsfiles with stages, agents, parallel builds, post-build actions, and security scanning for Declarative and Scripted pipeline syntaxes. Use when creating a Jenkins pipeline script, Groovy pipeline, or build configuration; implementing CI/CD workflows, continuous integration, or build automation; adding Docker/Kubernetes deployments, matrix builds, parameterized pipelines, or DevSecOps security scanning to a Jenkins setup.
npx skills add github:pantheon-org/tekhne --skill ci-cd--jenkinsfile--generatorGenerate production-ready Jenkinsfiles following best practices. All generated files are validated using devops-skills:jenkinsfile-validator skill.
Philosophy
- Prefer Declarative syntax; reach for Scripted only when full Groovy control is genuinely needed.
- Never hardcode secrets: bind credentials through the Jenkins Credentials Store.
- Generate from the templates and best-practice references, then validate before presenting.
- Fail fast and clean up: set timeouts, fail-fast on parallel blocks and always clean the workspace.
When to Use
- Creating a new Jenkins pipeline script, Groovy pipeline or build configuration
- Adding parallel or matrix builds, parameters, or manual approval stages to a Jenkins setup
- Adding Docker or Kubernetes agents and deployments to a pipeline
- Adding DevSecOps stages such as SonarQube, OWASP Dependency-Check or Trivy
- Scaffolding a Jenkins shared library
When Not to Use
- Validating or linting an existing Jenkinsfile (use the Jenkinsfile validator skill instead)
- Writing CI configuration for other systems such as GitHub Actions, GitLab CI or Azure Pipelines
- Debugging a failed build from its console logs rather than the pipeline definition
- Configuring Jenkins controller settings, plugins installation or job folders outside the Jenkinsfile
Core Capabilities
1. Declarative Pipelines (RECOMMENDED)
Process:
- Consult
assets/templates/declarative/basic.Jenkinsfileandreferences/best_practices.md - Generate with required elements:
- Stages with descriptive names
- Environment block with credentials binding (never hardcode secrets)
- Options: timeout, buildDiscarder, timestamps, disableConcurrentBuilds
- Post conditions: always (cleanup), success (artifacts), failure (notifications)
- Always add
parallelsAlwaysFailFast()in options when using parallel blocks - Always include
fingerprint: truewhen usingarchiveArtifacts
- ALWAYS validate using devops-skills:jenkinsfile-validator skill
2. Scripted Pipelines
Use for complex conditional logic, dynamic generation, or full Groovy control.
Process:
- Consult
assets/templates/scripted/basic.Jenkinsfile - Implement try-catch-finally for error handling
- ALWAYS validate using devops-skills:jenkinsfile-validator skill
3. Parallel/Matrix Pipelines
Use parallel {} or matrix {} with axes {} for multi-dimensional builds. See Parallel & Matrix for failFast configuration.
4. Security Scanning (DevSecOps)
Add SonarQube, OWASP Dependency-Check, Trivy stages with fail thresholds.
5. Shared Library Scaffolding
python3 scripts/generate_shared_library.py --name my-library --package org.example
Declarative Syntax Reference
Agent Types
agent any // Any available agent
agent { label 'linux && docker' } // Label-based
agent { docker { image 'maven:3.9.11-eclipse-temurin-21' } }
agent { kubernetes { yaml '...' } } // K8s pod template
agent { kubernetes { yamlFile 'pod.yaml' } } // External YAML
Environment & Credentials
environment {
VERSION = '1.0.0'
AWS_KEY = credentials('aws-key-id') // Creates _USR and _PSW vars
}
Options
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 1, unit: 'HOURS')
disableConcurrentBuilds()
timestamps()
parallelsAlwaysFailFast()
durabilityHint('PERFORMANCE_OPTIMIZED') // 2-6x faster for simple pipelines
}
Parameters
parameters {
string(name: 'VERSION', defaultValue: '1.0.0')
choice(name: 'ENV', choices: ['dev', 'staging', 'prod'])
booleanParam(name: 'SKIP_TESTS', defaultValue: false)
}
When Conditions
| Condition | Example |
|---|---|
branch |
branch 'main' or branch pattern: 'release/*', comparator: 'GLOB' |
tag |
tag pattern: 'v*', comparator: 'GLOB' |
changeRequest |
changeRequest target: 'main' |
changeset |
changeset 'src/**/*.java' |
expression |
expression { env.DEPLOY == 'true' } |
allOf/anyOf/not |
Combine conditions |
Add beforeAgent true to skip agent allocation if condition fails.
Error Handling
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { sh '...' }
warnError('msg') { sh '...' } // Mark UNSTABLE but continue
unstable(message: 'Coverage low') // Explicit UNSTABLE
error('Config missing') // Fail without stack trace
Post Section
post {
always { junit '**/target/*.xml'; cleanWs() }
success { archiveArtifacts artifacts: '**/*.jar', fingerprint: true }
failure { slackSend color: 'danger', message: 'Build failed' }
fixed { echo 'Build fixed!' }
}
Order: always → changed → fixed → regression → failure → success → unstable → cleanup
Always use fingerprint: true with archiveArtifacts for build traceability.
Parallel & Matrix
Always add parallelsAlwaysFailFast() to pipeline options {} block: covers all parallel/matrix blocks automatically. Use per-block failFast true only when options-level is not set:
// Per-block alternative (when options-level not set)
stage('Tests') {
failFast true
parallel {
stage('Unit') { steps { sh 'npm test:unit' } }
stage('E2E') { steps { sh 'npm test:e2e' } }
}
}
stage('Matrix') {
matrix {
axes {
axis { name 'PLATFORM'; values 'linux', 'windows' }
axis { name 'BROWSER'; values 'chrome', 'firefox' }
}
excludes { exclude { axis { name 'PLATFORM'; values 'linux' }; axis { name 'BROWSER'; values 'safari' } } }
stages { stage('Test') { steps { echo "Testing ${PLATFORM}/${BROWSER}" } } }
}
}
Input (Manual Approval)
stage('Deploy') {
input { message 'Deploy?'; ok 'Deploy'; submitter 'admin,ops' }
steps { sh './deploy.sh' }
}
Place input outside steps to avoid holding agents.
Scripted Syntax Reference
node('agent-label') {
try {
stage('Build') { sh 'make build' }
stage('Test') { sh 'make test' }
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
deleteDir()
}
}
// Parallel
parallel(
'Unit': { node { sh 'npm test:unit' } },
'E2E': { node { sh 'npm test:e2e' } }
)
// Environment
withEnv(['VERSION=1.0.0']) { sh 'echo $VERSION' }
withCredentials([string(credentialsId: 'key', variable: 'KEY')]) { sh 'curl -H "Auth: $KEY" ...' }
NonCPS Annotation
@NonCPS
def parseJson(String json) {
new groovy.json.JsonSlurper().parseText(json)
}
Required for non-serializable operations (JsonSlurper, iterators, regex Matchers). No pipeline steps inside.
Docker & Kubernetes
Docker Agent
agent { docker { image 'maven:3.9.11'; args '-v $HOME/.m2:/root/.m2'; reuseNode true } }
Build & Push
def img = docker.build("myapp:${BUILD_NUMBER}")
docker.withRegistry('https://registry.example.com', 'creds') { img.push(); img.push('latest') }
Kubernetes Pod
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9.11-eclipse-temurin-21
command: [sleep, 99d]
'''
}
}
// Use: container('maven') { sh 'mvn package' }
Shared Libraries
@Library('my-shared-library') _
// or dynamically: library 'my-library@1.0.0'
// vars/log.groovy
def info(msg) { echo "INFO: ${msg}" }
// Usage
log.info 'Starting build'
Validation Workflow
ALWAYS validate using devops-skills:jenkinsfile-validator skill:
- Generate Jenkinsfile
- Invoke
devops-skills:jenkinsfile-validatorskill - Handle validation results by severity:
- ERRORS: Must fix - these break the pipeline
- WARNINGS: Should fix - indicate potential issues
- INFO: Apply optimizations based on use case (failFast, build triggers, etc.)
- Re-validate after fixes
- Present validated Jenkinsfile to user
Validation commands:
# Full validation (syntax + security + best practices)
bash scripts/validate_jenkinsfile.sh Jenkinsfile
# Syntax only (fastest)
bash scripts/validate_jenkinsfile.sh --syntax-only Jenkinsfile
Generator Scripts
Use for simple, standard pipelines. Use manual generation for complex pipelines with custom logic or non-standard requirements.
# Declarative (simple pipelines)
python3 scripts/generate_declarative.py --output Jenkinsfile --stages build,test,deploy --agent docker
# Scripted (simple pipelines)
python3 scripts/generate_scripted.py --output Jenkinsfile --stages build,test --agent label:linux
# Shared Library (always use script for scaffolding)
python3 scripts/generate_shared_library.py --name my-library --package com.example
Plugin Documentation Lookup
Always consult external docs for:
- Plugins NOT in
references/common_plugins.md - Version-specific documentation
- Complex configurations or advanced options
Skip external lookup when:
- Using basic syntax from
references/common_plugins.md - Simple, well-documented steps (e.g.,
sh,checkout scm,junit)
Covered plugins: Git, Docker, Kubernetes, Credentials, JUnit, Slack, SonarQube, OWASP Dependency-Check, Email, AWS, Azure, HTTP Request, Microsoft Teams, Nexus, Artifactory, GitHub
Anti-Patterns
NEVER use Scripted Pipeline for new work
WHY: Declarative Pipeline syntax is the current standard with better tooling and cleaner validation; Scripted Pipeline should only be used to maintain existing files or when full Groovy control is required.
BAD: New pipelines starting with node { ... }.
GOOD: Start with pipeline { agent any stages { ... } } Declarative syntax.
NEVER store credentials as plain text pipeline parameters
WHY: Pipeline parameters are logged and visible in the Jenkins UI; credentials must go through the Jenkins Credentials Store.
BAD: parameters { string(name: 'API_KEY', ...) }
GOOD: withCredentials([string(credentialsId: 'api-key-prod', variable: 'API_KEY')]) { ... }
NEVER run all stages on a heavyweight executor without parallelism
WHY: Sequential stages on a single executor waste build time; extract independent stages into parallel { } blocks.
BAD: Lint, unit-test, integration-test, and SAST in sequential stages.
GOOD: Wrap independent stages in parallel { stage('Lint') { ... } stage('Unit Test') { ... } }.
NEVER omit post { always { cleanWs() } }
WHY: Without workspace cleanup, Jenkins agents fill disk with build artifacts from previous runs, causing disk-full build failures.
BAD: No post block in the pipeline.
GOOD: post { always { cleanWs() } } in every Declarative pipeline.
NEVER call sh with inline secret variable expansion
WHY: Shell substitution expands secrets into the command string where they appear in build logs and process lists.
BAD: sh "curl -H 'Authorization: Bearer ${API_KEY}'"
GOOD: withCredentials([...]) { sh 'curl -H "Authorization: Bearer $API_KEY"' } (single quotes prevent Groovy expansion; the credential is still available via the environment).
NEVER use parallel blocks without fail-fast
WHY: Without fail-fast, the remaining parallel branches keep running after one has failed, wasting agent time on a build that is already lost.
BAD: A parallel { ... } block with no parallelsAlwaysFailFast() in options and no failFast true.
GOOD: Add parallelsAlwaysFailFast() to the pipeline options {} block.
NEVER archive artifacts without fingerprinting
WHY: Fingerprints give build traceability, linking an artifact back to the build that produced it.
BAD: archiveArtifacts artifacts: '**/*.jar'
GOOD: archiveArtifacts artifacts: '**/*.jar', fingerprint: true
NEVER present a generated Jenkinsfile without validating it
WHY: Generation can produce syntax errors or best-practice violations. The workflow requires validation with the Jenkinsfile validator skill and a re-run after fixes.
BAD: Generate a Jenkinsfile and hand it straight to the user.
GOOD: Run bash scripts/validate_jenkinsfile.sh Jenkinsfile, fix ERRORS and WARNINGS, re-validate, then present it.
NEVER omit timeouts from pipeline options
WHY: Without a timeout a hung build occupies an executor indefinitely.
BAD: An options {} block with no timeout(...), or no options at all.
GOOD: options { timeout(time: 1, unit: 'HOURS') }
NEVER place input inside steps
WHY: An input inside steps holds an agent while it waits for approval. Declaring it at stage level avoids that.
BAD: Calling input within a stage’s steps block while an agent is allocated.
GOOD: stage('Deploy') { input { message 'Deploy?' } steps { sh './deploy.sh' } }
References
// Minimal Declarative Pipeline
pipeline {
agent any
stages {
stage('Build') { steps { sh 'make' } }
stage('Test') { steps { sh 'make test' } }
}
}
// Error-tolerant stage
stage('Flaky Tests') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
sh 'run-flaky-tests.sh'
}
}
}
// Conditional deployment with approval
stage('Deploy') {
when { branch 'main'; beforeAgent true }
input { message 'Deploy to production?' }
steps { sh './deploy.sh' }
}
| Option | Purpose |
|---|---|
timeout(time: 1, unit: 'HOURS') |
Prevent hung builds |
buildDiscarder(logRotator(numToKeepStr: '10')) |
Manage disk space |
disableConcurrentBuilds() |
Prevent race conditions |
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') |
Continue on error |
references/best_practices.md- Performance, security, reliability patternsreferences/common_plugins.md- Git, Docker, K8s, credentials, notificationsassets/templates/- Declarative and scripted templatesdevops-skills:jenkinsfile-validatorskill - Syntax and best practices validation
Always prefer Declarative unless scripted flexibility is required.
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 - Generator Reference
Quick reference for generating best-practice Jenkinsfiles.
Performance Best Practices
1. Combine Shell Commands
Bad:
sh 'echo "Starting build"'
sh 'mkdir build'
sh 'cd build && cmake ..'
sh 'make'
Good:
sh '''
echo "Starting build"
mkdir build
cd build && cmake ..
make
'''
2. Use Agent-Based Operations
Bad (runs on controller):
def data = readFile('data.json')
def parsed = new groovy.json.JsonSlurper().parseText(data)
Good (runs on agent):
def result = sh(script: 'jq ".field" data.json', returnStdout: true).trim()
3. Minimize Controller Memory Usage
Bad:
def logFile = readFile('huge-log.txt') // Loads entire file
Good:
def errorCount = sh(script: 'grep ERROR huge-log.txt | wc -l', returnStdout: true).trim()
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'
}
2. Use Environment Credentials Binding
environment {
DOCKER_CREDENTIALS = credentials('docker-hub-credentials')
// Creates DOCKER_CREDENTIALS_USR and DOCKER_CREDENTIALS_PSW
API_KEY = credentials('api-key')
}
3. Validate User Input
Bad:
sh "git checkout ${params.BRANCH}" // Injection risk!
Good:
parameters {
choice(name: 'BRANCH', choices: ['main', 'develop', 'release'], description: 'Branch to build')
}
// Or validate
def branch = params.BRANCH
if (!branch.matches(/^[a-zA-Z0-9_\-\/]+$/)) {
error "Invalid branch name: ${branch}"
}
Reliability Best Practices
1. Always Use Timeouts
// Pipeline level
options {
timeout(time: 1, unit: 'HOURS')
}
// Stage level
stage('Long Running') {
options {
timeout(time: 30, unit: 'MINUTES')
}
steps {
sh './long-task.sh'
}
}
2. Implement Error Handling
Declarative:
post {
always {
cleanWs()
}
success {
slackSend color: 'good', message: "Build succeeded"
}
failure {
slackSend color: 'danger', message: "Build failed"
}
}
Scripted:
node {
try {
stage('Build') { sh 'make build' }
stage('Test') { sh 'make test' }
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
cleanWs()
}
}
3. Use catchError for Resilient Pipelines
Allow pipelines to continue after non-critical failures:
catchError - Continue on Failure:
// Mark stage as failed but continue pipeline
stage('Non-Critical Tests') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh 'npm run test:experimental'
}
}
}
// Mark build as unstable if integration tests fail
stage('Integration Tests') {
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') {
sh 'npm run test:integration'
}
}
}
warnError - Quick Unstable Pattern:
stage('Code Analysis') {
steps {
warnError('Linting warnings detected') {
sh 'npm run lint'
}
}
}
unstable - Explicit Unstable Status:
stage('Coverage Check') {
steps {
script {
def coverage = sh(script: 'get-coverage.sh', returnStdout: true).trim().toInteger()
if (coverage < 80) {
unstable(message: "Code coverage ${coverage}% is below 80% threshold")
}
}
}
}
error - Fail Without Stack Trace:
stage('Validation') {
steps {
script {
if (!fileExists('config.json')) {
error('Configuration file not found')
}
}
}
}
Combined Error Handling Pattern (Recommended):
stage('Test Suite') {
steps {
// Critical - fail build if unit tests fail
sh 'npm run test:unit'
// Important - mark unstable if integration tests fail
catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') {
sh 'npm run test:integration'
}
// Non-critical - warn only
warnError('Smoke tests had warnings') {
sh 'npm run test:smoke'
}
// Optional - continue regardless
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh 'npm run test:experimental'
}
}
}
catchError Parameters:
| Parameter | Values | Description |
|---|---|---|
buildResult |
SUCCESS, UNSTABLE, FAILURE, NOT_BUILT, ABORTED | Overall build result on error |
stageResult |
SUCCESS, UNSTABLE, FAILURE, NOT_BUILT, ABORTED | Stage result on error |
message |
String | Message logged on error |
catchInterruptions |
true/false | Whether to catch timeout/abort exceptions (default: true) |
3. Clean Workspace
post {
always {
cleanWs()
}
}
// Or use deleteDir()
post {
cleanup {
deleteDir()
}
}
4. Implement Retries
retry(3) {
sh 'curl -f https://flaky-api.example.com/data'
}
// With backoff
script {
def attempts = 0
retry(3) {
attempts++
if (attempts > 1) {
sleep time: attempts * 10, unit: 'SECONDS'
}
sh 'flaky-command'
}
}
Pipeline Structure Best Practices
1. 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') { }
2. Use Nested Stages for Organization
stages {
stage('Build') {
stages {
stage('Compile') { }
stage('Package') { }
}
}
stage('Quality Checks') {
parallel {
stage('Unit Tests') { }
stage('Integration Tests') { }
stage('Code Analysis') { }
}
}
}
3. Use Parallel Execution
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' }
}
}
}
4. Use failFast with Parallel
stage('Deploy') {
failFast true
parallel {
stage('Region 1') { }
stage('Region 2') { }
stage('Region 3') { }
}
}
Options Best Practices
Recommended Pipeline Options
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
))
timestamps() // Add timestamps to console
timeout(time: 1, unit: 'HOURS') // Pipeline timeout
disableConcurrentBuilds() // No concurrent builds
parallelsAlwaysFailFast() // Fail fast in parallel stages
}
Docker Best Practices
1. Use Docker Agents
agent {
docker {
image 'maven:3.9.9-eclipse-temurin-21'
args '-v $HOME/.m2:/root/.m2'
reuseNode true
}
}
2. Reuse Docker Images
Bad:
sh 'docker run maven:3.9.9 mvn clean'
sh 'docker run maven:3.9.9 mvn compile'
sh 'docker run maven:3.9.9 mvn package'
Good:
docker.image('maven:3.9.9').inside {
sh 'mvn clean compile package'
}
3. Build Once, Deploy Many
stage('Build') {
steps {
script {
dockerImage = docker.build("myapp:${env.BUILD_NUMBER}")
}
}
}
stage('Test') {
steps {
script {
dockerImage.inside { sh 'run-tests.sh' }
}
}
}
stage('Deploy') {
steps {
script {
dockerImage.push()
dockerImage.push('latest')
}
}
}
4. Docker Image Selection Best Practices
Node.js Images
Per Snyk’s Node.js Docker best practices:
| Image Type | Recommendation | Use Case |
|---|---|---|
node:22-bookworm-slim |
Recommended for production | Minimal size, stable Debian base |
node:22-alpine |
Use with caution | Smallest size, but Alpine is experimental in Node.js |
node:22 |
Development only | Large image, includes unnecessary tools |
node:lts |
Avoid in CI/CD | Tag changes over time, not reproducible |
Best Practice:
agent {
docker {
// Use specific version for reproducibility
image 'node:22.11.0-bookworm-slim' // Specific + slim
}
}
// Alternative for size-sensitive builds (with caution)
agent {
docker {
image 'node:22-alpine' // Note: Alpine is experimental in Node.js
}
}
Why avoid Alpine for Node.js?
- Node.js marks Alpine as “experimental” in their official documentation
- Uses musl libc instead of glibc (potential compatibility issues)
- Native dependencies may require recompilation
- Some npm packages may not work correctly
Java/Maven Images
// Recommended: Eclipse Temurin (successor to AdoptOpenJDK)
agent {
docker { image 'maven:3.9.11-eclipse-temurin-21' }
}
// For smaller images
agent {
docker { image 'maven:3.9.11-eclipse-temurin-21-alpine' }
}
Python Images
// Recommended: Slim variant with Debian Bookworm
agent {
docker { image 'python:3.12-slim-bookworm' }
}
// Alpine (smaller but may need additional build tools)
agent {
docker { image 'python:3.12-alpine' }
}
Go Images
// Alpine works well for Go (statically compiled)
agent {
docker { image 'golang:1.23-alpine' }
}
General Rules:
- Always use specific version tags (not
latestorlts) - Prefer
-slimor-bookworm-slimvariants for production - Use Alpine only when you understand the trade-offs
- Test native dependencies before switching to Alpine
Kubernetes Best Practices
1. Set Resource Limits
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9.9
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
'''
}
}
2. Use Service Accounts
agent {
kubernetes {
yaml '''
spec:
serviceAccountName: jenkins-agent
'''
}
}
Testing Best Practices
1. Always Publish Test Results
post {
always {
junit '**/target/surefire-reports/*.xml'
publishHTML([
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
2. Archive Artifacts
post {
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
3. Separate Build and Test Stages
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
}
Notification Best Practices
Send Notifications for Important Events
post {
failure {
slackSend(
color: 'danger',
message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
fixed {
slackSend(
color: 'good',
message: "Build FIXED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
}
Include Relevant Information
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}
"""
}
}
Multi-Branch Pipeline Best Practices
Use Branch-Specific Logic
stage('Deploy') {
when {
branch 'main'
}
steps {
sh 'deploy-production.sh'
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh 'deploy-staging.sh'
}
}
Use PR Triggers
stage('PR Validation') {
when {
changeRequest()
}
steps {
sh 'run-pr-checks.sh'
}
}
Input Best Practices
Free Agents During Input
Good (input outside agent):
stage('Approval') {
input {
message 'Deploy to production?'
ok 'Deploy'
submitter 'admin,ops-team'
}
steps {
sh './deploy.sh'
}
}
Bad (holds agent during input):
stage('Approval') {
steps {
input 'Deploy to production?'
sh './deploy.sh'
}
}
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 descriptive stage names
- Configure build discarder
- Use Docker for consistent build environment
- Set resource limits for Kubernetes pods
- Validate user input
- Use least-privilege credentials
- Free agents during input
References
Common Jenkins Plugins - Generator Reference
Quick reference for generating Jenkinsfiles with popular plugin steps.
Table of Contents
- Git Plugin
- Docker Plugin
- Kubernetes Plugin
- Credentials Plugin
- Pipeline Utility Steps
- JUnit Plugin
- Slack Notification Plugin
- Email Extension Plugin
- Build Timeout Plugin
- Workspace Cleanup Plugin
- AWS Steps Plugin
- Azure CLI Plugin
- SonarQube Plugin
- HTTP Request Plugin
- Microsoft Teams Notification Plugin
- Nexus Artifact Uploader Plugin
- Artifactory Plugin
- OWASP Dependency-Check Plugin
- GitHub Plugin
Git Plugin
Basic Checkout
// Auto-detect SCM
checkout scm
// Explicit URL
git branch: 'main', url: 'https://github.com/user/repo.git'
// With credentials
git branch: 'main',
url: 'https://github.com/user/repo.git',
credentialsId: 'github-credentials'
Advanced Checkout
checkout scmGit(
branches: [[name: '*/main']],
userRemoteConfigs: [[
url: 'https://github.com/user/repo.git',
credentialsId: 'github-credentials'
]],
extensions: [
cloneOption(shallow: true, depth: 1),
submodule(recursiveSubmodules: true)
]
)
Git Environment Variables
GIT_COMMIT- Current commit hashGIT_BRANCH- Branch nameGIT_URL- Repository URLGIT_AUTHOR_NAME- Commit author
Docker Plugin
Docker Agent (Declarative)
agent {
docker {
image 'maven:3.9.11-eclipse-temurin-21'
args '-v $HOME/.m2:/root/.m2'
reuseNode true
}
}
Docker Agent with Dockerfile
agent {
dockerfile {
filename 'Dockerfile.build'
dir 'docker'
additionalBuildArgs '--build-arg VERSION=1.0'
}
}
Docker in Scripted Pipeline
node {
docker.image('maven:3.9.11').inside('-v $HOME/.m2:/root/.m2') {
sh 'mvn clean package'
}
}
Build and Push Docker Image
node {
def image = docker.build("myapp:${env.BUILD_NUMBER}")
docker.withRegistry('https://registry.example.com', 'docker-credentials') {
image.push()
image.push('latest')
}
}
Sidecar Container
docker.image('mysql:8').withRun('-e MYSQL_ROOT_PASSWORD=secret') { db ->
docker.image('maven:3.9.11').inside("--link ${db.id}:mysql") {
sh 'mvn verify'
}
}
Kubernetes Plugin
Pod Template (Declarative)
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9.11-eclipse-temurin-21
command: ['sleep']
args: ['99d']
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
- name: docker
image: docker:latest
command: ['sleep']
args: ['99d']
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock
'''
}
}
Container Step
stage('Build') {
steps {
container('maven') {
sh 'mvn clean package'
}
}
}
Scripted Pod Template
podTemplate(
containers: [
containerTemplate(name: 'maven', image: 'maven:3.9.11', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'kubectl', image: 'bitnami/kubectl:latest', ttyEnabled: true, command: 'cat')
],
volumes: [
secretVolume(secretName: 'kubeconfig', mountPath: '/root/.kube')
]
) {
node(POD_LABEL) {
container('maven') {
sh 'mvn clean package'
}
}
}
Credentials Plugin
Username/Password
withCredentials([usernamePassword(
credentialsId: 'docker-hub',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS'
}
Secret Text
withCredentials([string(credentialsId: 'api-token', variable: 'API_TOKEN')]) {
sh 'curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com'
}
SSH 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 Credential
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
sh 'kubectl --kubeconfig=$KUBECONFIG get pods'
}
Environment Binding (Declarative)
environment {
DOCKER_CREDENTIALS = credentials('docker-hub-credentials')
// Creates DOCKER_CREDENTIALS_USR and DOCKER_CREDENTIALS_PSW
API_KEY = credentials('api-key')
}
Pipeline Utility Steps
File Operations
// 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')
// 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'])
// Check if file exists
if (fileExists('path/to/file')) {
echo 'File exists'
}
// Find files
def files = findFiles(glob: '**/*.jar')
ZIP Operations
// Create ZIP
zip(zipFile: 'archive.zip', dir: 'target')
// Unzip
unzip(zipFile: 'archive.zip', dir: 'output')
JUnit Plugin
post {
always {
junit(
testResults: '**/target/surefire-reports/*.xml',
allowEmptyResults: true,
keepLongStdio: true
)
}
}
Slack Notification Plugin
// 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}\nStatus: ${currentBuild.result}",
channel: '#builds',
tokenCredentialId: 'slack-token'
)
// Post conditions
post {
success {
slackSend color: 'good', message: "Build ${env.BUILD_NUMBER} succeeded"
}
failure {
slackSend color: 'danger', message: "Build ${env.BUILD_NUMBER} failed"
}
}
Email Extension Plugin
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>
""",
to: 'team@example.com',
mimeType: 'text/html',
attachLog: true
)
// With recipient providers
post {
failure {
emailext(
subject: "Build Failed: ${env.JOB_NAME}",
body: "Check ${env.BUILD_URL}",
recipientProviders: [developers(), culprits(), requestor()]
)
}
}
Build Timeout Plugin
// Declarative
options {
timeout(time: 1, unit: 'HOURS')
}
// Per-stage
stage('Long Running') {
options {
timeout(time: 30, unit: 'MINUTES')
}
steps {
sh './long-task.sh'
}
}
// Scripted
timeout(time: 30, unit: 'MINUTES') {
node {
// steps
}
}
Workspace Cleanup Plugin
// Clean workspace
cleanWs()
// In post block
post {
always {
cleanWs()
}
}
// With options
cleanWs(
deleteDirs: true,
patterns: [
[pattern: 'target', type: 'INCLUDE'],
[pattern: '*.log', type: 'INCLUDE']
]
)
// Simple delete
deleteDir()
AWS Steps Plugin
withAWS(credentials: 'aws-credentials', region: 'us-east-1') {
// S3 operations
s3Upload(bucket: 'my-bucket', path: 'artifacts/', includePathPattern: '**/*.jar')
s3Download(bucket: 'my-bucket', path: 'config/', file: 'config.json')
// ECR login
def login = ecrLogin()
sh "${login}"
// ECS deploy
ecsDeployTaskDefinition(taskDefinition: 'my-task', cluster: 'my-cluster')
}
Azure CLI Plugin
withCredentials([azureServicePrincipal('azure-sp')]) {
sh '''
az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID
az account set --subscription $AZURE_SUBSCRIPTION_ID
az webapp deploy --resource-group mygroup --name myapp --src-path app.zip
'''
}
SonarQube Plugin
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('sonarqube-server') {
sh 'mvn sonar:sonar'
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
Common Gotcha: Ensure the SonarQube Server URL in Jenkins configuration does NOT have a trailing slash (e.g., http://sonarqube:9000 not http://sonarqube:9000/).
HTTP Request Plugin
Make HTTP/HTTPS requests from pipeline with full control over method, headers, and response handling.
Basic GET Request
def response = httpRequest 'https://api.example.com/status'
echo "Status: ${response.status}"
echo "Content: ${response.content}"
POST with JSON Body
def response = httpRequest(
url: 'https://api.example.com/deploy',
httpMode: 'POST',
contentType: 'APPLICATION_JSON',
requestBody: '{"environment": "production", "version": "1.0.0"}',
validResponseCodes: '200:299'
)
With Authentication
withCredentials([string(credentialsId: 'api-token', variable: 'API_TOKEN')]) {
def response = httpRequest(
url: 'https://api.example.com/data',
httpMode: 'GET',
customHeaders: [[name: 'Authorization', value: "Bearer ${API_TOKEN}"]],
timeout: 30
)
}
Response Handling Options
// Don't read response body (for large responses)
def response = httpRequest(
url: 'https://api.example.com/large-file',
responseHandle: 'NONE'
)
// Keep connection open for streaming
def response = httpRequest(
url: 'https://api.example.com/stream',
responseHandle: 'LEAVE_OPEN'
)
// Must close manually:
response.close()
Advanced Options
def response = httpRequest(
url: 'https://api.example.com/upload',
httpMode: 'PUT',
uploadFile: './report.html',
validResponseCodes: '200,201,204',
ignoreSslErrors: true,
httpProxy: 'http://proxy.local:8080',
timeout: 60,
consoleLogResponseBody: true
)
HTTP Methods Available
GET- Retrieve data (default)POST- Submit dataPUT- Update/upload resourcePATCH- Partial updateDELETE- Remove resourceHEAD- Retrieve headers onlyOPTIONS- Query available methods
Microsoft Teams Notification Plugin
// Simple notification
office365ConnectorSend(
webhookUrl: 'https://outlook.office.com/webhook/...',
message: 'Build completed!',
color: '00FF00'
)
// With card formatting
office365ConnectorSend(
webhookUrl: "${TEAMS_WEBHOOK}",
message: "Build ${currentBuild.result}",
status: currentBuild.result,
factDefinitions: [
[name: 'Job', value: env.JOB_NAME],
[name: 'Build', value: "#${env.BUILD_NUMBER}"],
[name: 'Duration', value: "${currentBuild.durationString}"]
],
potentialAction: [[
'@type': 'OpenUri',
'name': 'View Build',
'targets': [[
'os': 'default',
'uri': env.BUILD_URL
]]
]]
)
// In post block
post {
success {
office365ConnectorSend(
webhookUrl: "${TEAMS_WEBHOOK}",
message: "Build succeeded",
color: '00FF00'
)
}
failure {
office365ConnectorSend(
webhookUrl: "${TEAMS_WEBHOOK}",
message: "Build failed",
color: 'FF0000'
)
}
}
Nexus Artifact Uploader Plugin
nexusArtifactUploader(
nexusVersion: 'nexus3',
protocol: 'https',
nexusUrl: 'nexus.example.com',
repository: 'maven-releases',
credentialsId: 'nexus-credentials',
groupId: 'com.example',
version: '1.0.0',
artifacts: [
[artifactId: 'myapp', classifier: '', file: 'target/myapp.jar', type: 'jar'],
[artifactId: 'myapp', classifier: '', file: 'pom.xml', type: 'pom']
]
)
Artifactory Plugin
// Configure Artifactory server
def server = Artifactory.server('artifactory-server')
def uploadSpec = """{
"files": [{
"pattern": "target/*.jar",
"target": "libs-release-local/com/example/myapp/1.0.0/"
}]
}"""
// Upload
server.upload(uploadSpec)
// Download
def downloadSpec = """{
"files": [{
"pattern": "libs-release-local/com/example/myapp/1.0.0/*.jar",
"target": "dependencies/"
}]
}"""
server.download(downloadSpec)
// Publish build info
def buildInfo = Artifactory.newBuildInfo()
server.upload spec: uploadSpec, buildInfo: buildInfo
server.publishBuildInfo buildInfo
OWASP Dependency-Check Plugin
stage('Dependency Check') {
steps {
dependencyCheck(
additionalArguments: '''
--scan .
--format HTML
--format XML
--format JSON
--out dependency-check-report
--suppression suppression.xml
--failOnCVSS 7
''',
odcInstallation: 'OWASP-Dependency-Check'
)
}
post {
always {
dependencyCheckPublisher(
pattern: '**/dependency-check-report.xml',
failedTotalCritical: 0,
failedTotalHigh: 5,
unstableTotalMedium: 10
)
}
}
}
GitHub Plugin
Set Commit Status
// Using step
githubNotify(
status: 'PENDING',
description: 'Build in progress',
context: 'jenkins/build'
)
// After build
post {
success {
githubNotify status: 'SUCCESS', description: 'Build passed'
}
failure {
githubNotify status: 'FAILURE', description: 'Build failed'
}
}
Create/Update PR Comment
// Using GitHub API via sh
withCredentials([string(credentialsId: 'github-token', variable: 'GITHUB_TOKEN')]) {
sh '''
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/owner/repo/issues/${CHANGE_ID}/comments \
-d '{"body": "Build succeeded!"}'
'''
}
Common Build Steps
Archive Artifacts
archiveArtifacts(
artifacts: '**/*.jar',
fingerprint: true,
onlyIfSuccessful: true
)
Stash/Unstash
// Stash
stash(name: 'build-artifacts', includes: 'target/*.jar')
// Unstash
unstash 'build-artifacts'
Build 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']),
string(name: 'VERSION', defaultValue: '1.0')
],
submitter: 'admin,ops'
)
Retry
retry(3) {
sh 'flaky-command'
}
Sleep
sleep(time: 30, unit: 'SECONDS')
Plugin Documentation Lookup
For unlisted plugins:
- Context7: Search for
/jenkinsci/<plugin-name>-plugin - Web Search: “Jenkins
<plugin-name>plugin documentation” - Official Plugins: https://plugins.jenkins.io/
- Pipeline Steps: https://www.jenkins.io/doc/pipeline/steps/