Pipeline Perfection: Mastering Jenkins Pipelines with Docker
Jenkins is the Swiss army knife of CI/CD — open-source, extensible, and trusted by developers worldwide. Whether managing a microservices fleet or deploying a monolith, Jenkins Pipelines provide a robust, flexible way to automate builds, tests, and deployments. This blog will walk you through building Jenkins Pipelines from scratch and show how to supercharge your pipelines using Docker.
What Is a Jenkins Pipeline?
A Jenkins Pipeline is a user-defined model for a continuous delivery pipeline. It's written in a domain-specific language (DSL) based on Groovy and is stored as code — often inside your repository as a Jenkinsfile
.
Why Jenkins Pipelines?
- As Code: Maintain, review, and version pipelines like application code.
- Complex Workflow Support: Parallel steps, conditional logic, and stages.
- Plugin Ecosystem: Extend pipeline functionality with 1,800+ plugins.
- Scalable Execution: Run builds across distributed Jenkins agents.
Jenkins + Docker: The DevOps Power Couple
Docker and Jenkins together streamline DevOps pipelines — isolating builds, managing environments, and simplifying deployments.
Benefits of Dockerized Jenkins Pipelines:
- Clean builds every time with ephemeral containers
- Environment parity between dev/staging/prod
- Simplified deployment via Docker Compose and Docker Registry
- Secure secrets management via Jenkins credentials
Setting Up Jenkins with Docker
Let's set up Jenkins using Docker for local or on-prem environments.
Jenkins Setup
To set up Jenkins, create two files in the root directory of your server:
jenkins-setup.yml
Dockerfile
Copy the jenkins-setup.yml
and Dockerfile
content into each respective file.
📝 Note: Make sure both files are placed directly in the root directory of your project—not inside any subfolders.
This setup ensures Jenkins can be easily built and run using Docker, making it simple to integrate with your CI/CD pipeline.
jenkins-setup.yml
- Docker Compose File
version: '3.8'
services:
docker:
image: docker:dind
container_name: jenkins-docker
privileged: true
environment:
DOCKER_TLS_CERTDIR: /certs
networks:
jenkins:
aliases:
- docker
volumes:
- jenkins-docker-certs:/certs/client
- jenkins-data:/var/jenkins_home
ports:
- "2376:2376"
jenkins:
build:
context: .
dockerfile: Dockerfile
container_name: jenkins
restart: on-failure
networks:
- jenkins
environment:
TZ: Asia/Kolkata
DOCKER_HOST: tcp://docker:2376
DOCKER_CERT_PATH: /certs/client
DOCKER_TLS_VERIFY: '1'
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins-data:/var/jenkins_home
- jenkins-docker-certs:/certs/client:ro
- /etc/localtime:/etc/localtime:ro # Optional but ensures accurate system time sync
volumes:
jenkins-docker-certs:
jenkins-data:
networks:
jenkins:
Dockerfile
for Jenkins with Docker CLI Support
FROM jenkins/jenkins:2.492.3-jdk17
USER root
RUN apt-get update && apt-get install -y \
lsb-release \
ca-certificates \
curl && \
install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
chmod a+r /etc/apt/keyrings/docker.asc && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null && \
apt-get update && apt-get install -y docker-ce-cli && \
apt-get clean && rm -rf /var/lib/apt/lists/*
USER jenkins
RUN jenkins-plugin-cli --plugins "blueocean docker-workflow"
Run Jenkins
docker compose -f jenkins-setup.yml up -d
Access Jenkins at http://localhost:8080
(or your server IP).
Your First Jenkins Pipeline
Option 1: Pipeline Script (Inline)
Best for quick and simple automation jobs.
pipeline {
agent any
stages {
stage('Build') {
steps { echo 'Building...' }
}
stage('Test') {
steps { echo 'Testing...' }
}
stage('Deploy') {
steps { echo 'Deploying...' }
}
}
}
Option 2: Pipeline Script from SCM
Best for projects under version control — especially for teams.
pipeline {
agent any
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Build') {
steps { echo 'Build from SCM pipeline' }
}
}
}
Real-World Example: Dockerized Jenkins CI/CD Pipeline
Let’s look at a production-ready Jenkinsfile used for building and deploying a Dockerized Node.js API:
Highlights:
- GitHub Webhooks
- Conditional Build Trigger
- Secure Credential Handling
- Docker Build & Push
- Remote SSH Deployment
pipeline {
agent any
environment {
REGISTRY_URL = <registry-url>
IMAGE_NAME = <image-name>
}
stages {
stage('Checkout Code') {
steps {
checkout scm
}
}
stage('Build & Push Docker Image') {
steps {
script {
withCredentials([usernamePassword(credentialsId: 'docker-registry', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh """
echo "${DOCKER_PASS}" | docker login ${REGISTRY_URL} -u "${DOCKER_USER}" --password-stdin
docker build -t ${REGISTRY_URL}/${IMAGE_NAME}:latest .
docker push ${REGISTRY_URL}/${IMAGE_NAME}:latest
docker logout ${REGISTRY_URL}
"""
}
}
}
}
stage('Deploy') {
steps {
sshagent(['jenkins-ssh-key']) {
sh """
ssh -o StrictHostKeyChecking=no <vm-username>@<vm-ip> '
cd /home/ubuntu/docker-compose/apps &&
docker compose -f ${IMAGE_NAME}.yml pull &&
docker compose -f ${IMAGE_NAME}.yml down &&
docker compose -f ${IMAGE_NAME}.yml up -d
'
"""
}
}
}
}
post {
always {
script {
// Set up date format and time zone (Asia/Kolkata = IST)
def now = new Date()
def formatter = new java.text.SimpleDateFormat('EEE, d MMM yyyy hh:mm:ss a z')
formatter.setTimeZone(TimeZone.getTimeZone('<your-preferred-timezone>'))
def formattedDate = formatter.format(now)
// Send build status email
emailext(
to: 'devteam@example.com',
subject: "📣 Jenkins Build: ${currentBuild.fullDisplayName} - ${currentBuild.currentResult}",
body: """
<p>Build Status - <span style="font-weight: bold; color: ${currentBuild.currentResult == 'SUCCESS' ? 'green' : 'red'};">${currentBuild.currentResult}</span></p>
<p>Project - <span style="font-weight: bold; color: #ff9900;">${env.JOB_NAME}</span></p>
<p>Build Number - <span style="font-weight: bold; color: #ffad33;">#${env.BUILD_NUMBER}</span></p>
<p>Build Details - <a href="${env.BUILD_URL}" style="font-weight: bold; color: #668cff;">Click here</a></p>
<p>Timestamp - <span style="font-weight: bold; color: #00e68a;">${formattedDate}</span></p>
""",
mimeType: 'text/html'
)
}
}
}
}
📌 Tip: UsewithCredentials
andsshagent
blocks to manage secrets and SSH keys securely via Jenkins Credentials.
Jenkins for Next.js Projects (Frontend)
Front-end frameworks like Next.js need a .env
at build time. Here’s how to inject it securely via Jenkins:
pipeline {
agent any
environment {
REGISTRY_URL = <registry-url>
IMAGE_NAME = <image-name>
}
stages {
stage('Checkout Code') {
steps {
checkout scm
}
}
stage('Inject .env') {
steps {
withCredentials([file(credentialsId: '<env-name-saved-in-jenkins-credentials>', variable: 'ENV_FILE')]) {
sh '''
echo "Fixing permissions and copying .env file..."
chmod -R 777 .
cp $ENV_FILE .env
'''
}
}
}
stage('Build & Push Docker Image') {
steps {
script {
withCredentials([usernamePassword(credentialsId: 'docker-registry', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh """
echo "${DOCKER_PASS}" | docker login ${REGISTRY_URL} -u "${DOCKER_USER}" --password-stdin
docker build -t ${REGISTRY_URL}/${IMAGE_NAME}:latest .
docker push ${REGISTRY_URL}/${IMAGE_NAME}:latest
docker logout ${REGISTRY_URL}
"""
}
}
}
}
stage('Deploy') {
steps {
sshagent(['jenkins-ssh-key']) {
sh """
ssh -o StrictHostKeyChecking=no <vm-username>@<vm-ip> '
cd /home/ubuntu/docker-compose/apps &&
docker compose -f ${IMAGE_NAME}.yml pull &&
docker compose -f ${IMAGE_NAME}.yml down &&
docker compose -f ${IMAGE_NAME}.yml up -d
'
"""
}
}
}
}
post {
always {
script {
// Set up date format and time zone (Asia/Kolkata = IST)
def now = new Date()
def formatter = new java.text.SimpleDateFormat('EEE, d MMM yyyy hh:mm:ss a z')
formatter.setTimeZone(TimeZone.getTimeZone('<your-preferred-timezone>'))
def formattedDate = formatter.format(now)
// Send build status email
emailext(
to: 'devteam@example.com',
subject: "📣 Jenkins Build: ${currentBuild.fullDisplayName} - ${currentBuild.currentResult}",
body: """
<p>Build Status - <span style="font-weight: bold; color: ${currentBuild.currentResult == 'SUCCESS' ? 'green' : 'red'};">${currentBuild.currentResult}</span></p>
<p>Project - <span style="font-weight: bold; color: #ff9900;">${env.JOB_NAME}</span></p>
<p>Build Number - <span style="font-weight: bold; color: #ffad33;">#${env.BUILD_NUMBER}</span></p>
<p>Build Details - <a href="${env.BUILD_URL}" style="font-weight: bold; color: #668cff;">Click here</a></p>
<p>Timestamp - <span style="font-weight: bold; color: #00e68a;">${formattedDate}</span></p>
""",
mimeType: 'text/html'
)
}
}
}
}
Pro Tip: Secure Credentials Management
Store sensitive data like API tokens, Docker credentials, and .env
files in:
Dashboard → Manage Jenkins → Credentials
Use:
Username with Password
for Docker or GitHubSecret file
for.env
filesSSH Username with private key
for SSH deploys
Final Thoughts
Jenkins Pipelines bring clarity, consistency, and control to your CI/CD workflows — and when combined with Docker, they’re a force multiplier. Whether you’re automating builds or deploying microservices to production, Jenkins gives you the tools to build confidently and deploy efficiently.