How to Use Jenkins Pipelines for Continuous Delivery

How to Use Jenkins Pipelines for Continuous Delivery

Continuous Delivery is one of those terms that gets thrown around a lot, but actually implementing it well takes real thought about how your pipeline is structured. I’ve built more than a few Jenkins pipelines over the years, and the ones that work best treat every stage — build, test, staging deploy, approval, production deploy — as a first-class citizen with clear visibility. In this article, I’ll walk through how to design and implement Jenkins Pipelines specifically for Continuous Delivery (CD).

Continuous Integration vs Continuous Delivery vs Continuous Deployment

Before diving in, it’s worth being precise about terminology:

  • Continuous Integration (CI): Automatically building and testing code every time it changes.
  • Continuous Delivery (CD): Extending CI so that every change that passes tests is automatically prepared for release — but a human still approves the final production deployment.
  • Continuous Deployment: Going one step further, where passing changes are deployed to production automatically, with no manual gate.

This article focuses on Continuous Delivery — automated up to the point of production, with an explicit approval gate before going live.

Jenkins Pipeline Fundamentals

Jenkins Pipeline is a suite of plugins that lets you define your entire build/test/deploy process as code, stored in a Jenkinsfile in your repository. There are two syntaxes:

  • Declarative Pipeline: Structured, easier to read, recommended for most use cases.
  • Scripted Pipeline: Full Groovy scripting flexibility, used for complex or dynamic logic.

I’ll focus mainly on Declarative syntax here since it covers the vast majority of real-world CD needs.

Jenkins Architecture for CD

For Continuous Delivery, your Jenkins setup typically includes:

  • Controller: Orchestrates the pipeline stages.
  • Multiple Agents: Often separate agents for build/test versus deployment, sometimes with different capabilities (e.g., Docker-enabled agents, cloud-access agents).
  • Artifact Repository: Stores build outputs (Docker images, JAR files, etc.) between stages — think Nexus, Artifactory, or a container registry.
  • Environments: Distinct staging and production environments that the pipeline deploys to sequentially.

Step 1: Structuring a Multi-Stage Jenkinsfile

Here’s a complete example modeling a realistic Continuous Delivery pipeline:

pipeline {
    agent any

    environment {
        IMAGE_NAME = "yourdockerhub/app"
        IMAGE_TAG = "${env.BUILD_NUMBER}"
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                sh 'npm ci && npm run build'
            }
        }

        stage('Unit Tests') {
            steps {
                sh 'npm test'
            }
            post {
                always {
                    junit 'reports/junit.xml'
                }
            }
        }

        stage('Build Docker Image') {
            steps {
                sh 'docker build -t $IMAGE_NAME:$IMAGE_TAG .'
            }
        }

        stage('Push to Registry') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'docker-creds', usernameVariable: 'USER', passwordVariable: 'PASS')]) {
                    sh 'echo $PASS | docker login -u $USER --password-stdin'
                    sh 'docker push $IMAGE_NAME:$IMAGE_TAG'
                }
            }
        }

        stage('Deploy to Staging') {
            steps {
                sh 'kubectl set image deployment/app app=$IMAGE_NAME:$IMAGE_TAG -n staging'
            }
        }

        stage('Integration Tests on Staging') {
            steps {
                sh './run_integration_tests.sh staging'
            }
        }

        stage('Approval for Production') {
            steps {
                input message: 'Deploy to production?', ok: 'Deploy'
            }
        }

        stage('Deploy to Production') {
            steps {
                sh 'kubectl set image deployment/app app=$IMAGE_NAME:$IMAGE_TAG -n production'
            }
        }
    }

    post {
        success {
            echo 'Pipeline completed successfully!'
        }
        failure {
            echo 'Pipeline failed — check the logs.'
        }
    }
}

The input step is the heart of Continuous Delivery — it pauses the pipeline and waits for a human to click “Deploy” before touching production. Everything before that point runs automatically.

Step 2: Using Parallel Stages for Speed

Continuous Delivery pipelines can get slow if every stage runs sequentially. Use parallel to run independent tasks simultaneously:

stage('Quality Checks') {
    parallel {
        stage('Unit Tests') {
            steps { sh 'npm test' }
        }
        stage('Lint') {
            steps { sh 'npm run lint' }
        }
        stage('Security Scan') {
            steps { sh 'npm audit --audit-level=high' }
        }
    }
}

Step 3: Managing Environments and Secrets

Store environment-specific configuration (staging vs production credentials, URLs, etc.) using Jenkins Credentials combined with environment-scoped variables, or externalize them into a tool like HashiCorp Vault accessed via the Vault Plugin.

environment {
    DB_PASSWORD = credentials('prod-db-password')
}

Step 4: Integrating Infrastructure as Code

Many CD pipelines also apply infrastructure changes as part of the delivery process using Terraform or Ansible:

stage('Provision Infrastructure') {
    steps {
        sh 'terraform init'
        sh 'terraform plan -out=tfplan'
        sh 'terraform apply tfplan'
    }
}

Combining application deployment with infrastructure provisioning in the same pipeline (or a closely coupled one) ensures your environment and your code stay in sync.

Step 5: Rollback Strategy

A solid Continuous Delivery setup includes a rollback plan. With Kubernetes, this is often as simple as:

stage('Rollback') {
    when {
        expression { currentBuild.result == 'FAILURE' }
    }
    steps {
        sh 'kubectl rollout undo deployment/app -n production'
    }
}

Monitoring and Feedback Loops

Once deployed, monitoring tools like Prometheus, Grafana, or Datadog should feed back into your delivery process. Some teams add a post-deployment health check stage:

stage('Post-Deploy Health Check') {
    steps {
        sh 'curl -f https://yourapp.com/health || exit 1'
    }
}

If this fails, the pipeline can automatically trigger the rollback stage above.

Real-World Continuous Delivery Scenario

Here’s a pattern I’ve seen work well for a mid-sized SaaS team:

  1. Every PR triggers unit tests and lint checks.
  2. Merges to main trigger the full pipeline: build, Dockerize, push, deploy to staging, run integration tests.
  3. A Slack notification alerts the team that staging is ready and awaiting approval.
  4. A release manager reviews and clicks “Deploy” in Jenkins.
  5. Production deployment happens with an automatic rollback safety net if health checks fail.

Blue-Green and Canary Deployment Strategies

More mature Continuous Delivery setups often go beyond a simple “deploy and hope” model. Two common strategies I’ve implemented in Jenkins pipelines:

Blue-Green Deployment: Maintain two identical production environments (blue and green). Deploy the new version to the idle environment, run smoke tests, then switch traffic over:

stage('Deploy to Green') {
    steps {
        sh 'kubectl apply -f k8s/green-deployment.yaml'
    }
}
stage('Switch Traffic') {
    steps {
        sh 'kubectl patch service app-service -p \'{"spec":{"selector":{"version":"green"}}}\''
    }
}

Canary Deployment: Gradually shift a small percentage of traffic to the new version, monitoring error rates before a full rollout:

stage('Canary Deploy') {
    steps {
        sh 'kubectl apply -f k8s/canary-deployment.yaml'
        sh 'sleep 300'
        sh './check_canary_metrics.sh'
    }
}

Both strategies dramatically reduce the blast radius of a bad deployment compared to simply overwriting production directly.

Notifications and Visibility Throughout the Pipeline

A Continuous Delivery pipeline that runs silently in the background isn’t very useful to a team that needs to act on its results. I typically add Slack or email notifications at key transition points:

post {
    success {
        slackSend channel: '#deployments', message: "✅ Deployed ${env.IMAGE_TAG} to production"
    }
    failure {
        slackSend channel: '#deployments', message: "❌ Pipeline failed at stage: ${env.STAGE_NAME}"
    }
}

Troubleshooting Common CD Pipeline Issues

  • Pipeline hangs indefinitely at the approval step: This is expected behavior — input will wait until someone interacts with it, so consider adding a timeout.
input message: 'Deploy to production?', ok: 'Deploy'
options {
    timeout(time: 24, unit: 'HOURS')
}
  • Docker push failures: Verify registry credentials haven’t expired and the agent has Docker installed and running.
  • Flaky integration tests: Isolate test data and environments per run to avoid cross-test contamination.

Security Best Practices

  • Require manual approval before any production-impacting stage.
  • Use role-based access control so only authorized users can approve production deployments.
  • Scan Docker images for vulnerabilities before pushing to a registry (e.g., Trivy or Snyk).
  • Audit pipeline logs regularly, since they often reveal misconfigurations or repeated failed deployment attempts.

Feature Flags as a Complement to Continuous Delivery

Many teams pair their Jenkins-driven Continuous Delivery pipeline with feature flags, letting code ship to production behind a flag that’s toggled separately from the deployment itself. This decouples “deploying code” from “releasing a feature,” giving product teams finer control over rollout timing without needing a new pipeline run for every toggle.

FAQs

Q: What’s the difference between Continuous Delivery and Continuous Deployment in a Jenkinsfile? Continuous Delivery includes a manual input gate before production; Continuous Deployment removes it, deploying automatically once all checks pass.

Q: Can I use Jenkins Pipelines with Kubernetes-based agents? Yes, using the Kubernetes Plugin, Jenkins can dynamically spin up pod-based agents for each pipeline run, which is great for scalability.

Q: How do I notify my team about pipeline status? Use plugins like Slack Notification or email-ext to send messages at key stages (start, approval needed, success, failure).

Q: Is Declarative or Scripted Pipeline better for CD? Declarative is recommended for most teams due to its readability and built-in structure; Scripted is useful for highly dynamic or complex logic that Declarative syntax can’t easily express.

Summary

Jenkins Pipelines give you a code-based, version-controlled way to define your entire Continuous Delivery process — from build through staging deployment to a controlled, human-approved production release. By combining declarative stages, parallel execution, proper secret management, and a solid rollback strategy, you get a delivery pipeline that’s both fast and safe.

References

Total
1
Shares

Leave a Reply

Previous Post
How to Create a Freestyle Project in Jenkins

How to Create a Freestyle Project in Jenkins

Next Post
How to Schedule Jenkins Jobs

How to Schedule Jenkins Jobs

Related Posts