Build Your First Jenkins Declarative Pipeline for CI/CD

Unlock the power of automated software delivery by building your first Jenkins Declarative Pipeline. This comprehensive guide provides a step-by-step tutorial covering the entire CI/CD workflow, from integrating source code management to defining secure build, testing, and conditional deployment stages. Learn the fundamental structure of the `Jenkinsfile` and implement best practices for handling agents and credentials, ensuring robust and maintainable automation for your projects.

35 views

Build Your First Jenkins Declarative Pipeline for CI/CD

Jenkins is the de facto standard for open-source automation servers, serving as the backbone for continuous integration and continuous delivery (CI/CD) pipelines across thousands of organizations. While Jenkins offers two types of pipeline syntax—Scripted and Declarative—the Declarative Pipeline syntax is the recommended approach for most users due to its simpler, structured, and more readable Groovy DSL.

This guide provides a comprehensive, step-by-step tutorial on building your very first functional Jenkins Declarative Pipeline. We will cover integrating source code management (SCM), defining the build environment, executing testing, and implementing a sample deployment stage, giving you a strong foundation for automating your software delivery lifecycle.

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.

The 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 -t ${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 rmi -f $(docker images -aq --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 "docker login -u ${USER} -p ${PASS} ${CONTAINER_REGISTRY}"
        }
    }
}

Warning: 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.

Conclusion

The Jenkins Declarative Pipeline offers a powerful, readable, and maintainable framework for implementing Continuous Integration and Continuous Delivery. By defining the workflow structure—including agents, environment variables, sequential stages, and post-build actions—you gain complete visibility and control over your software automation process. With this foundational Jenkinsfile established, you are now ready to integrate more complex steps, such as automated security scanning, artifact publishing, and multi-environment promotions.