CtrlK
BlogDocsLog inGet started
Tessl Logo

pantheon-ai/jenkinsfile-toolkit

Complete jenkinsfile toolkit with generation and validation capabilities

97

Quality

97%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Overview
Quality
Evals
Security
Files

best_practices.mdgenerator/references/

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:

ParameterValuesDescription
buildResultSUCCESS, UNSTABLE, FAILURE, NOT_BUILT, ABORTEDOverall build result on error
stageResultSUCCESS, UNSTABLE, FAILURE, NOT_BUILT, ABORTEDStage result on error
messageStringMessage logged on error
catchInterruptionstrue/falseWhether 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 TypeRecommendationUse Case
node:22-bookworm-slimRecommended for productionMinimal size, stable Debian base
node:22-alpineUse with cautionSmallest size, but Alpine is experimental in Node.js
node:22Development onlyLarge image, includes unnecessary tools
node:ltsAvoid in CI/CDTag 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:

  1. Always use specific version tags (not latest or lts)
  2. Prefer -slim or -bookworm-slim variants for production
  3. Use Alpine only when you understand the trade-offs
  4. 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

generator

SKILL.md

tile.json