Part 2: How to build CI/CD with Jenkins & Snyk

๐ Introduction
๐ In Part 1 of this series, we built a solid CI/CD pipeline using Jenkins, SonarQube, and Trivy to automate build, test, and security scanning.
๐ฅ In this part, weโre shifting gears from basic automation to production-grade CI/CD by introducing:
- โฑ๏ธ Parallelized Jenkins pipeline stages to speed up builds
- ๐ณ Multistage Dockerfiles to reduce image size and improve performance
- ๐ก๏ธ Snyk Integration for comprehensive dependency and image vulnerability scanning
Letโs elevate your DevOps workflow to the next level!
โ๏ธ 1. Pipeline Optimization with Parallel Stages
By default, Jenkins runs everything sequentially. But why wait?
We can install dependencies, run Snyk scans, and build Docker images in parallel using the parallel {}
block.
โ Example: Parallel Dependency Installation
stage('Install Dependencies') {
steps {
parallel (
backend: {
dir('backend') {
sh 'npm install'
}
},
frontend: {
dir('frontend') {
sh 'npm install'
}
}
)
}
}
๐ This instantly reduced my pipeline time by 30โ40%.
๐ณ 2. Build Lean Images with Multistage Dockerfiles
Large Docker images = security risk + slow deploys.
๐ Before: Single-stage Dockerfile (bloated)
# Use a specific version of node
FROM node:21
# Set the working directory
WORKDIR /app
# Copy package.json and package-lock.json first to leverage Docker cache
COPY package*.json ./
# Install dependencies
RUN npm install
# Now copy the rest of your application code
COPY . .
# Use a non-root user to run your container for better security
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
# Expose the port your app runs on
EXPOSE 5000
# Command to start your application
CMD ["npm", "start"]
โ After: Multistage Dockerfile (lean, clean)
# =====================
# Stage 1: Build stage
# =====================
FROM node:21 as build
WORKDIR /app
# Copy only necessary files to install dependencies
COPY package*.json ./
# Install all dependencies (including dev)
RUN npm install
# Copy the rest of the application code
COPY . .
# ==========================
# Stage 2: Production stage
# ==========================
FROM node:21-slim
WORKDIR /app
# Copy only necessary files from the build stage
COPY --from=build /app ./
# Install only production dependencies
RUN npm install --omit=dev
# Copy environment configuration file (optional if used during build)
#COPY .env.sample .env
# Create non-root user for better security
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
EXPOSE 5000
CMD ["npm", "start"]
โฑ๏ธ Build faster, deploy lighter, and reduce image size by ~60%.
Current Frontend Dockerfile
# Use a specific version of node
FROM node:21
# Set the working directory
WORKDIR /app
# Copy only the necessary files first (e.g., package.json) to leverage Docker cache
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of your application code
COPY . .
# Use a non-root user to run your container
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
# Expose the port the app runs on
EXPOSE 5173
# Command to run your application
CMD ["npm", "run", "dev", "--", "--host"]
After Multistage build Dockerfile look like
# -------- Stage 1: Dependencies --------
FROM node:21-alpine AS deps
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --include=dev --prefer-offline --no-audit
# -------- Stage 2: Runtime (Development Server) --------
FROM node:21-alpine AS runtime
WORKDIR /app
# Copy package files
COPY package*.json ./
# Copy node_modules from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy ALL source files (maintains exact same structure)
COPY . .
# Set permissions
RUN chown -R node:node /app
USER node
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host"]
Measuring Build & Scan Time (Before Optimizations)
Before jumping into parallel execution and multi-stage Docker builds, it’s smart to measure baseline performance. This helps you quantify improvements.
๐ Track & Visualize Jenkins Build Duration
โ 1. Pipeline Stage View Plugin
Best For: Visualizing per-stage execution time directly in the Jenkins UI.
Why It’s Useful:
- ๐ Clearly displays how long each pipeline stage takes.
- ๐ Makes it easy to identify slow stages (e.g., security scans or image builds).
- ๐งโ๐ป Especially handy when experimenting with parallel stages or multi-stage Docker builds.
- โ๏ธ No extra configuration required in declarative pipelines โ just works out of the box.
๐ ๏ธ How to Install:
- Navigate to Manage Jenkins โ Plugin Manager
- Go to the Available tab and search for:Pipeline: Stage View Plugin
- Click Install without Restart.

โฑ๏ธ 1. Check Jenkins Build Duration
- 1. Check Jenkins Build Duration
After running a pipeline build, you can quickly analyze how much time each stage took using the Pipeline Stage View plugin.
๐ Steps to View Stage-Wise Build Time:
Go to your Jenkins job (e.g.,wanderlust-deploy1
).
In the left sidebar, click on “Full Stage View”.
Youโll see a graphical view of:
Each pipeline stage (e.g., Build, Test, Scan, Deploy)
Time taken by each stage (in seconds/minutes)
Color-coded status (โ Passed, โ Failed)

fter applying multistage builds, weโll check again to see the size reduction.
๐ก๏ธ 3. Replace Trivy with Snyk for Complete Security
While Trivy is great for scanning images, Snyk adds dependency scanning, better dashboards, and Git integration.
๐ง Step-by-Step: Integrate Snyk with Jenkins
๐งโ๐ป 1๏ธโฃ Create a Snyk Account & Retrieve Your API Token
To scan your code and Docker images from Jenkins using Snyk, you’ll need a personal API token from your Snyk account. Here’s how to get it:
๐ Step-by-Step:
- ๐ Go to https://snyk.io and sign up using your Google account or email.
- Once signed in, navigate to the bottom-left corner of your Snyk dashboard.
- Click on your profile name/email (e.g., Rajesh K) and select “Account settings” from the dropdown.
๐ธ Refer screenshot 1 below.

- Youโll be taken to the Account โ General page.
- Scroll down to the โAuth Tokenโ section.
Click on the button that says โclick to showโ under theKEY
field.
๐ธ Refer screenshot 2 below.

- Once clicked, it will reveal your Snyk API Token โ a long alphanumeric key.
๐ธ Refer screenshot 3 below.

- โ Copy this token โ it will be used to configure Jenkins.
โ Make sure you treat this token like a password. It authenticates your CLI and Jenkins integration with Snyk.
โ๏ธ 2. Install Snyk Plugin
- ๐งฉ Step A: Install the Snyk Security Plugin
- Open your Jenkins dashboard and navigate to:
Manage Jenkins โ Plugin Manager - Click the Available tab.
- Search for โSnyk Security Pluginโ
- Check the box and click Install without restart
- ๐ฆ This plugin allows Jenkins to run Snyk scans natively in pipeline stages using the
snykSecurity
block.

๐ Step B: Add Your Snyk API Token as a Credential
- Go to Manage Jenkins โ Credentials โ (Global) โ Add Credentials
- Set the Kind to: โSnyk API Tokenโ (๐ Not โSecret Textโ)
- Enter the following:
- ID:
snyk-api
- Description:
Snyk API Token for CI/CD Scans
- Token: Paste your API token from the Snyk dashboard
- ID:
- Click Create

โ With this, your Jenkins instance is now securely integrated with Snyk and ready to scan your source code and Docker images.
๐งฐ 3๏ธโฃ Configure the Snyk CLI Tool in Jenkins (One-Time Setup)
This step ensures Jenkins downloads and uses the Snyk CLI automatically when your pipeline runs.
๐ Where to Configure:
Navigate to:Jenkins โ Manage Jenkins โ Global Tool Configuration โ Snyk installations
๐ ๏ธ Configuration:
- Name:
snyk
(Make sure this matches what you use insnykSecurity(snykInstallation: 'snyk', ...)
) - โ Check: โInstall automaticallyโ
- Version:
latest
- Update policy interval:
24
- OS platform architecture:
Auto-detection
(default and recommended)

โ Why This Matters:
Ensures compatibility with the snykSecurity
pipeline steps
No manual CLI installation needed on Jenkins agents
Always uses the latest version of the Snyk CLI
โ 4. Use in Jenkinsfile
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'snyk-api',
targetFile: 'package.json',
projectName: 'my-backend-src',
failOnIssues: false,
additionalArguments: "--severity-threshold=high"
)
Stage: Snyk Scanning (Parallel Block)
This stage runs four Snyk scans in parallel, significantly improving performance by reducing total pipeline time.
โ 1. Frontend Dependency Scan
dir('wanderlust-3tier-project/frontend') {
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'snyk-api',
targetFile: 'package.json',
projectName: 'wanderlust-frontend-src',
failOnIssues: false,
additionalArguments: "--severity-threshold=critical"
)
}
Purpose: Scans package.json
in the frontend directory for vulnerable open-source dependencies.
FailOnIssues: false
to avoid breaking the build if issues are found.
Threshold: Only fails on critical vulnerabilities (if failOnIssues
were true
).
projectName: Helps uniquely identify the scan in the Snyk dashboard.
โ 2. Backend Dependency Scan
dir('wanderlust-3tier-project/backend') {
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'snyk-api',
targetFile: 'package.json',
projectName: 'wanderlust-backend-src',
failOnIssues: false,
additionalArguments: "--severity-threshold=critical"
)
}
Same logic as the frontend scan, but scoped to the backend source dependencies.
๐ณ 3. Backend Image Scan
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'synk-api',
failOnIssues: false,
projectName: 'wanderlust-backend-image',
additionalArguments: "--docker ${BACKEND_IMAGE}:${IMAGE_TAG} --severity-threshold=critical"
)
Scans the built Docker image of the backend (rjshk013/wanderlust-backend:<tag>
).
Checks for vulnerabilities in the OS layer and image dependencies.
--severity-threshold=critical
filters out less severe issues.
๐ณ 4. Frontend Image Scan
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'snyk-api',
failOnIssues: false,
projectName: 'wanderlust-frontend-image',
additionalArguments: "--docker ${FRONTEND_IMAGE}:${IMAGE_TAG} --severity-threshold=critical"
)
Here is the final full Pipeline script
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 {
parallel (
backend: {
dir('wanderlust-3tier-project/backend') {
sh "npm install || true"
}
},
frontend: {
dir('wanderlust-3tier-project/frontend') {
sh "npm install"
}
}
)
}
}
stage('Docker Build') {
steps {
dir('wanderlust-3tier-project') {
sh '''
docker build -f Dockerfile_multi-stage -t ${BACKEND_IMAGE}:${IMAGE_TAG} ./backend
docker build -f Dockerfile_multi-stage -t ${FRONTEND_IMAGE}:${IMAGE_TAG} ./frontend
'''
}
}
}
stage('Snyk Scanning') {
parallel {
stage('Frontend Dependency Scan') {
steps {
dir('wanderlust-3tier-project/frontend') {
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'snyk-api',
targetFile: 'package.json',
projectName: 'wanderlust-frontend-src',
failOnIssues: false,
additionalArguments: "--severity-threshold=critical"
)
}
}
}
stage('Backend Dependency Scan') {
steps {
dir('wanderlust-3tier-project/backend') {
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'snyk-api',
targetFile: 'package.json',
projectName: 'wanderlust-backend-src',
failOnIssues: false,
additionalArguments: "--severity-threshold=critical"
)
}
}
}
stage('Backend Image Scan') {
steps {
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'snyk-api',
failOnIssues: false,
projectName: 'wanderlust-backend-image',
additionalArguments: "--docker ${BACKEND_IMAGE}:${IMAGE_TAG} --severity-threshold=critical"
)
}
}
stage('Frontend Image Scan') {
steps {
snykSecurity(
snykInstallation: 'snyk',
snykTokenId: 'snyk-api',
failOnIssues: false,
projectName: 'wanderlust-frontend-image',
additionalArguments: "--docker ${FRONTEND_IMAGE}:${IMAGE_TAG} --severity-threshold=critical"
)
}
}
}
}
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('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 EC2 Host with Docker Compose') {
steps {
sshagent(credentials: ['deploy-server-ssh']) {
sh '''
echo "๐ Deploying on EC2 instance with docker compose..."
ssh -o StrictHostKeyChecking=no user@172.18.0.1 '
cd /home/user/devops-projects/wanderlust-3tier-project &&
docker compose pull &&
docker compose up -d
'
'''
}
}
}
}
post {
always {
echo '๐ฆ Pipeline execution completed'
deleteDir()
}
success {
echo "โ
Wanderlust pipeline succeeded: Build #${BUILD_NUMBER}"
}
failure {
echo "โ Wanderlust pipeline failed: Build #${BUILD_NUMBER}"
}
}
}
โ Final Pipeline Flow
- ๐ Parallel install and scan stages
- ๐ณ Multistage build for backend & frontend
- ๐ก๏ธ Snyk scan for both code & containers

๐ณ Docker Image Size After Multistage Build
After applying multistage builds to both frontend and backend Dockerfiles, we checked the image size using below command on our host machine or jenkins agent container
docker images


๐ Result:
The image size has been significantly reduced, confirming the effectiveness of multistage optimization by eliminating unnecessary dependencies and build artifacts from the final image.
๐ Snyk Dashboard Insights After CI/CD Run
Once our Jenkins pipeline completes the dependency and image scans for both backend and frontend using the Snyk Security Plugin
, the vulnerabilities are automatically reflected in the Snyk dashboard:
๐ Project Segmentation
The dashboard intelligently groups the scanned artifacts:
wanderlust-backend-image
wanderlust-frontend-image
- Path-based:
/app/package.json
/usr/local/lib/node_modules
- other specific build locations (like
esbuild
)
๐ Key Highlights from the Report:
๐ wanderlust-backend-image
- 1 Critical, 7 High, 10 Medium, 42 Low severity vulnerabilities
- Deep analysis of Docker layers, package.json, and node_modules directories

๐ wanderlust-frontend-image
- Similar breakdown: 1 Critical, 5 High, 10 Medium, 40 Low
- Even detects vulnerabilities in build-time binaries (e.g.,
esbuild
)

๐งฉ Source Code Scan
- Source repositories like
wanderlust-backend-src
andwanderlust-frontend-src
scanned viapackage.json
- Backend source has 6 high-severity issues; frontend source has 3

๐ฎ What’s Coming in Part 3?
In the next blog post, Iโll cover:
- ๐ Visual build metrics using dashboards for performance insights
๐ Slack and email notifications to keep teams informed in real time
๐ณ Advanced Docker optimizations using layer caching to reduce build time
๐ก๏ธ Security and performance best practices for production-grade pipelines
๐ Part 1 Recap: How I built a CI/CD pipeline with Jenkins, SonarQube & Trivy
๐ฌ Drop your thoughts or ask questions in the comments โ and follow for Part 3!
One thought on “Part 2: How to build CI/CD with Jenkins & Snyk”