Complete jenkinsfile toolkit with generation and validation capabilities
97
97%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Risky
Do not use without reviewing
Quick reference for generating best-practice Jenkinsfiles.
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
'''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()Bad:
def logFile = readFile('huge-log.txt') // Loads entire fileGood:
def errorCount = sh(script: 'grep ERROR huge-log.txt | wc -l', returnStdout: true).trim()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'
}environment {
DOCKER_CREDENTIALS = credentials('docker-hub-credentials')
// Creates DOCKER_CREDENTIALS_USR and DOCKER_CREDENTIALS_PSW
API_KEY = credentials('api-key')
}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}"
}// Pipeline level
options {
timeout(time: 1, unit: 'HOURS')
}
// Stage level
stage('Long Running') {
options {
timeout(time: 30, unit: 'MINUTES')
}
steps {
sh './long-task.sh'
}
}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()
}
}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) |
post {
always {
cleanWs()
}
}
// Or use deleteDir()
post {
cleanup {
deleteDir()
}
}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'
}
}Bad:
stage('Step 1') { }
stage('Step 2') { }Good:
stage('Build Application') { }
stage('Run Unit Tests') { }
stage('Build Docker Image') { }
stage('Deploy to Staging') { }stages {
stage('Build') {
stages {
stage('Compile') { }
stage('Package') { }
}
}
stage('Quality Checks') {
parallel {
stage('Unit Tests') { }
stage('Integration Tests') { }
stage('Code Analysis') { }
}
}
}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' }
}
}
}stage('Deploy') {
failFast true
parallel {
stage('Region 1') { }
stage('Region 2') { }
stage('Region 3') { }
}
}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
}agent {
docker {
image 'maven:3.9.9-eclipse-temurin-21'
args '-v $HOME/.m2:/root/.m2'
reuseNode true
}
}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'
}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')
}
}
}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?
// 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' }
}// 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' }
}// Alpine works well for Go (statically compiled)
agent {
docker { image 'golang:1.23-alpine' }
}General Rules:
latest or lts)-slim or -bookworm-slim variants for productionagent {
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"
'''
}
}agent {
kubernetes {
yaml '''
spec:
serviceAccountName: jenkins-agent
'''
}
}post {
always {
junit '**/target/surefire-reports/*.xml'
publishHTML([
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}post {
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
}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}"
)
}
}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}
"""
}
}stage('Deploy') {
when {
branch 'main'
}
steps {
sh 'deploy-production.sh'
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh 'deploy-staging.sh'
}
}stage('PR Validation') {
when {
changeRequest()
}
steps {
sh 'run-pr-checks.sh'
}
}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'
}
}