In this project, I designed and implemented an end-to-end CI/CD pipeline using Docker, Docker Compose, and Jenkins for a simple 2-tier web application. The objective was to automate the complete application lifecycle — from code commit to deployment — while following practical DevOps and containerization best practices.
The pipeline is triggered automatically on every code push and performs build, runtime validation, security scanning, image publishing, and deployment without manual intervention.
Build a complete CI/CD system that automatically tests, builds, and deploys a simple web application through development, staging, and production environments using Docker and Jenkins.
| Layer | Technology |
|---|---|
| Frontend | HTML + CSS served via Nginx |
| Backend | Python Flask |
| Database | PostgreSQL |
| CI/CD | Jenkins |
| Containerization | Docker |
| Orchestration | Docker Compose |
| Image Registry | Docker Hub |
| Security Scan | Trivy |
| Webhook Exposure | ngrok |
| Version Control | GitHub |
The project was designed by first building and testing all components locally, then integrating GitHub, Docker Hub, and Jenkins to create a fully automated CI/CD workflow. Since Jenkins was running locally, ngrok was used to expose it publicly so that GitHub webhooks could trigger the pipeline automatically.
Project Design Flow Chart (How the Project Was Built)
Local Development (My PC)
|
| Create application files
| - Frontend
| - Backend
| - Dockerfiles
| - docker-compose.yml
| - Jenkinsfile
v
Local Testing with Docker Compose
|
| Verify application works locally
v
Push Source Code to GitHub
|
| Upload Docker images
v
Push Images to Docker Hub
|
v
Run Jenkins Using Docker Image
|
| Jenkins runs in container
v
Connect GitHub Repository to Jenkins
|
v
Configure GitHub Webhook
|
| Jenkins running on localhost
v
Expose Jenkins Using ngrok
|
| Convert localhost to public URL
v
GitHub Webhook Trigger Enabled
|
v
Automatic CI/CD Pipeline Execution
The application follows a 2-tier architecture with CI/CD automation.
Architecture Flow:
- Developer pushes code to GitHub
- GitHub Webhook triggers Jenkins automatically
- Jenkins pipeline builds, tests, scans, and pushes images
- Jenkins deploys application to target environment
- Frontend communicates with backend
- Backend interacts with PostgreSQL database
- Static HTML/CSS application
- Served using Nginx
- Displays:
- Application health status
- Database connection status
- Total record count
- Employee data table
User Browser
|
| HTTP Request (Port 80)
v
Frontend Container (Nginx)
|
| Serves static HTML / JS
v
index.html
|
| fetch()
v
Backend API (/health, /db-status)
|
v
Render Status & Data in UI
- Flask REST API
- Provides endpoints:
- /health – application health
- /db-status – database status and employee records
- Uses environment variables for database connection
Frontend Request
|
| HTTP Request (Port 5000)
v
Backend Container (Flask)
|
| /health endpoint
|---------------------> Returns "OK"
|
| /db-status endpoint
v
Connect to Database
|
v
Query Employee Records
|
v
JSON Response to Frontend
- PostgreSQL container
- Stores employee records
- Persistent volume for data durability
Backend Container
|
| SQL Query
v
PostgreSQL Container
|
| Authentication
v
employees Table
|
| Fetch Records
v
Result Set
|
v
Backend Response
cicd-capstone/
│
├── backend/
│ ├── app.py # Flask backend application
│ ├── requirements.txt # Python dependencies
│ └── Dockerfile # Multi-stage backend Dockerfile
│
├── frontend/
│ ├── index.html # UI displaying app & database status
│ └── Dockerfile # Nginx-based frontend Dockerfile
│
├── scripts/
│ └── deploy.sh # Environment-based deployment script
│
├── .env.dev # Development environment variables
├── .env.staging # Staging environment variables
├── .env.prod # Production environment variables
│
├── docker-compose.yml # Multi-service orchestration
├── Jenkinsfile # CI/CD pipeline definition
├── README.md # Project documentation
List of all tools used in the project, along with their purpose and where they are implemented.
Purpose: Containerization platform.
Usage in Project:
- Containerizes frontend, backend, database, and Jenkins
- Ensures consistency across environments
- Used in Jenkins pipeline for image build, push, and deployment
Purpose: Multi-container orchestration.
Usage in Project:
- Defines frontend, backend, and database services
- Creates shared networks and persistent volumes
- Used for local development, testing, and deployment
Purpose: CI/CD automation tool.
Usage in Project:
- Runs as a Docker container
- Executes full CI/CD pipeline defined in Jenkinsfile
- Handles build, test, scan, push, and deployment stages
- Includes manual approval gate for production
Purpose: Source code management.
Usage in Project:
- Stores application source code
- Triggers Jenkins pipeline using GitHub Webhooks
- Acts as the CI trigger point
Purpose: Automatic pipeline triggering.
Usage in Project:
- Jenkins pipeline starts automatically on every git push
- Eliminates manual “Build Now”
- Enables true Continuous Integration
Purpose: Public URL tunneling.
Usage in Project:
- Exposes locally running Jenkins to the public internet
- Required because GitHub Webhooks need a public endpoint
- Used only for webhook communication
Purpose: Backend web framework.
Usage in Project:
- Provides REST APIs
- Exposes /health and /db-status endpoints
- Connects to PostgreSQL database
- Returns JSON data consumed by frontend
Purpose: Cross-Origin Resource Sharing.
Usage in Project:
- Allows frontend (port 80) to access backend APIs (port 5000)
- Prevents browser CORS blocking
- Required for frontend–backend communication
Purpose: Web server.
Usage in Project:
- Serves static frontend files
- Used with nginx:alpine image for minimal size
- Acts as production-grade frontend server
Purpose: Relational database.
Usage in Project:
- Stores employee records
- Runs as a Docker container
- Connected to backend using environment variables
- Data displayed dynamically on frontend UI
Purpose: Container security scanning.
Usage in Project:
- Scans Docker images for vulnerabilities
- Integrated into Jenkins pipeline
- Ensures secure images before deployment
Purpose: Deployment automation.
Usage in Project:
- deploy.sh handles environment-specific deployment
- Automates container restart and health verification
- Used by Jenkins during CD stages
- Docker
- Docker Compose
- Git
- Jenkins
git clone https://github.com/PavanSPK/cicd-capstone.git
cd cicd-capstone
docker pull spk487/cicd-backend:latest
docker pull spk487/cicd-frontend:latest
docker-compose up -d
- Frontend UI: http://localhost
- Backend API: http://localhost:5000
- Health Endpoint: http://localhost:5000/health
Backend API: http://localhost:5000
Health Endpoint: http://localhost:5000/health
The backend image follows container best practices:
- Multi-stage build to reduce image size
- Non-root user (appuser) for security
- Optimized layer caching
- Minimal final runtime image.
Non-root User: appuser

- Based on nginx:alpine
- Serves static content only
- Extremely small image footprint
Docker images were optimized using best practices.
- Backend image uses multi-stage builds to exclude build-time dependencies.
- Frontend image uses Nginx Alpine for a minimal runtime footprint.
Image Content Size:
- Backend (multi-stage): ~56 MB
- Frontend (Nginx Alpine): ~23 MB
docker-compose.yml includes:
- backend
- frontend
- db (PostgreSQL)
- Isolated Docker network
- Named volume for database persistence
- Environment variables for database configuration Docker Compose is used both locally and during deployment to ensure environment consistency.
The complete CI/CD workflow is defined in the Jenkinsfile
Pipeline Stages:
- Checkout Code
- Build Docker Images
- Container Health Test
- Security Scan (Trivy)
- Push Images to Docker Hub
- Deploy to Development
- Deploy to Staging
- Manual Approval
- Deploy to Production
After building the images, Jenkins:
- Starts the containers using Docker Compose
- Polls the /health endpoint
- Retries automatically if needed
- Fails the pipeline if the service does not become healthy
Jenkins Pipeline
|
v
+---------------------------+
| Build Docker Images |
+---------------------------+
|
v
+---------------------------+
| Start Containers |
| (docker-compose up -d) |
+---------------------------+
|
v
+---------------------------+
| Poll /health Endpoint |
| (Backend Service) |
+---------------------------+
|
v
+---------------------------+
| Is Service Healthy? |
+-----------+---------------+
|
+------+------+
| |
v v
+-----------+ +----------------------+
| YES | | NO |
| | | Retry Health Check |
| Continue | | (Wait & Recheck) |
| Pipeline | +----------+-----------+
+-----------+ |
|
+------+------+
| Max Retries?|
+------+------+
|
+--------+--------+
| |
v v
+---------------+ +----------------------+
| NO | | YES |
| Retry Again | | Fail Jenkins Pipeline|
+---------------+ +----------------------+
Trivy is integrated into the pipeline as a mandatory gate.
- Scans backend image
- Checks for HIGH and CRITICAL vulnerabilities
- Pipeline fails immediately if issues are found This step ensures that insecure images are never deployed.
- Backend and frontend images are pushed separately
- Docker Hub access token is used (no plaintext credentials)
- Images are versioned and reusable across environments
Although the same Docker Compose configuration is reused, logical environments are clearly defined through execution context and process separation.
- Local machine execution
- Manual docker-compose up
- Used for development and local testing
- Jenkins pipeline execution
- Automated build, test, scan, and validation
- Acts as a controlled pre-production gate
- Final Jenkins deployment stage
- Uses Docker Hub images
- Fully automated deployment via Docker Compose
- Every GitHub push triggers Jenkins automatically
- No manual intervention required
- ngrok provides public webhook access
- Jenkins pulls latest images
- Stops old containers
- Starts new containers
- Verifies deployment using /health endpoint
- Production deployment requires manual approval
deploy.sh performs:
- Existing containers are stopped
- Latest images are pulled from Docker Hub
- New containers are started using Docker Compose
- No manual deployment steps are required
Start deploy.sh
|
v
Read Environment Argument
|
v
Select Correct .env File
|
v
Pull Latest Docker Images
|
v
Stop Existing Containers
|
v
Start New Containers
|
v
Run Health Check
|
v
Deployment SUCCESS / FAILURE
This section lists common issues encountered during local execution or CI/CD pipeline runs, along with quick resolutions.
Symptoms: Containers exit or application is unreachable.
Fix:
docker-compose logs
Symptoms: UI loads, API endpoints fail.
Fix:
docker-compose logs backend
docker-compose restart backend
Symptoms: Jenkins pipeline fails at /health check.
Fix:
curl http://localhost:5000/health
Ensure Flask binds to 0.0.0.0 and backend container is running.
Symptoms: Backend crashes with DB errors.
Fix:
docker-compose logs db
docker-compose restart db
Verify database environment variables.
Symptoms: Push does not start Jenkins job.
Fix:
Verify webhook URL ends with /github-webhook/ and Jenkins is publicly reachable (ngrok if local).
Symptoms: Pipeline stops at security scan.
Fix:
Update base images or dependencies. Failure is expected for HIGH/CRITICAL vulnerabilities.
Symptoms: Latest changes not reflected.
Fix:
docker-compose down
docker-compose pull
docker-compose up -d
Ensure Jenkins has access to Docker:
-v /var/run/docker.sock:/var/run/docker.sock
This capstone project demonstrates an end-to-end CI/CD pipeline using Jenkins and Docker to automate the complete workflow from code commit to deployment for a containerized web application. By integrating GitHub webhooks, optimized Docker builds, automated security scanning, and reliable service deployment, the project follows real-world DevOps practices and validates a stable frontend–backend–database integration in a production-like environment.
Sandu Pavan Kumar
GitHub: @PavanSPK
















