DEV Community

Cover image for πŸš€ Building a Real-World CI/CD Pipeline with Jenkins, Docker, and Docker Compose
Vivek Singh
Vivek Singh

Posted on

πŸš€ Building a Real-World CI/CD Pipeline with Jenkins, Docker, and Docker Compose

From broken builds to a fully automated deployment pipeline.

As part of my DevOps learning journey, I wanted to build something practical, realistic, and close to production workflows β€” not just follow tutorials.

So I built a two-tier web application and automated its entire build, test, and deployment lifecycle using Jenkins, Docker, and Docker Compose.

This article walks through:
β€’ The architecture I built
β€’ The CI/CD pipeline design
β€’ Real issues I faced (and fixed)
β€’ Key DevOps learnings


🧱 Project Overview

The goal was simple but important:

Automate everything from code commit to deployment β€” with no manual steps.

Tech Stack
β€’ Frontend / API: Flask (Python)
β€’ Database: MySQL
β€’ CI/CD: Jenkins (Pipeline as Code)
β€’ Containerization: Docker
β€’ Orchestration: Docker Compose
β€’ Version Control: GitHub


πŸ— Architecture Overview

The application follows a classic two-tier architecture:
β€’ Web tier: Flask application
β€’ Data tier: MySQL database
β€’ Both services run in separate Docker containers
β€’ Managed via Docker Compose

GitHub
  ↓
Jenkins Pipeline
  ↓
Docker Build
  ↓
Tests
  ↓
Docker Compose Deploy
Enter fullscreen mode Exit fullscreen mode

🐳 Docker & Docker Compose Setup

Docker Compose is used to define and run multiple containers together.

Services:
β€’ db: MySQL 8 container
β€’ web: Flask application container

Key highlights:
β€’ Database initialization using init.sql
β€’ Environment variables for DB connectivity
β€’ Service dependency (web depends on db)

πŸ“Έ Image: docker-compose.yml (services definition)


🐍 Flask Application

The Flask app exposes simple endpoints:
β€’ / β†’ health check
β€’ /users β†’ fetches data from MySQL

The database connection is handled using environment variables, making the app environment-agnostic.

πŸ“Έ Image: db.py (MySQL connection using env variables)

πŸ“Έ Image: app.py (Flask routes and app runner)

This design ensures:
β€’ No hardcoded credentials
β€’ Easy containerization
β€’ Smooth CI/CD execution


πŸ‹ Dockerfile (Application Image)

The Flask app is containerized using a simple, optimized Dockerfile:
β€’ Lightweight base image (python:3.9-slim)
β€’ Dependency installation via requirements.txt
β€’ Exposes port 5000
β€’ Runs the app using CMD

πŸ“Έ Image: Dockerfile


βš™οΈ Jenkins CI/CD Pipeline

This is where the real DevOps work happens.

The pipeline is written using Jenkins Declarative Pipeline and stored as a Jenkinsfile in the repository.

Pipeline Stages:
1. Checkout – Pull source code from GitHub
2. Build Image – Build Docker images using Docker Compose
3. Run Tests – Start DB container and validate application
4. Deploy – Bring up all services using Docker Compose

πŸ“Έ Image: Jenkinsfile (pipeline stages)


πŸ“Š Jenkins Stage View (Pipeline Execution)

The Jenkins Stage View clearly shows:
β€’ Which stage failed
β€’ How long each stage took
β€’ When the pipeline finally turned green

This helped massively during debugging.

πŸ“Έ Image: Jenkins Stage View (failed β†’ successful build)


🧨 Real Issues I Faced (and Fixed)

This project was not smooth, and that’s where the learning came from.

πŸ”΄ Issue 1: Jenkins couldn’t find Jenkinsfile
β€’ Root cause: Case sensitivity
β€’ jenkinsfile β‰  Jenkinsfile

βœ… Fixed by forcing correct filename in Git


πŸ”΄ Issue 2: docker: not found in Jenkins

β€’ Jenkins was running inside a container
β€’ Docker CLI wasn’t available inside Jenkins
Enter fullscreen mode Exit fullscreen mode

βœ… Installed Docker CLI inside Jenkins container


πŸ”΄ Issue 3: Docker Compose v1 vs v2 mismatch

β€’ Jenkins environment only had docker-compose
β€’ Pipeline was using docker compose
Enter fullscreen mode Exit fullscreen mode

βœ… Aligned Jenkinsfile with available tooling


πŸ”΄ Issue 4: Permission denied on /var/run/docker.sock

β€’ Jenkins user couldn’t talk to Docker daemon
β€’ Linux socket permission issue
Enter fullscreen mode Exit fullscreen mode

βœ… Fixed by adjusting Docker socket permissions (for local CI)


βœ… Final Result

After resolving all issues:
β€’ βœ” Fully automated CI/CD pipeline
β€’ βœ” Build β†’ Test β†’ Deploy without manual steps
β€’ βœ” Dockerized multi-container application
β€’ βœ” Jenkins pipeline running clean and green

πŸ“Έ Image: Successful Jenkins pipeline run


πŸ“š Key Learnings

This project taught me far more than any tutorial:
β€’ DevOps is more about debugging systems than writing YAML
β€’ Linux fundamentals matter a lot
β€’ CI/CD environments behave differently than local machines
β€’ Logs are your best debugging tool
β€’ Small permission issues can break entire pipelines
β€’ Automation only works when every layer is understood


πŸ”— Source Code

GitHub Repository:
πŸ‘‰ https://github.com/developedbyviv/Two-tier-Architecture-Application

Top comments (1)

Collapse
 
bcouetil profile image
Benoit COUETIL πŸ’«

Jenkins in 2025... you are funny πŸ˜†