Skip to main content

Jenkins Declarative Pipelines — Jenkinsfile for Production Workloads

Write production-grade declarative Jenkinsfiles with multi-stage pipelines, parallel execution, shared libraries, Docker agent builds, and credential management.

22 Terms

Overview and What You Will Learn

Jenkins is the dominant CI platform in enterprise environments -- banks, insurance companies, large Indian enterprises running on-premise infrastructure for compliance. Despite cloud-native alternatives, Jenkins remains the answer when you need complete control over your CI infrastructure, when your company policy prohibits cloud CI runners, or when you are working in an organisation with a decade of Jenkins investment.

By the end of this lab you will:

  • Write a complete declarative Jenkinsfile from scratch
  • Configure multi-stage pipelines with parallel execution
  • Use Docker agents to run each stage in the correct container
  • Bind Jenkins credentials to pipeline environment variables securely
  • Write and use Jenkins shared libraries for reusable pipeline code
  • Configure post-conditions for notifications and cleanup

Why This Matters in Production

Many Indian enterprises -- large banks, NBFCs, insurance companies -- run Jenkins on-premise because their compliance requirements prohibit sending source code or build artifacts to cloud CI providers. Engineers joining these teams need Jenkins proficiency from day one. Understanding declarative Jenkinsfiles is how you work effectively in these environments without fighting the tooling.

Core Principles

Jenkins architecture -- controller and agents:

Bash
+------------------------------------------+
| Jenkins Controller |
| Web UI, job scheduling, credential store |
+------------------------------------------+
| | |
v v v
+----------+ +----------+ +----------+
| Agent 1 | | Agent 2 | | Agent 3 |
| static | | docker | | k8s pod |
| VM | | executor | | dynamic |
+----------+ +----------+ +----------+
Each job runs on ONE agent.
Parallel stages run on DIFFERENT agents simultaneously.
Dynamic K8s agents spin up per build, terminate after.

Declarative Jenkinsfile structure:

Bash
pipeline {
agent none <- per-stage agents
environment { } <- global env vars
stages {
stage('Build') {
agent { docker { image '...' } }
steps { sh '...' }
}
stage('Test') {
parallel { <- parallel stages
stage('Unit') { ... }
stage('Lint') { ... }
}
}
}
post { <- always runs
success { }
failure { }
always { cleanWs() }
}
}

Detailed Step-by-Step Practical Lab

Milestone 1 -- First declarative Jenkinsfile

GROOVY
// Jenkinsfile -- store in repository root
pipeline {
agent none
environment {
ECR_REGISTRY = '123456789.dkr.ecr.ap-south-1.amazonaws.com'
IMAGE_NAME = 'payment-api'
AWS_CREDS = credentials('aws-ecr-credentials')
}
options {
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
disableConcurrentBuilds()
}
stages {
stage('Build') {
agent { docker { image 'docker:24-cli'
args '-v /var/run/docker.sock:/var/run/docker.sock' } }
steps {
script {
def imageTag = "${ECR_REGISTRY}/${IMAGE_NAME}:${GIT_COMMIT}"
sh """
docker build -t ${imageTag} .
echo ${AWS_CREDS_PSW} | docker login \\
--username AWS --password-stdin ${ECR_REGISTRY}
docker push ${imageTag}
"""
env.IMAGE_TAG = imageTag
}
}
}
}
}

Milestone 2 -- Parallel stages

GROOVY
stage('Validate') {
parallel {
stage('Unit Tests') {
agent { docker { image 'node:20-alpine' } }
steps {
sh 'npm ci'
sh 'npm test -- --reporter=junit --outputFile=junit.xml'
}
post {
always { junit 'junit.xml' }
}
}
stage('Lint') {
agent { docker { image 'node:20-alpine' } }
steps {
sh 'npm ci && npm run lint'
}
}
stage('Security Scan') {
agent { docker { image 'aquasec/trivy:latest'
args '--entrypoint=' } }
steps {
sh "trivy image --exit-code 1 --severity HIGH,CRITICAL ${env.IMAGE_TAG}"
}
}
}
}

Milestone 3 -- Credential binding and secrets

GROOVY
stage('Deploy Staging') {
agent { label 'k8s-deployer' }
when { branch 'main' }
steps {
withCredentials([
string(credentialsId: 'kubeconfig-staging',
variable: 'KUBECONFIG_DATA'),
string(credentialsId: 'slack-webhook',
variable: 'SLACK_WEBHOOK')
]) {
sh '''
echo "$KUBECONFIG_DATA" > /tmp/kubeconfig
export KUBECONFIG=/tmp/kubeconfig
helm upgrade --install payment-api ./charts/payment-api \\
--namespace payment-api-staging \\
--set image.tag=$GIT_COMMIT \\
--atomic --timeout 5m --wait
rm -f /tmp/kubeconfig
'''
}
}
}
stage('Deploy Production') {
agent { label 'k8s-deployer' }
when { branch 'main' }
input {
message 'Deploy to production?'
ok 'Deploy Now'
submitter 'tech-leads,senior-engineers'
}
steps {
withCredentials([
string(credentialsId: 'kubeconfig-production',
variable: 'KUBECONFIG_DATA')
]) {
sh '''
echo "$KUBECONFIG_DATA" > /tmp/kubeconfig
export KUBECONFIG=/tmp/kubeconfig
helm upgrade --install payment-api ./charts/payment-api \\
--namespace payment-api-production \\
--set image.tag=$GIT_COMMIT \\
--atomic --timeout 10m --wait
rm -f /tmp/kubeconfig
'''
}
}
}

Milestone 4 -- Post conditions for notifications

GROOVY
post {
success {
slackSend(
channel: '#deployments',
color: 'good',
message: "SUCCESS: ${env.JOB_NAME} #${env.BUILD_NUMBER} | ${env.GIT_COMMIT[0..7]}"
)
}
failure {
slackSend(
channel: '#deployments',
color: 'danger',
message: "FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER} | ${env.BUILD_URL}console"
)
}
always {
cleanWs()
}
}

Milestone 5 -- Jenkins shared libraries

TEXT
// Shared library repository structure:
jenkins-shared-library/
vars/
deployToKubernetes.groovy
notifySlack.groovy
src/
com/company/HelmDeploy.groovy
GROOVY
// vars/deployToKubernetes.groovy
def call(Map config) {
def namespace = config.namespace ?: 'default'
def imageTag = config.imageTag ?: error('imageTag required')
def chart = config.chart ?: './charts/app'
def valuesFile = config.valuesFile ?: 'values.yaml'
sh """
helm upgrade --install ${config.releaseName} ${chart} \\
--namespace ${namespace} \\
--values ${valuesFile} \\
--set image.tag=${imageTag} \\
--atomic --timeout 5m --wait
"""
}
// Using in Jenkinsfile:
@Library('jenkins-shared-library') _
pipeline {
stages {
stage('Deploy') {
steps {
deployToKubernetes(
releaseName: 'payment-api',
namespace: 'payment-api-staging',
imageTag: env.GIT_COMMIT,
valuesFile: 'charts/values-staging.yaml'
)
}
}
}
}

Milestone 6 -- Dynamic Kubernetes agents

GROOVY
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: node
image: node:20-alpine
command: [sleep, infinity]
- name: docker
image: docker:24-cli
command: [sleep, infinity]
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
'''
}
}
stages {
stage('Test') {
steps {
container('node') { sh 'npm ci && npm test' }
}
}
stage('Build') {
steps {
container('docker') { sh 'docker build -t payment-api .' }
}
}
}
}

Production Best Practices and Common Pitfalls

Mistake Problem Fix
Using scripted pipeline Hard to read, no validation Always use declarative pipeline
Hardcoding credentials Secrets in Git history Use withCredentials() binding
Global agent declaration All stages on same machine Use agent none + per-stage agents
No workspace cleanup Disk fills up on agents Add cleanWs() in post { always {} }
No build timeout Hanging build blocks queue Set timeout(time: 30, unit: 'MINUTES')

Quick Reference and Troubleshooting Commands

Task Command
Validate Jenkinsfile syntax Jenkins UI > Pipeline Syntax > Declarative Linter
Replay last build Build > Replay (edit and rerun without commit)
Trigger via CLI java -jar jenkins-cli.jar build JOB_NAME
View console log curl $JENKINS_URL/job/JOB/lastBuild/consoleText
Abort a build java -jar jenkins-cli.jar stop-build JOB 42
List all jobs java -jar jenkins-cli.jar list-jobs
Tip

Use the Jenkins Pipeline Syntax generator at http://your-jenkins/pipeline-syntax/. Select any installed plugin step from the dropdown, fill in the form fields, and it generates the correct Groovy syntax. Far faster than reading plugin documentation for every step you need.

Remember

The input step pauses the build and holds a Jenkins executor slot. Set a timeout to prevent builds waiting indefinitely: timeout(time: 24, unit: 'HOURS') { input 'Deploy to production?' }. Without this, a forgotten approval gate can block all subsequent builds in the queue.

Security

Never use echo "${PASSWORD}" in a Jenkinsfile -- this prints the credential to the build log visible to anyone with read access. Jenkins masks credentials injected via withCredentials() but only for exact string matches. A base64-encoded version of the password will not be masked.

Common Mistake

Declaring a global agent at the top (agent { label 'linux' }) instead of agent none. A global agent runs ALL stages on the same machine, even stages needing different tools. Use agent none at the top and declare the appropriate agent per stage -- this enables parallel stages on different machines and proper tool isolation.

Resources

Explore More in CI/CD Tools and Platforms

All 3 Topics

Frequently Asked Questions

Is Jenkins Declarative Pipelines — Jenkinsfile for Production Workloads free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Jenkins Declarative Pipelines — Jenkinsfile for Production Workloads topic cover?

Write production-grade declarative Jenkinsfiles with multi-stage pipelines, parallel execution, shared libraries, Docker agent builds, and credential management.