Jenkins
CI/CD Tool
Jenkins
Overview
Jenkins is the most established open-source CI/CD tool. It features rich plugins, high customizability, and on-premises support, providing flexible pipeline construction through Groovy DSL.
Details
Jenkins is an open-source automation server that began as Hudson in 2004 and was renamed to its current form in 2011. As the most established CI/CD tool, it has continued to evolve for over 20 years. Developed in Java/Groovy languages, it realizes "Pipeline as Code" through Jenkinsfiles. It supports both Declarative and Scripted pipeline syntaxes, enabling advanced control flow construction through Groovy DSL. Its greatest feature is high extensibility through over 1,800 plugins, enabling integration with virtually any tool including Git, Docker, Kubernetes, AWS, and Azure. Beyond automating build, test, and deployment, it can flexibly construct complex workflows including visual pipeline editing through the Blue Ocean plugin, distributed builds, parallel execution, and conditional branching. As a self-hosted solution, it provides complete control and offers advantages in custom enterprise implementations and confidential information management.
Pros and Cons
Pros
- Completely Free: Open source with unlimited access to all features
- Rich Plugin Ecosystem: High extensibility with over 1,800 plugins
- High Customizability: Flexible pipeline control through Groovy DSL
- Proven Track Record: Over 20 years of development history and enterprise adoption
- Complete Control: Self-hosted solution enabling custom modifications
- Strong Community: Active developer community and support
- Distributed Builds: Scalability through Master-Agent architecture
- Pipeline as Code: Version control support through Jenkinsfiles
Cons
- Operational Costs: Server management, maintenance, and updates required
- Learning Curve: Need to understand Groovy syntax and plugin ecosystem
- Configuration Complexity: Complex initial setup and configuration management due to rich features
- Outdated UI/UX: Traditional interface and user experience
- Security Management: Need to address plugin and configuration security vulnerabilities
- Performance: Memory consumption and response speed in large-scale environments
- Dependency Management: Plugin compatibility and version management
Key Links
- Jenkins Official Site
- Jenkins Official Documentation
- Jenkins Pipeline Documentation
- Jenkins Plugins Index
- Jenkins Blue Ocean
- Jenkins GitHub Repository
Code Examples
Basic Declarative Pipeline
// Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
options {
skipStagesAfterUnstable()
timeout(time: 1, unit: 'HOURS')
retry(3)
}
environment {
NODE_VERSION = '18'
DOCKER_IMAGE = 'myapp'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'npm install'
sh 'npm run build'
}
}
stage('Test') {
steps {
sh 'npm run test'
sh 'npm run lint'
}
post {
always {
publishTestResults testResultsPattern: 'test-results.xml'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
script {
sh 'npm run deploy'
}
}
}
}
post {
always {
cleanWs()
}
success {
echo 'Pipeline succeeded!'
}
failure {
echo 'Pipeline failed!'
emailext (
subject: "Jenkins Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "Build failed. Check console output at ${env.BUILD_URL}",
to: "${env.CHANGE_AUTHOR_EMAIL}"
)
}
}
}
Docker Integration Pipeline
// Jenkinsfile (Docker Integration)
pipeline {
agent none
stages {
stage('Build') {
agent {
docker {
image 'node:18-alpine'
args '-v /var/run/docker.sock:/var/run/docker.sock'
}
}
steps {
sh 'npm install'
sh 'npm run build'
stash includes: 'dist/**', name: 'build-artifacts'
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
agent {
docker {
image 'node:18-alpine'
}
}
steps {
unstash 'build-artifacts'
sh 'npm run test:unit'
}
post {
always {
junit 'test-results/unit.xml'
}
}
}
stage('Integration Tests') {
agent {
docker {
image 'node:18-alpine'
}
}
steps {
unstash 'build-artifacts'
sh 'npm run test:integration'
}
post {
always {
junit 'test-results/integration.xml'
}
}
}
}
}
stage('Build Docker Image') {
agent any
steps {
unstash 'build-artifacts'
script {
def image = docker.build("${env.DOCKER_IMAGE}:${env.BUILD_ID}")
docker.withRegistry('https://registry.example.com', 'docker-registry-credentials') {
image.push()
image.push('latest')
}
}
}
}
stage('Deploy') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
agent any
steps {
script {
def environment = env.BRANCH_NAME == 'main' ? 'production' : 'staging'
sh """
docker run -d --name ${environment}-${env.BUILD_ID} \
-p 8080:3000 \
${env.DOCKER_IMAGE}:${env.BUILD_ID}
"""
}
}
}
}
}
Scripted Pipeline
// Jenkinsfile (Scripted Pipeline)
node('master') {
def branch = env.BRANCH_NAME
def buildNumber = env.BUILD_NUMBER
try {
stage('Checkout') {
checkout scm
}
stage('Environment Setup') {
def nodeHome = tool 'NodeJS-18'
env.PATH = "${nodeHome}/bin:${env.PATH}"
sh 'node --version'
sh 'npm --version'
}
stage('Install Dependencies') {
sh 'npm ci'
}
stage('Build') {
sh 'npm run build'
archiveArtifacts artifacts: 'dist/**', fingerprint: true
}
stage('Test') {
try {
parallel(
'Unit Tests': {
sh 'npm run test:unit'
},
'Linting': {
sh 'npm run lint'
},
'Security Scan': {
sh 'npm audit'
}
)
} catch (Exception e) {
currentBuild.result = 'UNSTABLE'
echo "Tests failed: ${e.getMessage()}"
} finally {
publishTestResults testResultsPattern: 'test-results/*.xml'
}
}
if (branch == 'main') {
stage('Security Checks') {
sh 'npm run security:check'
// SonarQube analysis
withSonarQubeEnv('SonarQube') {
sh 'npm run sonar'
}
// Quality gate
timeout(time: 10, unit: 'MINUTES') {
def qg = waitForQualityGate()
if (qg.status != 'OK') {
error "Pipeline aborted due to quality gate failure: ${qg.status}"
}
}
}
stage('Deploy to Production') {
input message: 'Deploy to production?',
ok: 'Deploy',
submitterParameter: 'DEPLOYER'
echo "Deployment approved by: ${env.DEPLOYER}"
withCredentials([
usernamePassword(credentialsId: 'production-server',
usernameVariable: 'DEPLOY_USER',
passwordVariable: 'DEPLOY_PASS')
]) {
sh '''
scp -r dist/ ${DEPLOY_USER}@production-server:/var/www/html/
ssh ${DEPLOY_USER}@production-server "systemctl restart nginx"
'''
}
}
} else if (branch == 'develop') {
stage('Deploy to Staging') {
sh 'npm run deploy:staging'
}
}
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
stage('Cleanup') {
deleteDir()
}
stage('Notifications') {
if (currentBuild.result == 'FAILURE') {
slackSend channel: '#dev-team',
color: 'danger',
message: "❌ Build Failed: ${env.JOB_NAME} #${buildNumber}"
} else if (currentBuild.result == 'SUCCESS') {
slackSend channel: '#dev-team',
color: 'good',
message: "✅ Build Successful: ${env.JOB_NAME} #${buildNumber}"
}
}
}
}
Multi-branch Pipeline
// Jenkinsfile (Multi-branch Pipeline)
pipeline {
agent any
parameters {
choice(
name: 'ENVIRONMENT',
choices: ['dev', 'staging', 'production'],
description: 'Target environment'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: 'Skip running tests'
)
string(
name: 'DEPLOY_VERSION',
defaultValue: 'latest',
description: 'Version to deploy'
)
}
environment {
REGISTRY = 'docker.io'
IMAGE_NAME = 'mycompany/myapp'
KUBECONFIG = credentials('kubeconfig')
}
stages {
stage('Prepare') {
steps {
script {
env.BRANCH_LOWER = env.BRANCH_NAME.toLowerCase()
env.IMAGE_TAG = "${env.BRANCH_LOWER}-${env.BUILD_NUMBER}"
if (env.BRANCH_NAME == 'main') {
env.TARGET_ENV = 'production'
} else if (env.BRANCH_NAME == 'develop') {
env.TARGET_ENV = 'staging'
} else {
env.TARGET_ENV = 'dev'
}
}
echo "Building branch: ${env.BRANCH_NAME}"
echo "Target environment: ${env.TARGET_ENV}"
echo "Image tag: ${env.IMAGE_TAG}"
}
}
stage('Build') {
steps {
sh 'docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .'
sh 'docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:${BRANCH_LOWER}-latest'
}
}
stage('Test') {
when {
not {
params.SKIP_TESTS
}
}
parallel {
stage('Unit Tests') {
steps {
sh 'docker run --rm ${IMAGE_NAME}:${IMAGE_TAG} npm test'
}
}
stage('Security Scan') {
steps {
sh 'trivy image ${IMAGE_NAME}:${IMAGE_TAG}'
}
}
stage('Performance Test') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
sh 'docker run --rm ${IMAGE_NAME}:${IMAGE_TAG} npm run test:performance'
}
}
}
}
stage('Push to Registry') {
steps {
withCredentials([usernamePassword(credentialsId: 'docker-registry', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh 'echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin'
sh 'docker push ${IMAGE_NAME}:${IMAGE_TAG}'
sh 'docker push ${IMAGE_NAME}:${BRANCH_LOWER}-latest'
}
}
}
stage('Deploy') {
when {
anyOf {
branch 'main'
branch 'develop'
branch 'feature/*'
}
}
steps {
script {
sh """
helm upgrade --install ${env.TARGET_ENV}-myapp ./helm/myapp \
--namespace ${env.TARGET_ENV} \
--set image.tag=${env.IMAGE_TAG} \
--set environment=${env.TARGET_ENV} \
--wait --timeout=300s
"""
}
}
}
}
post {
always {
sh 'docker system prune -f'
}
success {
script {
if (env.BRANCH_NAME == 'main') {
build job: 'create-release',
parameters: [
string(name: 'VERSION', value: env.BUILD_NUMBER),
string(name: 'IMAGE_TAG', value: env.IMAGE_TAG)
]
}
}
}
failure {
emailext (
subject: "❌ Jenkins Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: """
<h2>Build Failed</h2>
<p><strong>Job:</strong> ${env.JOB_NAME}</p>
<p><strong>Build Number:</strong> ${env.BUILD_NUMBER}</p>
<p><strong>Branch:</strong> ${env.BRANCH_NAME}</p>
<p><strong>Console Output:</strong> <a href="${env.BUILD_URL}console">${env.BUILD_URL}console</a></p>
""",
to: "${env.CHANGE_AUTHOR_EMAIL}",
mimeType: 'text/html'
)
}
}
}
Using Shared Libraries
// vars/deployApp.groovy (Shared Library)
def call(Map config) {
pipeline {
agent any
stages {
stage('Deploy') {
steps {
script {
echo "Deploying ${config.appName} to ${config.environment}"
def helmValues = [
"image.tag=${config.imageTag}",
"environment=${config.environment}",
"replicas=${config.replicas ?: 1}"
]
sh """
helm upgrade --install ${config.appName} ./helm/${config.appName} \
--namespace ${config.environment} \
--set ${helmValues.join(' --set ')} \
--wait --timeout=${config.timeout ?: '300s'}
"""
}
}
}
}
}
}
// Jenkinsfile using Shared Library
@Library('my-shared-library') _
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'docker build -t myapp:latest .'
}
}
stage('Deploy') {
steps {
deployApp([
appName: 'myapp',
environment: 'production',
imageTag: 'latest',
replicas: 3,
timeout: '600s'
])
}
}
}
}