How to Build a CI/CD Pipeline with Jenkins, SonarQube & Trivy

A hands-on DevOps project that brings together Jenkins, SonarQube, and Trivy β€” all running locally with Docker Compose β€” to create a cost-free and secure CI/CD pipeline.

🧭 Introduction

CI/CD is at the heart of modern DevOps. But what if you could build an end-to-end secure pipeline using only open-source tools β€” and run everything locally on a single Ubuntu server?

That’s exactly what I did βœ…

In this tutorial, I’ll walk you through setting up a complete DevSecOps pipeline where:

  • 🧠 Jenkins handles the automation
  • πŸ§ͺ SonarQube ensures code quality
  • πŸ›‘οΈ Trivy scans for vulnerabilities
  • 🐳 Docker Compose manages all containers
  • πŸ–₯️ Even the deployment host is the same server, making this setup incredibly efficient and minimal

No external VMs, no cloud cost, no complexity β€” just pure DevOps learning πŸ’»βœ¨

πŸ’» System Requirements

  • OS: Ubuntu 20.04+
  • RAM: 8 GB+
  • Tools: Docker, Docker Compose
  • Internet access for pulling images

🧱 Step 1: Clone the Project & Launch Jenkins + SonarQube with Docker Compose

Before setting up our CI/CD magic, let’s get the project ready by cloning the repository and initializing the required directories and SSH keys.

πŸ” 1️⃣ Clone the Repository

Start by cloning the DevSecOps project repo that contains our pre-configured docker-compose.yaml and helper scripts:

git clone https://github.com/rjshk013/devops-projects.git
cd cicd-jenkins

πŸ” 2️⃣ Run start.sh to Prepare the Jenkins Agent

This script does two important things:

  • πŸ“ Creates required directories (jenkins_home, jenkins_agent, jenkins_agent_keys)
  • πŸ”‘ Generates SSH key pairs for Jenkins master ↔ agent authentication
chmod +x setup.sh
sh setup.sh

βœ… After this, your system is ready to launch the services!

πŸ” How This Docker Compose Setup Works (All-in-One CI/CD Stack)

This Docker Compose file orchestrates a fully functional DevSecOps pipeline stack β€” all running locally, isolated in a shared network β€” and ready to automate builds, scans, and deployments πŸ’₯

version: "3.8"

services:
# πŸ”§ Jenkins Master
jenkins-master:
image: jenkins/jenkins:lts-jdk17
container_name: jenkins
restart: unless-stopped
user: 1000:1000
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home:rw
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
environment:
- JAVA_OPTS=-Dhudson.security.csrf.GlobalCrumbIssuerStrategy=true -Djenkins.security.SystemReadPermission=true
networks:
- jenkins_network
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp:size=2G
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/login || exit 1"]
interval: 1m30s
timeout: 10s
retries: 3

# πŸ”§ Jenkins SSH Agent
jenkins-agent:
image: jenkins/ssh-agent
container_name: jenkins-agent
restart: unless-stopped
expose:
- "22"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
- jenkins_agent:/home/jenkins/agent:rw
- type: bind
source: ./jenkins_agent_keys
target: /home/jenkins/.ssh
read_only: true
environment:
- SSH_PUBLIC_KEY_DIR=/home/jenkins/.ssh
networks:
- jenkins_network
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp:size=2G

# 🧠 SonarQube
sonarqube:
container_name: sonarqube
image: sonarqube:lts-community
restart: unless-stopped
depends_on:
- sonar_db
ports:
- "9001:9000"
environment:
SONAR_JDBC_URL: jdbc:postgresql://sonar_db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: sonar
volumes:
- sonarqube_conf:/opt/sonarqube/conf
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
- sonarqube_temp:/opt/sonarqube/temp
networks:
- jenkins_network

# 🐘 Postgres for SonarQube
sonar_db:
image: postgres:15
restart: unless-stopped
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
POSTGRES_DB: sonar
volumes:
- sonar_db:/var/lib/postgresql
- sonar_db_data:/var/lib/postgresql/data
networks:
- jenkins_network

# πŸ”— Shared Network
networks:
jenkins_network:
driver: bridge

# πŸ’Ύ Volumes
volumes:
jenkins_home:
jenkins_agent:
sonarqube_conf:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
sonarqube_temp:
sonar_db:
sonar_db_data:

πŸ› οΈ Jenkins Master (jenkins-master)

  • πŸ“¦ Image: Uses jenkins/jenkins:lts-jdk17
  • πŸ”Œ Ports:
  • 8080: Jenkins web UI
  • 50000: For connecting inbound agents
  • πŸ’Ύ Volumes:
  • jenkins_home: Persists Jenkins jobs, config, and plugins
  • /var/run/docker.sock: Lets Jenkins build and run Docker containers
  • /usr/bin/docker: Gives Jenkins CLI access to Docker commands
  • πŸ§ͺ Health Check:
  • Automatically checks service availability via a curl login probe
  • πŸ”’ Hardened Settings:
  • read_only: true: Makes the container filesystem immutable
  • tmpfs: Stores /tmp in RAM for better performance and safety
  • no-new-privileges: Prevents privilege escalation inside the container

βš™οΈ Jenkins SSH Agent (jenkins-agent)

  • πŸ“¦ Image: Uses jenkins/ssh-agent (for connecting back to master)
  • πŸ” SSH Access:
  • Mounts SSH keys from jenkins_agent_keys to enable secure agent communication
  • πŸ’Ύ Volumes:
  • jenkins_agent: Stores agent workspace data
  • /usr/bin/docker + Docker socket: Enables builds inside the agent container
  • πŸ›‘οΈ Security First:
  • read_only: true + tmpfs: Isolates temp files in RAM
  • no-new-privileges: Blocks processes from elevating access

🧠 SonarQube (sonarqube)

  • πŸ“¦ Image: sonarqube:lts-community
  • 🌐 Port Mapping:
  • Exposed as localhost:9001 β†’ container:9000
  • πŸ”— Connects to PostgreSQL (sonar_db)
  • Configured via SONAR_JDBC_URL and credentials
  • πŸ’Ύ Volumes:
  • Persist configuration, extensions, data, logs, and temp files
  • πŸ”„ Depends On:
  • Ensures PostgreSQL container starts before SonarQube

🐘 PostgreSQL Database (sonar_db)

  • πŸ“¦ Image: postgres:15
  • 🎯 Purpose: Backend database for SonarQube code analysis data
  • πŸ” Environment Variables:
  • Sets DB name, user, and password for SonarQube integration
  • πŸ’Ύ Volumes:
  • sonar_db and sonar_db_data: Persist DB schema and data

πŸ”— Common Docker Network: jenkins_network

All containers share a custom bridge network, enabling secure internal DNS resolution and private communication between services (like jenkins ↔ agent ↔ sonarqube ↔ postgres).

🐳 3️⃣ Launch Jenkins + SonarQube + PostgreSQL with Docker Compose

Now that everything is set, fire up the full stack:

docker-compose up -d

βœ… Verify Container Status After Launch

Once you’ve started the stack using docker compose up -d, you’ll want to make sure everything is running properly.

πŸ§ͺ Run this command to check the status of all containers:

docker ps

⏳ Give Jenkins Some Time…

⚠️ Note: Jenkins Master takes a bit of time to fully initialize β€” especially on the first run when it sets up plugins and internal directories.

⏲️ Wait 1–2 minutes, then run docker ps again.

You should see:

  • 🟒 jenkins container: showing healthy
  • 🟒 jenkins-agent container: up
  • 🧠 sonarqube: should also be healthy and accessible at localhost:9001

βœ… Once all containers are up and healthy, you’re ready to configure Jenkins and start building pipelines!

🌐 Step 4: Access Jenkins Web Interface

Your Jenkins Master is now running β€” let’s unlock the power of automation! πŸš€

πŸ–₯️ Open your browser and go to:

http://localhost:8080

You’ll be greeted by the Jenkins Setup Wizard πŸ§™β€β™‚οΈ

  • πŸ” Enter the initial admin password

To retrieve the initial admin password for login run the below command

docker exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Follow the guided setup: install recommended plugins, create your admin user

Once it finished will ask for configure admin user.Click save & continue

Once you completed we can see the jenkins dashboard as below

🧱 Step 2: Configure jenkins agent for build

πŸ”‘ 1️⃣ Add SSH Credentials for Jenkins Agent

Now let’s securely configure the SSH authentication so the Jenkins Master can talk to the Jenkins Agent πŸš€

  1. Navigate to your Jenkins dashboard.
  2. Click on Manage Jenkins.
  3. Select Credentials.
  4. Under (global), click Add Credentials.
  5. Fill in the form:
  • Kind: SSH Username with private key
  • Scope: Global
  • Username: jenkins
  • Private Key: Enter directly
  • Key: Paste the contents of the id_rsa file located inside the jenkins_agent_keys folder. This key is automatically generated during the setup process when you run the setup.sh script.
  • ID: can give some name to identify the credential,here i am giving build-agent
  • Description: SSH key for Jenkins agent
  • Click OK to save.

πŸ”— Step 2: Connect Agent to Jenkins Master

  1. In Jenkins, go to Manage Jenkins > Manage Nodes and Clouds.
  2. Click New Node.
  3. Enter a name (e.g., agent), select Permanent Agent, and click create
  1. Configure the node:
  • Remote root directory: /home/jenkins/agent
  • Labels: agent
  • Launch method: Launch agents via SSH
  • Host: jenkins-agent (matches the Docker service name)
  • Credentials: Select the credential that you have created earlier
  • Host Key Verification Strategy: Manually trusted key verification Strategy
  • Click Save

Verify Connection:

  • Jenkins will attempt to connect to the agent. If successful, the agent’s status will show as Connected.

Step 3:Configure Jenkins Agent SSH Key for Remote Host Access

Since we already generated the SSH key pair during the Jenkins setup (via setup.sh), we can reuse the same private key for connecting to the remote host.(in our case it is the same local ubuntu server where we running containers )

βœ… Just copy the public key to your host machine’s authorized_keys.

On your host machine, run the following commands:

cat /home/user/devops-projects/cicd-jenkins/jenkins_agent_keys/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

🧠 This allows Jenkins (via the agent) to SSH into the host machine securely using the existing key β€”

Add SSH Credentials for Remote Host Deployment

These credentials allow Jenkins to SSH into your remote host and trigger Docker Compose commands during deployment.

➑️ Go to Manage Jenkins β†’ Credentials β†’ (Global) β†’ Add Credentials
Then fill out the following fields:

  • Kind: SSH Username with private key
  • Scope: Global
  • Username: user (or your actual host system username)
  • Private Key: Enter directly β†’ paste the content of id_rsa from jenkins_agent_keys folder (generated by setup.sh)
  • ID (optional): deploy-server-ssh
  • Description: Remote host SSH for Docker Compose deployment

πŸ”Œ Plugin Power-Up: Install Essential Jenkins Plugins πŸš€

βš™οΈ Go to: Manage Jenkins β†’ Plugins β†’ Available Plugins
βœ… Search and
Install without restart for each listed plugin below.

1️⃣ Eclipse Temurin Installer

🧠 Installs and manages JDK versions for your build environments

2️⃣ SonarQube Scanner

πŸ” Integrates Jenkins with SonarQube to analyze code quality and detect bugs

3️⃣ NodeJS Plugin

🌐 Allows Jenkins to install and use specific Node.js versions in pipelines

4️⃣ Docker Plugin Suite (Install all individually) 🐳

Enables full Docker support in Jenkins β€” from simple builds to full pipelines

  • βœ… Docker Plugin
  • βœ… Docker Commons
  • βœ… Docker Pipeline
  • βœ… Docker API
  • βœ… Docker Build Step
5️⃣ SSH Agent Plugin

πŸ” Use SSH credentials securely to connect and execute on remote machines

🧠 Step 4: Access & Secure Your SonarQube Dashboard

πŸŽ‰ SonarQube is now up and running on port 9001

πŸ”— Open your browser and visit:
πŸ‘‰ http://localhost:9001/

πŸ” Login using default credentials:

  • Username: admin
  • Password: admin

⚠️ Important:
You’ll be prompted to change the default password on your first login.
Make sure to choose a strong one! πŸ”’

Step 5: Integrating Jenkins with SonarQube

πŸ” 1: Create a Global Token in SonarQube

  1. Log in to SonarQube with the admin user
  2. In the top right corner, click your username(Here it is Administrator)β†’ choose β€œMy Account”.
  1. Go to the β€œSecurity” tab.
  2. Under Generate Tokens:
  • Name: enter something like jenkins-global-token
  • Type: leave it as User Token
  • Expires In: choose No expiration (recommended for CI usage)
  1. Click Generate, and copy the token (you won’t be able to see it again later).

βœ… This token allows Jenkins to push analysis results to any project that the user has access to.

After previous step, you’ll see the generated token. Save it somewhere to not loose. We’ll need that for later.

πŸ” Why Use a Global Token?

  • πŸ” Reusable across all projects β€” no need to generate separate tokens per project
  • 🧹 Easy to manage β€” one place to rotate/update
  • βœ… Secure β€” stored in Jenkins as a credential (not hardcoded)
  • βš™οΈ Scalable β€” works even as your pipeline or project count grows

🧱 Step 2: Create the Project (Using the Global Token)

  1. Navigate to Projects β†’ Create Project.
  2. Select Manually.

3.Fill:

  • Project Key: blog-app
  • Display Name: blog-app
  • Main branch name: In my case it is main.But in some projects it could be master or dev as well

4.On the next screen, when you’re asked:

How do you want to analyze your project?
βœ… Choose:
Locally

  1. It will now prompt:

Use existing token
πŸ” Paste your
global token here

🟒 DO NOT click Generate Token β€” instead, choose β€œUse existing token” and paste the global one you created in Step 1.

This completes the project setup and links your global token to it.

Note: You can see the Run analysis on your project options after giving the token.Just ignore the further steps .Will add the necessary commands in Jenkinsfile

🧾 3. Add SonarQube Token to Jenkins Credentials

  1. In Jenkins, go to Manage Jenkins > Credentials.
  2. Select the appropriate domain (e.g., (global)).
  3. Click Add Credentials.
  4. Choose Secret text as the kind.
  5. Paste the SonarQube token into the Secret field that we created from the step Create a Global Token in SonarQube
  6. Provide an ID (e.g., sonar-token) and a description.
  7. Click Create to save.

βš™οΈ 4.Configure SonarQube Server in Jenkins

  1. Navigate to Manage Jenkins > Configure System.
  2. Scroll down to the SonarQube servers section.
  3. Click Add SonarQube.
  4. Provide a name for the server (e.g. sonar-server).
  5. Enter the SonarQube server URL (http://sonarqube:9000).

βœ… Use the Docker container name sonarqube β€” this works because both Jenkins and SonarQube are running on the same Docker Compose network (jenkins_network), and Docker handles the DNS resolution automatically

6.Server authentication token:
Select the credential ID you created earlier β€” e.g., sonar-token

7.Check Enable injection of SonarQube server configuration as build environment variables.

βœ… β€œEnable injection of SonarQube server configuration as build environment variables”

When checked, Jenkins automatically injects environment variables like:

SONAR_HOST_URL

SONAR_AUTH_TOKEN

SONAR_SCANNER_HOME

These can then be used inside shell scripts or pipeline steps without hardcoding.Click Save to apply the changes.

6. Installing Required Tools in Jenkins

To make Jenkins ready for CI/CD magic ✨, we need to equip it with essential tools. These tools empower Jenkins to build, test, scan, and deploy applications across the pipeline.

πŸ“ Navigate to:

Jenkins Dashboard β†’ Manage Jenkins β†’ Global Tool Configuration

Now, configure the following:

πŸ” 1. πŸ§ͺ SonarQube Scanner

βœ… Required for performing static code analysis on your project directly from Jenkins

πŸ”§ Steps to configure:

Navigate to the SonarQube Scanner section β†’ Click βž• Add SonarQube Scanner
🏷️ Name: sonar-scanner
βœ… Check: Install automatically
πŸ”½ Installer source: Install from Maven Central

πŸ“Œ Version: 7.0.2.4839 (or latest available)

πŸ“Œ Jenkins will now auto-download and manage the SonarQube Scanner version needed for your pipeline.

🧠 This ensures that your pipeline always has a ready-to-go scanner for SonarQube analysis without needing to install anything manually on your host.

2.β˜• JDK 21 (Temurin)

βœ… Required to compile Java-based projects and run tools like SonarQube Scanner in Jenkins

πŸ”§ Steps to configure:

  • Navigate to the JDK section β†’ Click βž• Add JDK
  • 🏷️ Name: jdk21
  • βœ… Check: Install automatically
  • πŸ”½ Installer source: Install from adoptium.net
  • πŸ“Œ Select version: 21.0.4 (or latest available under JDK 21)

🧠 This setup ensures Jenkins downloads and uses Java 21 from the official Adoptium build

3.🟩 NodeJS 21.0.0

βœ… Required for building frontend applications (React, Vue, Angular) or running JavaScript-based tools in Jenkins

πŸ”§ Steps to configure:

  • Scroll to the NodeJS section β†’ Click βž• Add NodeJS
  • 🏷️ Name: node21
  • βœ… Check: Install automatically
  • πŸ”½ Installer source: Install from nodejs.org
  • πŸ“Œ Select version: 21.0.0

🧠 This configuration allows Jenkins to download and manage Node.js v21.0.0 directly from the official Node.js website

4.🐳 Docker (Latest)

βœ… Essential for building, running, and scanning Docker containers directly from Jenkins pipelines

πŸ”§ Steps to configure:

  • Scroll to the Docker section β†’ Click βž• Add Docker
  • 🏷️ Name: docker
  • βœ… Check: Install automatically
  • πŸ”½ Installer source: Download from docker.com
  • πŸ“Œ Select version: (latest available or your preferred Docker version)

🧠 With this setup, Jenkins automatically installs Docker from the official source β€” no manual setup or system-level install required.

  • Ensure Docker is already installed on your system (/usr/bin/docker)

πŸ“Œ You’ve already mounted the Docker socket in your Jenkins Compose file, so this config links Jenkins with the host Docker engine.

Apply and Save:

  1. Once all tools are configured, click Apply and Save.

7.Configure Essential Credentials in Jenkins for Secure CI/CD

🧾 1. πŸ” Generate a Docker Hub Access Token

Go to: Docker Hub β†’ Account Settings β†’ Security β†’ Access Tokens

  • Click βž• β€œNew Access Token”
  • Give it a meaningful name (e.g.,jenkins-ci)
  • Click Generate
  • πŸ“‹ Copy the token (you won’t see it again!)

🧰 2. πŸ’Ό Add the Token to Jenkins Credentials

Go to your Jenkins Dashboard:
➑️ Manage Jenkins β†’ Credentials β†’ Global Credentials (unrestricted) β†’ Add Credentials

  • πŸ‘‡ Select: Kind: Secret text
  • ✏️ Secret: (Paste the Docker token here)
  • 🏷️ ID: docker-hub-token (or any unique identifier)
  • πŸ’¬ Description: Docker Hub Personal Access Token for Jenkins

βœ… Create it!

πŸ› οΈ Step-8:Grant Docker Access to Jenkins

To enable Jenkins (inside the container) to communicate with Docker on the host:

sudo chmod 666 /var/run/docker.sock

⚠️ Why this is needed?
This sets read/write permissions on the Docker socket so Jenkins containers can:

  • 🐳 Build Docker images
  • πŸ” Scan them with Trivy
  • πŸš€ Push to Docker Hub

πŸ’‘ Note: This is safe for local testing environments, but not recommended for production.

🌐 About the Application: Wanderlust β€” A 3-Tier Web App

🧭 Wanderlust is a lightweight 3-tier web application designed to demonstrate full CI/CD automation using open-source DevOps tools. It features:

πŸ“¦ Architecture:

  • Frontend: A modern web UI built with ReactJS (Node.js 21), offering a clean and responsive interface for user interactions.
  • Backend: A RESTful API developed using Node.js and Express.js, handling business logic and communication with the database.
  • Database: MongoDB used as the NoSQL data store for posts or travel-related content.

πŸ” Set Up Your Jenkins CI/CD Pipeline β€” Automation Begins Here!

Now that your DevOps environment is all set 🎯, it’s time to automate your entire workflow β€” from code checkout to deployment β€” using a Jenkins pipeline.

πŸ’‘ What will this pipeline do?
βœ… Clone your code from GitHub
βœ… Run static code analysis via SonarQube
βœ… Perform vulnerability scans with Trivy
βœ… Build & push Docker images
βœ… Deploy the app β€” hands-free!

πŸ“Œ 8.Let’s Create the Pipeline Job

πŸ–₯️ Navigate to Jenkins Dashboard:

  1. βž• Click on β€œNew Item”
  2. 🏷️ Name your project β€” e.g., wanderlust-deploy
  3. 🧱 Choose β€œPipeline” as the project type
  4. βœ… Click OK to continue

Define Pipeline Syntax:

  • In the Pipeline section, select Pipeline script and add the following pipeline code:
pipeline {
agent { label 'agent' }

tools {
jdk 'jdk21'
nodejs 'node21'
}

environment {
IMAGE_TAG = "${BUILD_NUMBER}"
BACKEND_IMAGE = "rjshk013/wanderlust-backend"
FRONTEND_IMAGE = "rjshk013/wanderlust-frontend"
}

stages {
stage('SCM Checkout') {
steps {
git branch: 'main', url: 'https://github.com/rjshk013/devops-projects.git'
}
}

stage('Install Dependencies') {
steps {
dir('wanderlust-3tier-project/backend') {
sh "npm install || true"
}
dir('wanderlust-3tier-project/frontend') {
sh "npm install"
}
}
}

stage('Run SonarQube') {
environment {
scannerHome = tool 'sonar-scanner'
}
steps {
withSonarQubeEnv('sonar-server') {
sh """
${scannerHome}/bin/sonar-scanner \
-Dsonar.projectKey=blog-app \
-Dsonar.projectName=blog-app \
-Dsonar.sources=wanderlust-3tier-project
"""
}
}
}

stage('Docker Build') {
steps {
dir('wanderlust-3tier-project') {
sh '''
docker build -t ${BACKEND_IMAGE}:${IMAGE_TAG} ./backend
docker build -t ${FRONTEND_IMAGE}:${IMAGE_TAG} ./frontend
'''
}
}
}

stage('Scan with Trivy') {
steps {
script {
def images = [
[name: "${BACKEND_IMAGE}:${IMAGE_TAG}", output: "trivy-backend.txt"],
[name: "${FRONTEND_IMAGE}:${IMAGE_TAG}", output: "trivy-frontend.txt"]
]

for (img in images) {
echo "πŸ” Scanning ${img.name}..."
sh """
mkdir -p wanderlust-3tier-project
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v \$HOME/.trivy-cache:/root/.cache/ \
-v \$WORKSPACE/wanderlust-3tier-project:/scan-output \
aquasec/trivy image \
--scanners vuln \
--severity HIGH,CRITICAL \
--exit-code 0 \
--format table \
${img.name} > wanderlust-3tier-project/${img.output}
"""
}
}
}
}

stage('Push to Docker Hub') {
steps {
withCredentials([string(credentialsId: 'docker-hub-token', variable: 'DOCKER_TOKEN')]) {
sh '''
echo "${DOCKER_TOKEN}" | docker login -u rjshk013 --password-stdin
docker push ${BACKEND_IMAGE}:${IMAGE_TAG}
docker push ${FRONTEND_IMAGE}:${IMAGE_TAG}
'''
}
}
}

stage('Remote Deploy on Host with Docker Compose') {
steps {
sshagent(credentials: ['deploy-server-ssh']) {
sh '''
echo "πŸš€ Deploying on host with docker compose..."
ssh -o StrictHostKeyChecking=no user@172.18.0.1 '
cd /home/user/devops-projects/wanderlust-3tier-project &&
docker compose build &&
docker compose up -d
'
'''
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'wanderlust-3tier-project/trivy-*.txt', fingerprint: true
}
}
}

πŸ› οΈ Understanding the Jenkinsfile (Wanderlust CI/CD Pipeline)

This Jenkinsfile automates the complete build β†’ scan β†’ deploy lifecycle of your Wanderlust project.

Here’s a step-by-step overview of what it does:

πŸ”₯ Key Highlights

  • Secure Build: Code quality is validated by SonarQube before proceeding to deployment βœ…
  • Security First: Images are scanned by Trivy before pushing to Docker Hub πŸ›‘οΈ
  • Fully Automated: No manual steps β€” from Git pull to live deployment πŸš€
  • Secrets Handling: Docker Hub credentials and SonarQube token are injected via Jenkins Credentials Manager πŸ”’

πŸ”§ 9.Update Jenkinsfile with Your Values

Before using the pipeline, replace the following placeholders with your own values:

  • rjshk013 β†’ your actual Docker Hub username
  • https://github.com/rjshk013/devops-projects.git β†’ use the provided repo or your own cloned GitHub repo
  • user@172.18.0.1-Replace with your host username & ip

That’s it β€” just plug in your details and you’re good to go! πŸš€

πŸš€ 1️⃣0️⃣ Triggering the Pipeline & Viewing Results

Once your Jenkins pipeline is fully configured, it’s time to fire it up! πŸ’₯

πŸ› οΈ Steps to Run the Pipeline:

πŸ”˜ Go to the Jenkins Dashboard and click on your job (e.g., wanderlust-deploy).

▢️ Click β€œBuild Now” on the left panel to trigger the pipeline.

πŸ“œ Navigate to β€œBuild History”, and click on the latest build number (e.g., #1).

πŸ“ˆ In the build details page:

  • βœ… Click Pipeline Steps or Pipeline Overview to visualize each stage.
  • πŸ–₯️ Click Console Output to follow the logs in real-time β€” great for debugging and validation.

βœ… 1️⃣1️⃣ Post-Pipeline Verification: Confirm Everything Worked! πŸ•΅οΈβ€β™‚οΈ

After the pipeline runs successfully, it’s time to verify each key milestone to ensure your CI/CD process worked perfectly.

🐳 Docker Image Push Confirmation

βœ… Go to your Docker Hub repository.

πŸ” Navigate to:

  • wanderlust-backend
  • wanderlust-frontend

πŸ“Œ Check if the latest image tags (build numbers) are successfully listed and match the ones from the Jenkins pipeline.

🧠 SonarQube Code Quality Analysis

πŸ“ Open your browser and go to:
http://localhost:9001 (or your configured SonarQube URL)

πŸ“Š Navigate to:

  • Projects β†’ blog-app

βœ… You should see a recent analysis with quality gate status, bugs, code smells, duplications, etc.

πŸ›‘οΈ Trivy Vulnerability Scan

πŸ“‚ Go back to Jenkins UI β†’ your job β†’ latest build β†’ Artifacts

πŸ“Ž You’ll find files like:

  • trivy-backend.txt
  • trivy-frontend.txt

πŸ” Open them and look for:

  • Severity levels: CRITICAL, HIGH
  • Confirm no critical issues OR review and mitigate them accordingly.

πŸš€ UPDATE: Enhanced Container Vulnerability Scanning with Trivy

From Basic to Comprehensive: A Major Security Reporting Upgrade

The original Trivy scanning configuration in our pipeline provided basic vulnerability detection but lacked advanced reporting features. Our new implementation dramatically improves readability, analysis capabilities, and overall security insights.

✨ Key Improvements

  • Multi-format reporting: Generate HTML, text, and JSON formats simultaneously
  • Organized report structure: Separate reports by container type (frontend/backend)
  • Improved readability: HTML reports for visual analysis
  • Automated summary: Markdown summary highlighting critical findings
  • Better organization: Dedicated reports directory with consistent naming

Before: Basic text output with limited readability and no summary

stage('Scan with Trivy') {
    steps {
        script {
            def images = [
                [name: "${BACKEND_IMAGE}:${IMAGE_TAG}", output: "trivy-backend.txt"],
                [name: "${FRONTEND_IMAGE}:${IMAGE_TAG}", output: "trivy-frontend.txt"]
            ]
            for (img in images) {
                echo "πŸ” Scanning ${img.name}..."
                sh """
                    mkdir -p wanderlust-3tier-project
                    docker run --rm \
                        -v /var/run/docker.sock:/var/run/docker.sock \
                        -v \$HOME/.trivy-cache:/root/.cache/ \
                        -v \$WORKSPACE/wanderlust-3tier-project:/scan-output \
                        aquasec/trivy image \
                        --scanners vuln \
                        --severity HIGH,CRITICAL \
                        --exit-code 0 \
                        --format table \
                        ${img.name} > wanderlust-3tier-project/${img.output}
                """
            }
        }
    }
}

After: Comprehensive multi-format reporting with automated summary

stage('Scan with Trivy') {
    steps {
        script {
            def trivyDir = "${WORKSPACE}/trivy-reports"
            
            // Create reports directory
            sh "mkdir -p ${trivyDir}"
            
            // Define scan configurations
            def images = [
                [name: "${BACKEND_IMAGE}:${IMAGE_TAG}", type: "backend"],
                [name: "${FRONTEND_IMAGE}:${IMAGE_TAG}", type: "frontend"]
            ]

            // Run scans with multiple output formats
            for (img in images) {
                echo "πŸ” Scanning ${img.name}..."
                
                // HTML Report - Highly readable in browser
                sh """
                    docker run --rm \
                        -v /var/run/docker.sock:/var/run/docker.sock \
                        -v \$HOME/.trivy-cache:/root/.cache/ \
                        -v ${trivyDir}:/reports \
                        aquasec/trivy image \
                        --scanners vuln \
                        --severity HIGH,CRITICAL \
                        --exit-code 0 \
                        --format template \
                        --template '@/contrib/html.tpl' \
                        ${img.name} > ${trivyDir}/trivy-${img.type}-report.html
                """
                
                // Table format for console and archive
                sh """
                    docker run --rm \
                        -v /var/run/docker.sock:/var/run/docker.sock \
                        -v \$HOME/.trivy-cache:/root/.cache/ \
                        -v ${trivyDir}:/reports \
                        aquasec/trivy image \
                        --scanners vuln \
                        --severity HIGH,CRITICAL \
                        --exit-code 0 \
                        --format table \
                        ${img.name} > ${trivyDir}/trivy-${img.type}-report.txt
                """
                
                // JSON format for potential programmatic processing
                sh """
                    docker run --rm \
                        -v /var/run/docker.sock:/var/run/docker.sock \
                        -v \$HOME/.trivy-cache:/root/.cache/ \
                        -v ${trivyDir}:/reports \
                        aquasec/trivy image \
                        --scanners vuln \
                        --severity HIGH,CRITICAL \
                        --exit-code 0 \
                        --format json \
                        ${img.name} > ${trivyDir}/trivy-${img.type}-report.json
                """
            }
            
            // Generate combined summary report
            sh """
                echo "# Trivy Vulnerability Summary Report" > ${trivyDir}/summary.md
                echo "## Images Scanned" >> ${trivyDir}/summary.md
                echo "- ${BACKEND_IMAGE}:${IMAGE_TAG}" >> ${trivyDir}/summary.md
                echo "- ${FRONTEND_IMAGE}:${IMAGE_TAG}" >> ${trivyDir}/summary.md
                echo "\\n## Backend Vulnerabilities" >> ${trivyDir}/summary.md
                echo '```' >> ${trivyDir}/summary.md
                grep -A 10 "CRITICAL\\|HIGH" ${trivyDir}/trivy-backend-report.txt | head -20 >> ${trivyDir}/summary.md
                echo "\\n... (See full report for more details)" >> ${trivyDir}/summary.md
                echo '```' >> ${trivyDir}/summary.md
                echo "\\n## Frontend Vulnerabilities" >> ${trivyDir}/summary.md
                echo '```' >> ${trivyDir}/summary.md 
                grep -A 10 "CRITICAL\\|HIGH" ${trivyDir}/trivy-frontend-report.txt | head -20 >> ${trivyDir}/summary.md
                echo "\\n... (See full report for more details)" >> ${trivyDir}/summary.md
                echo '```' >> ${trivyDir}/summary.md
            """
        }
    }
}

πŸ’‘ Benefits of This Upgrade

  1. Enhanced visibility: HTML reports provide clear visualization of vulnerabilities
  2. Better decision making: Prioritized view of HIGH and CRITICAL issues
  3. Automation-friendly: JSON output enables programmatic analysis and integration
  4. Quick executive summaries: Markdown summary highlights the most critical findings
  5. Improved DevOps workflow: Consistent report location and naming

πŸ” How It Works

The new configuration runs three separate Trivy scans for each container image:

  1. HTML report: Beautiful, browser-viewable vulnerability analysis
  2. Text report: Traditional console output for quick review
  3. JSON report: Structured data for automation and integrations

πŸ›‘οΈ NEW: Adding Source Code Security Scanning with Trivy Filesystem Scanner

Moving Security Left: Detecting Vulnerabilities Before They Reach Your Containers

While scanning container images is crucial, the best security starts at the source. By adding filesystem scanning to our pipeline, we can identify vulnerabilities in our application dependencies and configuration files before they’re packaged into containers.

This “shift-left” approach to security helps catch issues earlier in the development lifecycle, reducing both risk and remediation costs.

πŸ” What Does Trivy Filesystem Scanning Find?

  • Dependency Vulnerabilities: Detects known CVEs in package.json, package-lock.json, and other dependency files
  • Misconfigurations: Identifies security issues in configuration files like Dockerfiles and YAML
  • License Violations: Flags dependencies with restrictive or non-compliant licenses
  • Secrets Detection: Identifies hardcoded credentials, API keys, and other sensitive data

πŸ’» Implementation: The Trivy Filesystem Scan Stage

Add this stage to your Jenkins pipeline before the Docker Build stage:

        stage('Trivy Filesystem Scan') {
            steps {
                script {
                    def trivyDir = "${WORKSPACE}/trivy-reports"
                    sh "mkdir -p ${trivyDir}"
                    
                    // Define targets for filesystem scanning
                    def fsTargets = [
                        [path: "${WORKSPACE}/wanderlust-3tier-project/backend", name: "backend-fs"],
                        [path: "${WORKSPACE}/wanderlust-3tier-project/frontend", name: "frontend-fs"]
                    ]
                    
                    // Run filesystem scans for source code vulnerabilities
                    for (target in fsTargets) {
                        echo "πŸ” Scanning filesystem at ${target.path}..."
                        
                        // Scan for vulnerabilities in dependencies
                        sh """
                            docker run --rm \
                                -v ${target.path}:/target \
                                -v ${trivyDir}:/reports \
                                aquasec/trivy fs \
                                --security-checks vuln \
                                --severity HIGH,CRITICAL \
                                --exit-code 0 \
                                --format table \
                                /target > ${trivyDir}/trivy-${target.name}-vuln.txt
                        """
                        
                        // Scan for security issues in configuration files
                        sh """
                            docker run --rm \
                                -v ${target.path}:/target \
                                -v ${trivyDir}:/reports \
                                aquasec/trivy fs \
                                --security-checks config \
                                --severity HIGH,CRITICAL \
                                --exit-code 0 \
                                --format json \
                                /target > ${trivyDir}/trivy-${target.name}-config.json
                        """
                        
                        // Generate an HTML report for easier reading
                        sh """
                            docker run --rm \
                                -v ${target.path}:/target \
                                -v ${trivyDir}:/reports \
                                aquasec/trivy fs \
                                --security-checks vuln,config \
                                --severity HIGH,CRITICAL \
                                --exit-code 0 \
                                --format template \
                                --template '@/contrib/html.tpl' \
                                /target > ${trivyDir}/trivy-${target.name}-report.html
                        """
                    }
                    
                    // Add filesystem scan summary to the main report
                    sh """
                        echo "\\n## Source Code Vulnerability Scan" >> ${trivyDir}/summary.md
                        echo "### Backend Source Vulnerabilities" >> ${trivyDir}/summary.md
                        echo '```' >> ${trivyDir}/summary.md
                        grep -A 10 "CRITICAL\\|HIGH" ${trivyDir}/trivy-backend-fs-vuln.txt | head -20 >> ${trivyDir}/summary.md
                        echo "\\n... (See full report for more details)" >> ${trivyDir}/summary.md
                        echo '```' >> ${trivyDir}/summary.md
                        
                        echo "\\n### Frontend Source Vulnerabilities" >> ${trivyDir}/summary.md
                        echo '```' >> ${trivyDir}/summary.md
                        grep -A 10 "CRITICAL\\|HIGH" ${trivyDir}/trivy-frontend-fs-vuln.txt | head -20 >> ${trivyDir}/summary.md
                        echo "\\n... (See full report for more details)" >> ${trivyDir}/summary.md
                        echo '```' >> ${trivyDir}/summary.md
                    """
                }
            }
        }

🧠 How It Works

The filesystem scan stage performs three distinct types of scans on both frontend and backend source code directories:

  1. Vulnerability Scan: Searches for known vulnerabilities in application dependencies
    • Outputs results to a readable text file
    • Only reports HIGH and CRITICAL severity issues to reduce noise
  2. Configuration Scan: Analyzes configuration files for security issues
    • Outputs results in JSON format for potential programmatic processing
    • Checks Dockerfiles, YAML files, and other config files for best practices
  3. Combined HTML Report: Creates a comprehensive, browser-friendly report
    • Combines both vulnerability and configuration scanning
    • Provides an easy-to-navigate interface for reviewing findings

All scan results are added to the existing summary report, providing a complete view of both source code and container vulnerabilities in one place.

πŸ“Š Complete Security Workflow

With this addition, our pipeline now provides comprehensive security in a layered approach:

  1. Source Code Analysis: SonarQube scans for quality issues and bugs
  2. Dependency Scanning: Trivy filesystem scan finds vulnerable packages
  3. Configuration Checks: Trivy detects misconfigurations in project files
  4. Container Scanning: Trivy image scan identifies vulnerabilities in the final containers

This multi-layered security approach gives you confidence that your application is protected at every stage of the development lifecycle.

πŸ” Sample Filesystem Scan Results

When your pipeline runs, you’ll see scan results like this for your source code:

Report Summary

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Target β”‚ Type β”‚ Vulnerabilities β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ - β”‚ - β”‚ - β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)

The clean report above indicates no vulnerabilities were foundβ€”great news! But if issues are detected, they’ll be displayed in an easy-to-read format with severity levels, affected packages, and available fixes.

πŸ› οΈ Implementation Notes

Simply replace your existing Trivy stage with this enhanced version to immediately benefit from the improved reporting capabilities. All reports will be saved to the trivy-reports directory in your workspace.

⭐ JUST UPDATED: The complete enhanced pipeline is now available in our GitHub repository!

# Clone the repo to get the latest security scanning improvements
git clone https://github.com/rjshk013/devops-projects
cd wanderlust-3tier-project

# Check out the new Trivy configuration in Jenkinsfile_trivyfull_report file

πŸ§ͺ 1️⃣2️⃣ Test the Deployed Application Locally

πŸš€ Once your CI/CD pipeline has completed successfully, your application is now live and accessible on your host machine. Here’s how to verify:

🌐 Access Frontend:

πŸ”— Open your browser and go to:
http://localhost:5173/

You should see your React-based frontend app running πŸŽ‰

πŸ› οΈ Access Backend (API):

πŸ”— Open another tab and hit:
http://localhost:5000/

🐳 Verify Running Containers on Host Machine

To ensure both your frontend and backend services are up and running after deployment, run the following command on your host terminal:

docker ps

πŸ”— GitHub Repository

πŸ—‚οΈ You can find the complete source code, Jenkinsfile, Docker Compose setup, and scripts used in this article right here:

πŸ‘‰ GitHub β€” Wanderlust 3-Tier DevSecOps CI/CD Project

βœ… Conclusion: Your DevSecOps Lab Is Now Live πŸš€

You’ve just built a real-world CI/CD pipeline that:

  • πŸ”§ Automates code building and testing with Jenkins
  • 🧠 Ensures code quality through SonarQube
  • πŸ›‘οΈ Scans for vulnerabilities using Trivy
  • 🐳 Runs entirely in Docker Compose
  • πŸ–₯️ Deploys applications seamlessly on the same local server

And the best part? You did it all with zero cloud cost β€” 100% open-source, local, and production-grade ready! πŸ’―

🎯 What’s Next?

  • βœ… Optimized pipeline speed using parallel stages in Jenkins
    βœ… Reduced Docker image sizes with multistage builds
    βœ… Upgraded security scanning by replacing Trivy with Snyk for deep dependency and container vulnerability checks

This setup isn’t just a tutorial β€” it’s a launchpad into real-world DevOps workflows. Now you’re equipped to experiment, expand, and evolve your own secure software delivery system πŸ”πŸ’»

Leave a Comment

Your email address will not be published. Required fields are marked *

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.