Build Your First Jenkins Declarative Pipeline for CI/CD

Build a first Jenkins Declarative Pipeline with stages, agents, checkout, tests, deployment gates, credentials, and post actions.

Build Your First Jenkins Declarative Pipeline for CI/CD

Your first Jenkins Declarative Pipeline should answer a practical question: can Jenkins check out your code, build it, test it, and run deployment steps only when the right branch passes? A Jenkinsfile gives you that workflow as code in the same repository as your app.

Declarative syntax is a good starting point because it gives you a predictable structure: pipeline, agent, stages, steps, and post.

Understanding the Declarative Pipeline Structure

The Declarative Pipeline defines the entire workflow in a file named Jenkinsfile, which is stored alongside your application code in your SCM repository (e.g., Git). This practice is known as Pipeline as Code.

Core Elements of a Declarative Pipeline

A Declarative Pipeline must contain the following top-level blocks:

  1. pipeline: The mandatory outermost block defining the pipeline content.
  2. agent: Specifies where the pipeline or a specific stage will execute (e.g., any, none, or specific labels).
  3. stages: Contains one or more sequential stage blocks.
  4. stage: Defines a conceptual block of work (e.g., Build, Test, Deploy).
  5. steps: Contains one or more commands or functions executed within a stage.
pipeline {
    agent any 
    environment { // Optional: Defines environment variables
        APP_VERSION = '1.0.0'
    }
    stages {
        stage('Build') {
            steps {
                // Commands go here
            }
        }
    }
    post { // Optional: Actions run after the pipeline completes
        always { 
            echo 'Pipeline finished.' 
        }
    }
}

Step 1: Setting Up the Jenkins Job

To run a Pipeline from code, you need to configure a Jenkins job to read the Jenkinsfile from your repository.

  1. Navigate to your Jenkins dashboard and select New Item.
  2. Enter a name for your pipeline (e.g., my-first-ci-pipeline).
  3. Select the Pipeline item type and click OK.
  4. In the configuration page, scroll down to the Pipeline section.
  5. Change the definition from Pipeline script to Pipeline script from SCM.
  6. Select your SCM (e.g., Git).
  7. Enter the Repository URL and configure your credentials if necessary.
  8. Ensure the Script Path is set to Jenkinsfile (the default).

Step 2: Defining the Jenkinsfile for CI/CD

We will create a comprehensive Jenkinsfile that simulates a simple CI/CD workflow, integrating standard stages for a successful deployment.

Assume you have a repository structure containing your application source code (e.g., a simple Python or Java project) and the Jenkinsfile at the root.

Complete Declarative Pipeline Example

This pipeline uses the node agent (if configured) and utilizes basic shell steps (sh) to simulate real work. The deployment stage is conditional, running only upon successful completion of the previous stages.

// Jenkinsfile
pipeline {
    // 1. Agent Definition: Specifies where the entire pipeline runs
    agent { 
        label 'my-build-agent' // Use a specific label if available, or 'any'
    }
    
    // 2. Environment Variables: Define variables usable throughout the pipeline
    environment {
        CONTAINER_REGISTRY = 'registry.example.com'
        IMAGE_NAME = 'myapp'
    }
    
    stages {
        
        // Stage 1: Checkout (Source Code Management)
        stage('Checkout Source') {
            steps {
                // The 'checkout scm' step automatically checks out the code 
                // based on the configuration defined in the Jenkins job settings.
                checkout scm
                sh 'echo "Source code successfully checked out."'
            }
        }
        
        // Stage 2: Build Artifacts
        stage('Build Artifact') {
            steps {
                sh 'echo "Starting build for $IMAGE_NAME:$BUILD_ID"'
                // Simulation: Build a Docker image or compile a jar/war file
                sh "docker build --label build_id=${BUILD_ID} -t ${CONTAINER_REGISTRY}/${IMAGE_NAME}:${BUILD_ID} ."
            }
        }
        
        // Stage 3: Testing and Quality Gates
        stage('Run Tests') {
            steps {
                sh './run_unit_tests.sh'
                sh 'echo "Running integration tests..."'
                // Example of collecting test results (requires appropriate plugins)
                // junit '**/target/surefire-reports/*.xml'
            }
        }
        
        // Stage 4: Deployment (Conditional)
        stage('Deploy to Staging') {
            // The 'when' directive ensures the stage only runs under specific conditions
            when {
                branch 'main'
            }
            steps {
                sh "docker push ${CONTAINER_REGISTRY}/${IMAGE_NAME}:${BUILD_ID}"
                sh 'kubectl apply -f k8s/deployment-staging.yaml'
                script {
                    // Script blocks allow traditional Groovy logic within Declarative steps
                    echo "Deployment successful to Staging environment."
                }
            }
        }
    }
    
    // 3. Post Actions: Define actions based on the final pipeline status
    post {
        success {
            echo 'Pipeline completed successfully. Notifying team via Slack...'
            // slackSend channel: '#devops-alerts', message: 'CI/CD Success!'
        }
        failure {
            echo 'Pipeline failed. Review logs for errors.'
        }
        cleanup {
            sh 'docker image prune -f --filter "label=build_id=$BUILD_ID"'
        }
    }
}

Step 3: Running and Monitoring the Pipeline

Once the Jenkinsfile is committed and pushed to the branch configured in your Jenkins job:

  1. On the Jenkins job page, click Build Now (or wait for the SCM polling/webhook trigger).
  2. Monitor the build in the Build History panel.
  3. Click the running build number and select Console Output to view the execution details step-by-step.
  4. You can also use the Stage View visualization (if the plugin is installed) to see the progress of the Checkout, Build, Test, and Deploy stages graphically.

Best Practices and Advanced Features

Utilizing Libraries and Shared Code

For complex or highly repetitive steps, avoid writing verbose Groovy directly in the Jenkinsfile. Instead, use Shared Libraries. These external libraries allow you to define common functions (like standard deployment routines or notification functions) that can be called from multiple pipelines, making your Jenkinsfile cleaner.

// Calling a shared library step
stage('Custom Setup') {
    steps {
        customLibrary.initializeEnvironment(env: 'prod')
    }
}

Working with Credentials

Never hardcode sensitive information (passwords, tokens, API keys) directly into the Jenkinsfile. Use Jenkins' built-in Credentials Management system.

The withCredentials step allows you to securely access stored secrets:

stage('Authenticate Registry') {
    steps {
        withCredentials([usernamePassword(credentialsId: 'docker-registry-creds', 
                                          passwordVariable: 'PASS', 
                                          usernameVariable: 'USER')]) {
            sh '''
                printf '%s' "$PASS" | docker login \
                  --username "$USER" \
                  --password-stdin "$CONTAINER_REGISTRY"
            '''
        }
    }
}

Agent Selection

Tip: Always define your agent specifically using a label rather than agent any. Using agent any means the pipeline could run on the Jenkins controller node, which is a security risk and can impact controller performance. Ensure you have properly configured Jenkins agents (nodes) with necessary tools (Docker, Maven, Node.js) installed.

Takeaway

Keep the first pipeline boring and reliable: choose a real agent label, check out code, build, test, gate deployment by branch, and keep secrets in Jenkins credentials. Once that works, add artifact publishing, security scanning, and environment promotion one step at a time.