A containerized Java web application (vprofile) backed by MySQL, packaged with
Docker and deployed across a Docker Swarm cluster.
This section documents the end-to-end CI/CD pipeline used to build, validate, scan, and deploy the application. The pipeline takes source code through static analysis, artifact publishing, image hardening, and a high-availability rollout onto a multi-node Docker Swarm cluster.
The workflow is orchestrated by Jenkins running on a dedicated build agent, and progresses through eight stages — from source checkout to a replicated Swarm deployment.
┌──────────────┐
│ Developer │ git push
│ (GitHub) │────────────────┐
└──────────────┘ │
▼
┌───────────────────────────────────────────────────────────────────────┐
│ JENKINS CI SERVER (slave: Project-1) │
│ │
│ 1. Checkout ──► 2. SonarQube ──► 3. Maven Build ──► 4. Nexus Upload │
│ (SCM) (analysis) (mvn package) (WAR artifact) │
│ │ │ │
│ └──────────────► 5. Docker Build ◄──┘ │
│ │ │
│ ▼ │
│ 6. Trivy Scan (vulnerability gate) │
│ │ │
│ ▼ │
│ 7. docker push ──► Docker Hub │
└───────────────────────────────────────────────┬───────────────────────┘
│ 8. docker stack deploy
▼
┌───────────────────────────────────────────────────────────────────────┐
│ DOCKER SWARM CLUSTER (overlay net) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Manager │ │ Worker 1 │ │ Worker 2 │ │
│ │ │ │ │ │ │ │
│ │ app x N │ │ app x N │ │ app x N │ :7777→8080 │
│ │ db x N │ │ db x N │ │ db x N │ :3306 │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ 3 replicas per service · self-healing · load-balanced │
└───────────────────────────────────────────────────────────────────────┘
The web application and database sources (each with its own Dockerfile) are
checked out from GitHub onto the Jenkins CI server. The pipeline is pinned to a
dedicated build agent (Project-1) so that builds run in an isolated,
reproducible environment rather than on the Jenkins controller.
pipeline {
agent { label 'Project-1' } // dedicated slave node
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/<owner>/dockerwebapplication.git'
}
}
}
}The supporting infrastructure runs on AWS EC2 — a Jenkins controller, the Swarm manager, a Nexus host, and two worker nodes:
In Jenkins, the build is pinned to the dedicated Project-1 agent rather than
the built-in controller node:
Static code analysis is performed with SonarQube to enforce quality gates before any artifact is built.
- Install the SonarQube Scanner plugin in Jenkins.
- Register the SonarQube server and authentication token under Manage Jenkins → System → SonarQube servers.
- Wrap the analysis stage in
withSonarQubeEnvso Jenkins injects the server URL and credentials automatically.
stage('SonarQube Analysis') {
environment {
scannerHome = tool 'sonar-scanner'
}
steps {
withSonarQubeEnv('sonar-server') {
sh '''
${scannerHome}/bin/sonar-scanner \
-Dsonar.projectKey=vprofile \
-Dsonar.sources=src/ \
-Dsonar.java.binaries=target/classes
'''
}
}
}Analysis results — code smells, bugs, coverage, and the quality gate status — are published to the SonarQube dashboard:
Individual issues (bugs, vulnerabilities, and security hotspots) can be drilled into directly against the source:
The Maven tool is configured in Jenkins (Manage Jenkins → Tools → Maven) and used to compile, test, and package the application into a deployable WAR.
stage('Build') {
steps {
sh 'mvn clean package'
}
}# Produces the deployable artifact:
target/vprofile-v2.warThe Jenkins console confirms a successful Maven run with the WAR packaged:
The packaged WAR is published to a Nexus Repository Manager, providing versioned, immutable artifact storage and a single source of truth for releases.
stage('Upload Artifact') {
steps {
nexusArtifactUploader(
nexusVersion: 'nexus3',
protocol: 'http',
nexusUrl: '<nexus-host>:8081',
groupId: 'com.visualpathit',
version: "${BUILD_ID}",
repository: 'vprofile-release',
credentialsId: 'nexus-login',
artifacts: [[
artifactId: 'vprofile',
file: 'target/vprofile-v2.war',
type: 'war'
]]
)
}
}The uploaded artifact, with its checksums, is then browsable in Nexus:
Application and database images are built from the Dockerfiles tracked in the
repository. The web image layers the WAR onto Tomcat; the database image seeds
MySQL with the accounts schema.
# Application image (Tomcat 8 / JRE 11, ROOT.war)
docker build -t nikhilkotharu/dockerapp:web ./Docker-app
# Database image (MySQL 5.7.25, accounts DB)
docker build -t nikhilkotharu/dockerapp:db ./Docker-dbTrivy is installed on the Swarm manager node and scans the freshly built images for OS-package and application-dependency vulnerabilities, acting as a security gate before publishing.
# Install Trivy on the manager node
sudo apt-get install -y wget apt-transport-https gnupg
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" \
| sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install -y trivy
# Scan images — fail the build on HIGH/CRITICAL findings
trivy image --severity HIGH,CRITICAL nikhilkotharu/dockerapp:web
trivy image --severity HIGH,CRITICAL nikhilkotharu/dockerapp:dbOnce images clear the Trivy gate, they are pushed to Docker Hub so every Swarm node can pull identical, scanned artifacts.
docker login -u nikhilkotharu
docker push nikhilkotharu/dockerapp:web
docker push nikhilkotharu/dockerapp:dbBoth tags (web and db) land in the Docker Hub repository, ready for the
Swarm nodes to pull:
The application is rolled out as a Docker Stack defined in
compose.yml. A stack is the bridge between the two worlds:
it consumes a standard Docker Compose file but schedules each service as a
replicated, cluster-aware Swarm workload — so a single file describes both
what to run and how to run it at scale.
The file declares two services, one network, and two named volumes:
version: "3.9"
services:
devopsdb: # MySQL database service
image: nikhilkotharu/dockerapp:db
ports:
- "3306:3306" # expose MySQL on the cluster
deploy:
replicas: 3 # 3 tasks for availability
restart_policy:
condition: any # self-heal — always restart
resources:
limits: { cpus: '0.3', memory: '256m' } # hard ceiling
reservations: { cpus: '0.1', memory: '128m' } # guaranteed minimum
networks:
- mynetwork
volumes:
- db_volume:/opt/nikhil # persist data across restarts
application: # Java/Tomcat web service
image: nikhilkotharu/dockerapp:web
ports:
- "7777:8080" # host 7777 → Tomcat 8080
deploy:
replicas: 3
restart_policy:
condition: any
resources:
limits: { cpus: '0.3', memory: '256m' }
reservations: { cpus: '0.1', memory: '128m' }
depends_on:
- devopsdb # start after the database
networks:
- mynetwork
volumes:
- app_volume:/opt/nikhil
networks:
mynetwork:
driver: overlay # multi-host networking across nodes
volumes:
db_volume:
app_volume:Key directives:
| Directive | What it does |
|---|---|
deploy.replicas: 3 |
Runs 3 tasks per service, load-balanced via the routing mesh |
restart_policy.condition: any |
Self-healing — failed tasks are always rescheduled |
resources.limits / reservations |
Caps and guarantees CPU/memory per task |
networks.driver: overlay |
Multi-host network so containers talk across nodes |
volumes |
Named volumes keep data persistent across task restarts |
depends_on |
Ensures the database service starts before the application |
# Initialize the cluster (manager node, one-time)
docker swarm init
# Join workers using the token printed by `docker swarm init`
docker swarm join --token <worker-token> <manager-ip>:2377
# Deploy / update the stack from the compose file
docker stack deploy -c compose.yml stackname
# Verify the rollout
docker stack services stackname
docker service ps stackname_application| Service | Image | Replicas | Port (host → container) |
|---|---|---|---|
application |
nikhilkotharu/dockerapp:web |
3 | 7777 → 8080 (Tomcat) |
devopsdb |
nikhilkotharu/dockerapp:db |
3 | 3306 → 3306 (MySQL) |
Running multiple replicas across multiple nodes provides high availability: Swarm load-balances traffic via the routing mesh and reschedules tasks onto healthy nodes if a container or node fails.
Once the stack is up, the application is reachable on port 7777 and serves the
login and registration pages:
After authenticating, the user lands on the live profile feed — confirming the full web ↔ database round-trip is working end to end:
The Jenkins Stage View breaks down per-stage timing for a single run across all eight stages (tool install → code → CQA → build → registry → docker images → image scan → registry → deploy):
Build history across successive runs shows stage stability and trend over time:
The post-build SonarQube quality gate result and build permalinks are surfaced back in Jenkins:
At the infrastructure level, AWS CloudWatch tracks CPU and network utilization across all five cluster nodes:
Because every service is attached to the mynetwork overlay network,
containers communicate across physical nodes as if on a single L2 segment.
The ping below succeeds between container IPs (10.0.7.x) scheduled on
different hosts — proving the overlay mesh is routing inter-container traffic:
Connecting to the MySQL service confirms the accounts database and its seeded
tables were initialized correctly, with SELECT returning the expected rows:
| Stage | Tool | Purpose |
|---|---|---|
| 1 | Git / GitHub | Source code management |
| 2 | SonarQube | Static analysis & quality gate |
| 3 | Maven | Compile, test, package WAR |
| 4 | Nexus | Artifact repository |
| 5 | Docker | Image build |
| 6 | Trivy | Image vulnerability scanning |
| 7 | Docker Hub | Container registry |
| 8 | Docker Swarm | Orchestration & high-availability deploy |















